Index: cortav.ct ================================================================== --- cortav.ct +++ cortav.ct @@ -1072,11 +1072,11 @@ * [*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] * [*gemtext]: essentially a downrezzing of cortav to make it readable to Gemini clients * [*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. ** [`tabtree] [!(default)]: a hierarchical tree view, with the number of tabs preceding an item showing its depth in the tree ** [`sexp] -** [`binary]: emit a raw binary format that is easier for programs to read. maybe an lmdb or cdb file? +** [`binary]: emit a raw binary format that is easier for programs to read. maybe a msgpack file? ** [`json]: obligatory, alas flatdoc: ~~~flat sexp example output [scheme]~~~ (nodes (section (id . "section1") Index: cortav.lua ================================================================== --- cortav.lua +++ cortav.lua @@ -972,15 +972,40 @@ kind = "paragraph"; spans = ct.parse_span(l, c); } end) -local insert_subtitle = blockwrap(function(l,c) - return { - kind = "subtitle"; - spans = ct.parse_span(l:sub(3), c); +local insert_caption = blockwrap(function(l,c,j,d) + if next(d) == nil then + c:fail 'subtitle in an unlabeled section is meaningless' + end + + local last = d[#d] + local me = { + kind = 'subtitle'; + spans = ct.parse_span(l:sub(3):gsub("^%s+",""), c); + } + + local captionable = { + quote=true, aside=true, + table=true, code=true, + embed=true, link=true, } + + if last.kind == 'label' then + me.attach = last; + elseif last.kind == 'subtitle' then + me.attach = last.attach; + elseif captionable[last.kind] then + me.kind = 'label' + me.captions = last + last.label_node = me + else + c:fail 'subtitle/attribution syntax in improper context' + end + + return me end) local function insert_section(skind) return function(l,c,j) local depth, id, t = l:match '^([#§^]+)([^%s]*)%s*(.-)$' @@ -1183,11 +1208,11 @@ {seq = '¶', fn = insert_paragraph}; {seq = '❡', fn = insert_paragraph}; {seq = '#', fn = insert_section()}; {seq = '§', fn = insert_section()}; {seq = '^', fn = insert_section 'namespace'}; - {seq = '--',fn = insert_subtitle}; + {seq = '--',fn = insert_caption}; {seq = '+', fn = insert_table_row}; {seq = '|', fn = insert_table_row}; {seq = '│', fn = insert_table_row}; {seq = '!', fn = function(l,c,j,d) local last = d[#d] Index: render/groff.lua ================================================================== --- render/groff.lua +++ render/groff.lua @@ -81,15 +81,15 @@ table.insert(me.lines, "'"..r) end; esc = function(me, e) me:raw('\\' .. e) end; - draw = function(me, args) - for _,v in ipairs(args) do + draw = function(me, args) + for _,v in ipairs(args) do me:esc("D'" .. v .. "'") - end - end; + end + end; flush = function(me) if me.linbuf ~= nil then local line = me.linbuf:compile() local first = line:sub(1,1) -- make sure our lines aren't accidentally interpreted @@ -420,11 +420,11 @@ local blockRenderers = {} blockRenderers['horiz-rule'] = function(rc, b, sec) rc.prop.margin = { top = 0.3 } rc.prop.underline = 0.1 end - function blockRenderers.label(rc, b, sec) + function blockRenderers.label(rc, b, sec) if ct.sec.is(b.captions) then local visDepth = b.captions.depth + (b.origin.docDepth or 0) local sizes = {36,24,12,8,4,2} local margins = {0,3} local dedents = {2.5,1.3,0.8,0.4} @@ -445,18 +445,23 @@ rs.renderSpans(rc, b.spans, b, sec) else ss.bug 'tried to render label for an unknown object type':throw() end end - function blockRenderers.paragraph(rc, b, sec) + function blockRenderers.paragraph(rc, b, sec) + rs.renderSpans(rc, b.spans, b, sec) + end + function blockRenderers.subtitle(rc, b, sec) + rc.prop.dsz = 16 -- TODO base on "parent" label + rc.prop.emph = true rs.renderSpans(rc, b.spans, b, sec) end - function blockRenderers.macro(rc, b, sec) + function blockRenderers.macro(rc, b, sec) local rc = rc.parent:clone() rs.renderDoc(rc, b.doc) end - function blockRenderers.quote(rc, b, sec) + function blockRenderers.quote(rc, b, sec) local rc = rc.parent:clone() rc.prop.indent = (rc.prop.indent or 0) + 1 local added = rs.renderDoc(rc, b.doc) -- select last block of last section and increase bottom margin local ap = added[#added].blocks @@ -469,11 +474,11 @@ end else ap.margin = {bottom = 1.1} end end - function blockRenderers.table(rc, b, sec) + function blockRenderers.table(rc, b, sec) function rc:begin(g) g:req 'TS' local aligns = {} for i, c in ipairs(b.rows[1]) do aligns[i] = ({ Index: render/html.lua ================================================================== --- render/html.lua +++ render/html.lua @@ -261,15 +261,10 @@ color: @tone(0.3 20); font-size: 1.2em; font-style: italic; margin-left: 1em; } - blockquote + .subtitle { - &::before { - content: "— "; - } - } ]]; accent = [[ @media screen { body { background: @bg; color: @fg } a[href] { @@ -315,11 +310,20 @@ margin: 0; } section aside + aside { margin-top: 0.5em; } - ]]; + ]]; + quoteCaption = [[ + blockquote > div.caption { + text-align: right; + font-style: italic; + &::before { + content: "— "; + } + } + ]]; code = [[ code { display: inline-block; background: @tone(-1); color: @tone(0.7); @@ -1023,17 +1027,32 @@ local mime = s.mime:clone() mime.opts = {} return string.format('data:%s;base64,%s', mime, ss.str.b64e(s.raw)) end end - --figure out how to embed the given object local function P(p) -- get prop if b.props and b.props[p] then return b.props[p] end return obj.props[p] end + + local cap = b.cap or P'desc' or P'detail' + local capIR = ''; + if b.label_node then + local ln = b.label_node + capIR = sr.htmlSpan(ln.spans, ln, s) + elseif cap then + -- the block here should really be the relevant + -- ref definition if an override caption isn't + -- specified, but oh well + capIR = sr.htmlSpan(spanparse( + cap, b.origin + ), b, s) + end + + --figure out how to embed the given object local embedActs = { {ss.mime'image/*', function(s,ctr) if s == nil then return {tag = "picture", nodes = {}} else @@ -1141,13 +1160,15 @@ end end -- nothing found; install fallback link if fallback then local lnk = htmlURI(fallback.uri) - return tag('a', {href=lnk}, - tag('div',{class=xref}, - string.format("→ %s [%s]", b.cap or '', tostring(fallback.mime)))) + return tag('a', {href=lnk}, catenate { + tag('div',{class=xref}, catenate { + '→ '; capIR; + string.format(" [%s]", tostring(fallback.mime)); + })}) else addStyle 'docmeta' return tag('div',{class="render-warn"}, 'could not embed object type ' .. tostring(obj.srcs.mime)) end @@ -1158,29 +1179,20 @@ if rtype[1] < src.mime then rtype[2](src, top) end end local ft = flatten(top) - local cap = b.cap or P'desc' or P'detail' if b.mode == 'inline' then -- TODO insert caption return ft else local prop = {} if b.mode == 'open' then prop.open = true end return tag('details', prop, catenate { - tag('summary', {}, - cap and ( - -- the block here should really be the relevant - -- ref definition if an override caption isn't - -- specified, but oh well - sr.htmlSpan(spanparse( - cap, b.origin - ), b, s) - ) or ''); + tag('summary', {}, capIR); ft; }) end end @@ -1190,10 +1202,15 @@ return tag(nil, {}, cat) end function block_renderers.quote(b,s) local ir = renderSubdoc(b.doc) + if b.label_node then + addStyle 'quoteCaption' + table.insert(ir, tag('div', {class='caption'}, + sr.htmlSpan(b.label_node.spans, b.label_node, s))) + end return tag('blockquote', b.id and {id=getSafeID(b)} or {}, catenate(ss.map(flatten,ir))) end return block_renderers end