if not modules then modules = { } end modules ['mtx-epub'] = { version = 1.001, comment = "companion to mtxrun.lua", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- The epub specification is far from beautiful. Especially the id related -- part is messy and devices/programs react differently on them (so an id is not -- really an id but has some special property). Then there is this ncx suffix -- thing. Somehow it give the impression of a reversed engineered application -- format so it will probably take a few cycles to let it become a real -- clean standard. Thanks to Adam Reviczky, Luigi Scarso and Andy Thomas for -- helping to figure out all the puzzling details. -- This is preliminary code. At some point we will deal with images as well but -- first we need a decent strategy to export them. More information will be -- available on the wiki. -- META-INF -- container.xml -- OEBPS -- content.opf -- toc.ncx -- Images -- Styles -- Text -- mimetype local format, gsub, find = string.format, string.gsub, string.find local concat = table.concat local replace = utilities.templates.replace local helpinfo = [[ mtx-epub ConTeXt EPUB Helpers 1.00 create epub zip file Example mtxrun --script epub --make mydocument ]] local application = logs.application { name = "mtx-epub", banner = "ConTeXt EPUB Helpers 1.00", helpinfo = helpinfo, } -- script code scripts = scripts or { } scripts.epub = scripts.epub or { } local mimetype = "application/epub+zip" local t_container = [[ ]] -- urn:uuid: -- %uuid% local t_package = [[ %title% %language% %uuid% %creator% %date% %date% %manifest% ]] local t_item = [[ ]] local t_nav = [[ ]] -- local t_toc = [[ %title% %author% start ]] local t_navtoc = [[ navtoc ]] -- -- local t_coverxhtml = [[ cover page
%content%
]] local t_coverimg = [[ The cover image ]] -- We need to figure out what is permitted. Numbers only seem to give -- problems is some applications as do names with dashes. Also the -- optional toc is supposed to be there and although id's are by -- concept neutral, there are sometimes hard requirements with respect -- to their name like ncx and toc.ncx). Maybe we should stick to 3.0 -- only. local function dumbid(filename) -- return (string.gsub(os.uuid(),"%-%","")) -- to be tested return file.nameonly(filename) .. "-" .. file.suffix(filename) end local mimetypes = { xhtml = "application/xhtml+xml", xml = "application/xhtml+xml", html = "application/html", css = "text/css", svg = "image/svg+xml", png = "image/png", jpg = "image/jpeg", ncx = "application/x-dtbncx+xml", gif = "image/gif", -- default = "text/plain", } local idmakers = { ncx = function(filename) return "ncx" end, -- css = function(filename) return "stylesheet" end, default = function(filename) return dumbid(filename) end, } -- specification = { -- name = "document", -- identifier = "123", -- root = "a.xhtml", -- files = { -- "a.xhtml", -- "b.css", -- "c.png", -- } -- } local function relocateimages(imagedata,oldname,newname,subpath) local data = io.loaddata(oldname) local images = { } local done = gsub(data,[[(id=")(.-)(".-background%-image *: * url%()(.-)(%))]], function(s1,id,s2,name,s3) local newname = imagedata[id].newname if newname then if subpath then name = file.join(subpath,file.basename(new name)) else name = file.basename(newname) end -- name = url.addscheme(name) end images[#images+1] = name if newname then return s1 .. id .. s2 .. name .. s3 end end) if newname then io.savedata(newname,done) end return images end local zippers = { { name = "zip", binary = "zip", uncompressed = "zip %s -X -0 %s", compressed = "zip %s -X -9 -r %s", }, { name = "7z (7zip)", binary = "7z", uncompressed = "7z a -tzip -mx0 %s %s", compressed = "7z a -tzip %s %s", }, } function scripts.epub.make() local filename = environment.files[1] if not filename or filename == "" or type(filename) ~= "string" then application.report("provide filename") return end filename = file.basename(filename) local specfile = file.replacesuffix(filename,"specification") if not lfs.isfile(specfile) then application.report("unknown specificaton file %a",specfile) return end local specification = dofile(specfile) if not specification or not next(specification) then application.report("invalid specificaton file %a",specfile) return end -- images: { ... url = location ... } local name = specification.name or file.removesuffix(filename) local identifier = specification.identifier or "" local files = specification.files or { file.addsuffix(filename,"xhtml") } local images = specification.images or { } local root = specification.root or files[1] local language = specification.language or "en" local creator = "context mkiv" local author = specification.author or "anonymous" local title = specification.title or name local subtitle = specification.subtitle or "" local firstpage = specification.firstpage or "" local lastpage = specification.lastpage or "" local imagefile = specification.imagefile or "" if subtitle ~= "" then title = format("%s, %s",title,subtitle) end -- identifier = gsub(identifier,"[^a-zA-z0-9]","") if firstpage == "" then -- firstpage = "firstpage.jpg" -- dummy else images[firstpage] = firstpage end if lastpage == "" then -- lastpage = "lastpage.jpg" -- dummy else images[lastpage] = lastpage end local uuid = format("urn:uuid:%s",os.uuid(true)) -- os.uuid() identifier = "bookid" -- for now local epubname = name local epubpath = file.replacesuffix(name,"tree") local epubfile = file.replacesuffix(name,"epub") local epubroot = file.replacesuffix(name,"opf") local epubtoc = "toc.ncx" local epubcover = "cover.xhtml" application.report("creating paths in tree %a",epubpath) lfs.mkdir(epubpath) lfs.mkdir(file.join(epubpath,"META-INF")) lfs.mkdir(file.join(epubpath,"OEBPS")) local used = { } local function registerone(filename) local suffix = file.suffix(filename) local mime = mimetypes[suffix] if mime then local idmaker = idmakers[suffix] or idmakers.default used[#used+1] = replace(t_item, { id = idmaker(filename), filename = filename, mime = mime, } ) return true end end local function copyone(filename,alternative) if registerone(filename) then local target = file.join(epubpath,"OEBPS",file.basename(filename)) local source = alternative or filename file.copy(source,target) application.report("copying %a to %a",source,target) end end if lfs.isfile(epubcover) then copyone(epubcover) epubcover = false else registerone(epubcover) end copyone("toc.ncx") local function copythem(files) for i=1,#files do local filename = files[i] if type(filename) == "string" then local suffix = file.suffix(filename) if suffix == "xhtml" then local alternative = file.replacesuffix(filename,"html") if lfs.isfile(alternative) then copyone(filename,alternative) else copyone(filename) end elseif suffix == "css" then if filename == "export-example.css" then if lfs.isfile(filename) then os.remove(filename) local original = resolvers.findfile(filename) application.report("updating local copy of %a from %a",filename,original) file.copy(original,filename) else filename = resolvers.findfile(filename) end elseif not lfs.isfile(filename) then filename = resolvers.findfile(filename) else -- use specific local one end copyone(filename) else copyone(filename) end end end end copythem(files) -- ["image-1"]={ -- ["height"]="7.056cm", -- ["name"]="file:///t:/sources/cow.svg", -- ["page"]="1", -- ["width"]="9.701cm", -- } local theimages = { } local pdftosvg = string.formatters[ [[mudraw -o "%s" "%s" %s]] ] for id, data in table.sortedpairs(images) do local name = url.filename(data.name) local used = url.filename(data.used) local base = file.basename(used) local page = data.page or "" if file.suffix(used) == "pdf" then -- todo : check timestamp and prefix, rename to image-* local command = pdftosvg(name,used,page) application.report("running command %a\n\n",command) os.execute(command) else name = used end data.newname = name theimages[#theimages+1] = name end used[#used+1] = replace(t_nav, { id = "nav", filename = "nav.xhtml", properties = "nav", mime = "application/xhtml+xml", }) io.savedata(file.join(epubpath,"OEBPS","nav.xhtml"),replace(t_navtoc, { -- version 3.0 root = root, } ) ) copythem(theimages) local idmaker = idmakers[file.suffix(root)] or idmakers.default io.savedata(file.join(epubpath,"mimetype"),mimetype) io.savedata(file.join(epubpath,"META-INF","container.xml"),replace(t_container, { -- version 2.0 rootfile = epubroot } ) ) io.savedata(file.join(epubpath,"OEBPS",epubroot),replace(t_package, { identifier = identifier, title = title, language = language, uuid = uuid, creator = creator, date = os.date("!%Y-%m-%dT%H:%M:%SZ"), firstpage = idmaker(firstpage), manifest = concat(used,"\n"), rootfile = idmaker(root) } ) ) -- t_toc is replaced by t_navtoc in >= 3 io.savedata(file.join(epubpath,"OEBPS",epubtoc), replace(t_toc, { identifier = uuid, -- identifier, title = title, author = author, root = root, } ) ) if epubcover then io.savedata(file.join(epubpath,"OEBPS",epubcover), replace(t_coverxhtml, { content = firstpage ~= "" and replace(t_coverimg, { image = firstpage }) or "no cover page defined", } ) ) end if imagefile ~= "" then local target = file.join(epubpath,"OEBPS",imagefile) application.report("relocating images") relocateimages(images,imagefile,target) -- ,file.join(epubpath,"OEBPS")) end application.report("creating archive\n\n") lfs.chdir(epubpath) os.remove(epubfile) local usedzipper = false local function zipped(zipper) local ok = os.execute(format(zipper.uncompressed,epubfile,"mimetype")) if ok == 0 then os.execute(format(zipper.compressed,epubfile,"META-INF")) os.execute(format(zipper.compressed,epubfile,"OEBPS")) usedzipper = zipper.name return true end end -- nice way for i=1,#zippers do if os.which(zippers[i].binary) and zipped(zippers[i]) then break end end -- trial and error if not usedzipper then for i=1,#zippers do if zipped(zippers[i]) then break end end end lfs.chdir("..") if usedzipper then local treefile = file.join(epubpath,epubfile) os.remove(epubfile) file.copy(treefile,epubfile) if lfs.isfile(epubfile) then os.remove(treefile) end application.report("epub archive made using %s: %s",usedzipper,epubfile) else local list = { } for i=1,#zippers do list[#list+1] = zippers[i].name end application.report("no epub archive made, install one of: % | t",list) end end -- if environment.argument("make") then scripts.epub.make() elseif environment.argument("exporthelp") then application.export(environment.argument("exporthelp"),environment.files[1]) else application.help() end -- java -jar d:\epubcheck\epubcheck-3.0.1.jar -v 3.0 -mode xhtml mkiv-publications.tree\mkiv-publications.epub