@@ -16,8 +16,19 @@ local getSafeID = ct.tool.namespace() local footnotes = {} local footnotecount = 0 + + local cdata = function(...) return ... end + if opts.epub then + opts.xhtml = true + end + + if opts.xhtml then + cdata = function(s) + return '' + end + end local langsused = {} local langpairs = { lua = { color = 0x9377ff }; @@ -47,9 +58,9 @@ ]]; list_ordered = [[]]; list_unordered = [[]]; footnote = [[ - div.footnote { + aside.footnote { font-family: 90%; grid-template-columns: 1em 1fr min-content; grid-template-rows: 1fr min-content; position: fixed; @@ -57,9 +68,9 @@ background: @tone(0.03); margin:auto; } @media screen { - div.footnote { + aside.footnote { display: grid; left: 10em; right: 10em; max-width: calc(@width + 2em); @@ -69,9 +80,9 @@ transform: translateY(200%); transition: 0.4s; z-index: 100; } - div.footnote:target { + aside.footnote:target { transform: translateY(0%); } #cover { position: fixed; @@ -85,25 +96,25 @@ transition: 1s; pointer-events: none; backdrop-filter: blur(0px); } - div.footnote:target ~ #cover { + aside.footnote:target ~ #cover { opacity: 100%; pointer-events: all; backdrop-filter: blur(5px); } } @media print { - div.footnote { + aside.footnote { display: grid; position: relative; } - div.footnote:first-of-type { + aside.footnote:first-of-type { border-top: 1px solid black; } } - div.footnote > a[href="#0"]{ + aside.footnote > a[href="#0"]{ grid-row: 2/3; grid-column: 3/4; display: block; text-align: center; @@ -119,33 +130,33 @@ user-select: none; -webkit-user-drag: none; user-drag: none; } - div.footnote > a[href="#0"]:hover { + aside.footnote > a[href="#0"]:hover { background: @tone(0.3); color: @tone(2); } - div.footnote > a[href="#0"]:active { + aside.footnote > a[href="#0"]:active { background: @tone(0.05); color: @tone(0.4); } @media print { - div.footnote > a[href="#0"]{ + aside.footnote > a[href="#0"]{ display:none; } } - div.footnote > div.number { + aside.footnote > div.number { text-align:right; grid-row: 1/2; grid-column: 1/2; } - div.footnote > div.text { + aside.footnote > div.text { grid-row: 1/2; grid-column: 2/4; padding-left: 1em; overflow-y: auto; } - div.footnote > div.text > p:first-child { + aside.footnote > div.text > p:first-child { margin-top: 0; } ]]; header = [[ @@ -164,9 +175,8 @@ } :is(h1,h2,h3,h4,h5,h6) + p { margin-top: 0.4em; } - ]]; headingAnchors = [[ :is(h1,h2,h3,h4,h5,h6) > a[href].anchor { text-decoration: none; @@ -254,8 +264,11 @@ margin-top: 0.6em; } section > aside p:first-child { margin: 0; + } + section aside + aside { + margin-top: 0.5em; } ]]; code = [[ code { @@ -439,17 +452,25 @@ return renderJob:hook(h, render_state_handle, ...) end local tagproc do - local elt = function(t,attrs) - return f('<%s%s>', t, - attrs and ss.reduce(function(a,b) return a..b end, '', + local html_open = function(t,attrs) + if attrs then + return t .. ss.reduce(function(a,b) return a..b end, '', ss.map(function(v,k) if v == true then return ' '..k elseif v then return f(' %s="%s"', k, v) end - end, attrs)) or '') + end, attrs)) + else return t end + end + + local elt = function(t,attrs) + if opts.xhtml then + return f('<%s />', html_open(t,attrs)) + end + return f('<%s>', html_open(t,attrs)) end tagproc = { toTXT = { @@ -471,9 +492,9 @@ }; toHTML = { elt = elt; tag = function(t,attrs,body) - return f('%s%s', elt(t,attrs), body, t) + return f('<%s>%s', html_open(t,attrs), body, t) end; catenate = table.concat; }; } @@ -481,11 +502,21 @@ local function getBaseRenderers(procs, span_renderers) local tag, elt, catenate = procs.tag, procs.elt, procs.catenate local htmlDoc = function(title, head, body) - return [[]] .. tag('html',nil, + local attrs + local header = [[]] + if opts['epub'] then + -- so cursed + attrs = { + xmlns = "http://www.w3.org/1999/xhtml"; + ['xmlns:epub'] = "http://www.idpf.org/2007/ops"; + } + header = [[]] + end + return header .. tag('html',attrs, tag('head', nil, - elt('meta',{charset = 'utf-8'}) .. + (opts.epub and '' or elt('meta',{charset = 'utf-8'})) .. (title and tag('title', nil, title) or '') .. (head or '')) .. tag('body', nil, body or '')) end @@ -648,9 +679,14 @@ return htmlSpan(d.spans, b, s) end end function span_renderers.footnote(f,b,s) - addStyle 'footnote' + local linkattr = {} + if opts.epub then + linkattr['epub:type'] = 'noteref' + else + addStyle 'footnote' + end local source, sid, ssec = b.origin:ref(f.ref) local cnc = getSafeID(ssec) .. ' ' .. sid local fn if footnotes[cnc] then @@ -660,14 +696,17 @@ fn = {num = footnotecount, origin = b.origin, fnid=cnc, source = source} fn.id = getSafeID(fn) footnotes[cnc] = fn end - return tag('a', {href='#'..fn.id}, htmlSpan(f.spans) .. + linkattr.href = '#'..fn.id + return tag('a', linkattr, htmlSpan(f.spans) .. tag('sup',nil, fn.num)) end return span_renderers end + + local astproc local function getBlockRenderers(procs, sr) local tag, elt, catenate = procs.tag, procs.elt, procs.catenate local null = function() return catenate{} end @@ -749,8 +788,33 @@ -- lists need to be rewritten to work like asides return ''; end; } + + function block_renderers.quote(b,s) + local ir = {} + local toIR = block_renderers + for i, sec in ipairs(b.doc.secorder) do + local secnodes = {} + for i, bl in ipairs(sec.blocks) do + if toIR[bl.kind] then + table.insert(secnodes, toIR[bl.kind](bl,sec)) + end + end + if next(secnodes) then + if b.doc.secorder[2] then --#secs>1? + -- only wrap in a section if >1 section + table.insert(ir, tag('section', + {id = getSafeID(sec)}, + secnodes)) + else + ir = secnodes + end + end + end + return tag('blockquote', b.id and {id=getSafeID(b)} or {}, catenate(ir)) + end + return block_renderers; end local function getRenderers(procs) @@ -759,9 +823,9 @@ r.block_renderers = getBlockRenderers(procs, r) return r end - local astproc = { + astproc = { toHTML = getRenderers(tagproc.toHTML); toTXT = getRenderers(tagproc.toTXT); toIR = { }; } @@ -779,17 +843,16 @@ -- yikes this needs to be cleaned up so badly local ir = {} local dr = astproc.toHTML -- default renderers local plainr = astproc.toTXT - local irBlockRdrs = astproc.toIR.block_renderers; render_state_handle.ir = ir; local function renderBlocks(blocks, irs) for i, block in ipairs(blocks) do local rd - if irBlockRdrs[block.kind] then - rd = irBlockRdrs[block.kind](block,sec) + if astproc.toIR.block_renderers[block.kind] then + rd = astproc.toIR.block_renderers[block.kind](block,sec) else local rdr = renderJob:proc('render',block.kind,'html') if rdr then rd = rdr({ @@ -817,8 +880,9 @@ runhook('ir_section_node_insert', rd, irs, sec) end end end + runhook('ir_assemble', ir) for i, sec in ipairs(doc.secorder) do if doctitle == nil and sec.depth == 1 and sec.heading_node then doctitle = astproc.toTXT.htmlSpan(sec.heading_node.spans, sec.heading_node, sec) @@ -829,31 +893,63 @@ irs = {tag='section',attrs={id = getSafeID(sec)},nodes={}} runhook('ir_section_build', irs, sec) renderBlocks(sec.blocks, irs) end - elseif sec.kind == 'blockquote' then + elseif sec.kind == 'quote' then elseif sec.kind == 'listing' then elseif sec.kind == 'embed' then end if irs then table.insert(ir, irs) end end - for _, fn in pairs(footnotes) do - local tag = tagproc.toIR.tag - local body = {nodes={}} - local ftir = {} - for l in fn.source:gmatch('([^\n]*)') do - ct.parse_line(l, fn.origin, ftir) + do local fnsorted = {} + for _, fn in pairs(footnotes) do + fnsorted[fn.num] = fn + end + + for _, fn in ipairs(fnsorted) do + local tag = tagproc.toIR.tag + local body = {nodes={}} + local ftir = {} + for l in fn.source:gmatch('([^\n]*)') do + ct.parse_line(l, fn.origin, ftir) + end + renderBlocks(ftir,body) + local fattr = {id=fn.id} + if opts.epub then + ---UUUUUUGHHH + local npfx = string.format('(%u) ', fn.num) + if next(body.nodes) then + local n = body.nodes[1] + repeat + if n.nodes[1] then + if type(n.nodes[1]) == 'string' then + n.nodes[1] = npfx .. n.nodes[1] + break + end + n = n.nodes[1] + else + n.nodes[1] = {tag='p',attrs={},nodes={npfx}} + break + end + until false + + else + body.nodes[1] = {tag='p',attrs={},nodes={npfx}} + end + fattr['epub:type'] = 'footnote' + else + fattr.class = 'footnote' + end + local note = tag('aside', fattr, opts.epub and body.nodes or { + tag('div',{class='number'}, tostring(fn.num)), + tag('div',{class='text'}, body.nodes), + tag('a',{href='#0'},'⤫') + }) + table.insert(ir, note) end - renderBlocks(ftir,body) - local note = tag('div',{class='footnote',id=fn.id}, { - tag('div',{class='number'}, tostring(fn.num)), - tag('div',{class='text'}, body.nodes), - tag('a',{href='#0'},'⤫') - }) - table.insert(ir, note) end - if next(footnotes) then + if next(footnotes) and not opts.epub then table.insert(ir, tagproc.toIR.tag('div',{id='cover'},'')) end -- restructure passes @@ -1015,9 +1111,9 @@ end if opts.accent then table.insert(styles, string.format(':root {--accent:%s}', opts.accent)) end - if opts.accent or (not opts['dark-on-light']) and (not opts['fossil-uv']) then + if not opts.epub and (opts.accent or (not opts['dark-on-light']) and (not opts['fossil-uv'])) then addStyle 'accent' end @@ -1034,9 +1130,9 @@ styletag = styletag .. tagproc.toHTML.elt('link',{rel='stylesheet',type='text/css',href=opts['link-css']}) end if next(styles) then if opts['gen-styles'] then - styletag = styletag .. tagproc.toHTML.tag('style',{type='text/css'},table.concat(styles)) + styletag = styletag .. tagproc.toHTML.tag('style',{type='text/css'},cdata(table.concat(styles))) end table.insert(head, styletag) end