// abc2svg - tohtml.js - HTML+SVG generation
//
// Copyright (C) 2014-2025 Jean-François Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with abc2svg. If not, see <http://www.gnu.org/licenses/>.
var init_done, pw, ml, mr, pkf, lkf, fn,
h_sty = ""
// replace <>& by XML character references
function clean_txt(txt) {
return txt.replace(/<|>|&.*?;|&/g, function(c) {
switch (c) {
case '<': return "<"
case '>': return ">"
case '&': return "&"
}
return c
})
}
abc2svg.abort = function(e) {
if (!init_done) // if empty document
user.img_out('')
abc.get_parse().state = 0 // force block flush
abc.blk_flush()
if (typeof abc2svg.printErr == 'function')
abc2svg.printErr(e.message + "\n*** Abort ***\n" + e.stack)
else
abc2svg.print("<pre>" + e.message + "\n*** Abort ***\n" + e.stack + "</pre>")
abc2svg.abc_end()
abc2svg.quit()
}
function get_date() {
return (new Date()).toUTCString()
} // get_date()
function header_footer(str) {
var c, d, i, t,
j = 0,
r = ["", "", ""]
if (str[0] == '"')
str = str.slice(1, -1)
while (1) {
i = str.indexOf('$', j)
if (i < 0)
break
c = str[++i]
switch (c) {
case 'd':
if (!abc2svg.get_mtime)
break // cannot know the modification date of the file
d = abc2svg.get_mtime(abc.get_parse().fname)
break
case 'D':
d = get_date()
break
case 'F':
d = abc.get_parse().fname
break
case 'I':
str = str.replace('$', '')
d = str[i]
case 'T':
t = abc.info()[c]
d = t ? t.split('\n', 1)[0] : ''
break
case 'P':
d = '\x0c' // form feed
break
case 'V':
d = "abc2svg-" + abc2svg.version
break
default:
d = ''
i--
break
}
str = str.replace('$' + c, d)
j = i
}
str = str.split('\n')
for (j = 0; j < str.length; j++) {
if (j != 0)
for (i = 0; i < 3; i++)
r[i] += '<br>'
t = str[j].split('\t')
if (t.length == 1) {
r[1] += t[0]
} else {
for (i = 0; i < 3; i++) {
if (t[i])
r[i] += t[i]
}
}
}
return r
} // header_footer()
// set a paragraph style
function set_pstyle() {
var nml, nmr, nlkf, npkf, npw,
cfmt = abc.cfmt(),
psty = ''
nml = cfmt.leftmargin
if (nml != ml) {
if (ml == undefined)
ml = nml
psty += 'margin-left:' + nml.toFixed(1) + 'px;'
}
nmr = cfmt.rightmargin
if (nmr != mr) {
if (mr == undefined)
mr = nmr
psty += 'margin-right:' + nmr.toFixed(1) + 'px;'
}
nlkf = cfmt.lineskipfac
if (nlkf != lkf) {
if (lkf == undefined)
lkf = nlkf
psty += 'line-height:' + ((nlkf * 100) | 0).toString() + '%;'
}
npkf = cfmt.parskipfac
if (npkf != pkf) {
if (pkf == undefined)
pkf = npkf
psty += 'margin-bottom:' + npkf.toFixed(2) + 'em;'
}
npw = cfmt.pagewidth
if (npw != pw || nml != ml || nmr != mr) {
if (pw == undefined)
pw = npw
psty += 'width:' + (npw - nml - nmr).toFixed(1) + 'px;'
}
return psty
}
// entry point from cmdline
abc2svg.abc_init = function(args) {
// var cfmt = abc.cfmt()
// output a header or footer
function gen_hf(type, str) {
var i, page,
lcr = ["l", "c", "r"],
//fixme: handle font changes?
a = header_footer(clean_txt(str))
abc2svg.print('<table class="' + type + '" width="100%"><tr>')
for (i = 0; i < 3; i++) {
str = a[i]
if (!str)
str = ' '
//fixme
if (str.indexOf('\x0c') >= 0) {
str = str.replace('\x0c', '')
page = " page"
} else {
page = ''
}
abc2svg.print('<td class="' + lcr[i] + page +
'" width="33%">' +
str + '</td>')
}
abc2svg.print('</table>')
}
user.page_format = true
// output the html header
user.img_out = function(str) {
if (!str)
return
if (init_done) {
abc2svg.print(str)
return
}
if (/^<style>[^<]+<\/style>$/.test(str)) {
h_sty = str.replace(/^<style>\n|<\/style>$/g,'')
return
}
var cfmt = abc.cfmt(),
header = cfmt.header,
footer = cfmt.footer,
topmargin = cfmt.topmargin || "1cm",
botmargin = cfmt.botmargin || "1cm",
media_s = '@media print {\n\
body {margin:0; padding:0; border:0}\n\
.newpage {page-break-before: always}\n\
div.nobrk {page-break-inside: avoid}\n\
}',
media_f = '@media screen {\n\
.header, .footer, .h-sp, .f-sp {display: none}\n\
}\n\
@media print {\n\
body {margin:0; padding:0; border:0;\n\
counter-reset: page;\n\
counter-increment: page; }\n\
.newpage {page-break-before: always}\n\
div.nobrk {page-break-inside: avoid}\n\
.header {\n\
position: fixed;\n\
top: ' + cfmt.headerfont.size + 'px;\n\
height: ' + (cfmt.headerfont.size * 2) + 'px;\n\
' + abc.style_font(cfmt.headerfont) + ';\n\
left: ' + cfmt.leftmargin.toFixed(1) + 'px;\n\
width: ' + (cfmt.pagewidth - cfmt.leftmargin
- cfmt.rightmargin).toFixed(1) + 'px\n\
}\n\
.footer {\n\
position: fixed;\n\
bottom: 0;\n\
height: ' + (cfmt.footerfont.size * 2) + 'px;\n\
' + abc.style_font(cfmt.footerfont) + ';\n\
left: ' + cfmt.leftmargin.toFixed(1) + 'px;\n\
width: ' + (cfmt.pagewidth - cfmt.leftmargin
- cfmt.rightmargin).toFixed(1) + 'px\n\
}\n\
.h-sp, .f-sp {height: '
+ (cfmt.headerfont.size * 2) + 'px}\n\
div.page:after {\n\
counter-increment: page;\n\
content: counter(page);\n\
}\n\
.l {text-align: left}\n\
.c {text-align: center}\n\
.r {text-align: right}\n\
}';
// no margin / header / footer when SVG page formatting
if (abc.page)
topmargin = botmargin = header = footer = 0
abc2svg.print('<!DOCTYPE html>\n\
<html>\n\
<meta charset="utf-8"/>\n\
<meta name="generator" content="abc2svg-' + abc2svg.version + '"/>\n\
<!-- CreationDate: ' + get_date() + '-->\n\
<style>\n\
body {width:' + cfmt.pagewidth.toFixed(0) +'px}\n\
svg {display:block}\n\
p {' + set_pstyle() + 'margin-top:0}\n\
p span {line-height:' + ((cfmt.lineskipfac * 100) | 0).toString() + '%}\n' +
((header || footer) ? media_f : media_s))
// important for chrome and --headless (abctopdf)
if (abc.page)
abc2svg.print('@page{size:' +
(cfmt.pagewidth / 96).toFixed(2) + 'in ' +
(cfmt.pageheight / 96).toFixed(2) + 'in;margin:0}')
abc2svg.print(h_sty + '</style>\n\
<title>' + fn.replace(/.*\//,'')
+ '</title>\n\
<body>')
if (header || footer) {
if (header)
gen_hf("header", header)
if (footer)
gen_hf("footer", footer)
abc2svg.print('\
<table style="margin:0" width="100%">\n\
<thead><tr><td>\n\
<div class="h-sp"> </div>\n\
</td></tr></thead>\n\
<tbody><tr><td>')
init_done = 2 // with header/footer
} else {
init_done = 1
}
// output the first generated string
abc2svg.print(str)
}
// get the main ABC source file name
for (var i = 0; i < args.length; i++) {
var a = args[i]
if (a[0] == '-') {
i++
continue
}
fn = a
break
}
}
abc2svg.abc_end = function() {
var font_style = abc.get_font_style()
if (!init_done) // if empty document
user.img_out('')
if (user.errtxt)
abc2svg.print("<pre>" + clean_txt(user.errtxt) + "</pre>")
if (font_style) // if some %%text at the end
abc2svg.print('<style>\n' + font_style + '\n</style>')
if (init_done == 2) // if with header/footer
abc2svg.print('\
</td></tr></tbody>\n\
<tfoot><tr><td>\n\
<div class="f-sp"> </div>\n\
</td></tr></tfoot>\n\
</table>')
//abc2svg.print('</html>')
}