1 local CreDocument
= require("document/credocument")
2 local FileManager
= require("apps/filemanager/filemanager")
3 local url
= require("socket.url")
4 local util
= require("util")
5 local logger
= require("logger")
6 local _
= require("gettext")
7 local T
= require("ffi/util").template
9 local GeminiDocument
= CreDocument
:extend
{}
11 local function convertGmi(i
, o
)
13 local function parseLine(line
)
14 -- strip ANSI CSI sequences (used in some gemtext documents for
15 -- colour, which we do not try to support)
16 line
= line
:gsub("%\x1b%[[ -?]*[@-~]","")
18 local alt
= line
:match("^```%s*(.*)$")
22 -- use <small> to improve chance that wide ascii art will fit
24 return "<small><pre>", false
27 return "</pre></small>", false
31 return util
.htmlEscape(line
), false
34 if line
:match("^%s*$") then
39 link
= line
:match('^=>%s*([^%s]+)%s*$')
41 link
,desc
= line
:match('^=>%s*([^%s]+)%s+(.+)$')
44 local purl
= url
.parse(link
)
46 desc
= util
.htmlEscape(desc
)
47 if purl
.scheme
and purl
.scheme
~= "gemini" then
48 desc
= desc
.. T(" <em>[%1]</em>", purl
.scheme
)
50 return '<li><a href="' .. link
.. '">' .. desc
.. '</a></li>', true
54 headers
,text
= line
:match('^(#+)%s*(.*)$')
56 local level
= headers
:len()
58 -- Avoid <h1> for level 1 headings, because it causes a
59 -- newpage in the default stylesheet epub.css
60 return "<h" .. level
+ 1 .. ">" .. util
.htmlEscape(text
) .. "</h" .. level
.. ">", false
64 text
= line
:match("^%*%s+(.*)$")
66 return "<li>" .. util
.htmlEscape(text
) .. "</li>", true
69 text
= line
:match('^>(.*)$')
71 return "<blockquote>" .. util
.htmlEscape(text
) .. "</blockquote>", true
74 return "<p>" .. util
.htmlEscape(line
) .. "</p>"
77 o
:write("<html><body>\n")
80 local written_line
= false
81 for line
in i
:lines() do
82 line
, list
= parseLine(line
)
83 if list
and not in_list
then
85 elseif in_list
and not list
then
86 line
= "</ul>\n" .. line
93 if not written_line
then
94 -- work around CRE not rendering empty html documents properly
95 o
:write(_("[Empty gemini document]\n"))
97 o
:write("</body></html>")
100 function GeminiDocument
:init()
101 self
.tmp_html_file
= os
.tmpname() .. ".html"
102 if not self
.tmp_html_file
then
103 error(_("Failed to create temporary file for gmi -> html conversion."))
105 local i
= io
.open(self
.file
, "r")
106 local o
= io
.open(self
.tmp_html_file
, "w")
109 local gemfile
= self
.file
110 self
.file
= self
.tmp_html_file
111 CreDocument
.init(self
)
114 -- XXX: hack; uses that only these methods read self.file
115 for _i
,method
in ipairs({"loadDocument","getNativePageDimensions","_readMetadata","getFullPageHash","renderPage"}) do
116 self
[method
] = function(slf
, ...)
117 slf
.file
= slf
.tmp_html_file
118 local ok
, re
= pcall(CreDocument
[method
], slf
, ...)
123 logger
.err("wrapped credocument call failed", method
, re
)
130 function GeminiDocument
:close()
131 CreDocument
.close(self
)
132 FileManager
:deleteFile(self
.tmp_html_file
, true)
135 return GeminiDocument