bump 0.1
[gemini.koplugin.git] / geminidocument.lua
blob23edda9f76a19bec85f5ec5ecd2d2941e4d3fac1
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)
12 local pre = false
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*(.*)$")
19 if alt then
20 if not pre then
21 pre = true
22 -- use <small> to improve chance that wide ascii art will fit
23 -- on the screen
24 return "<small><pre>", false
25 else
26 pre = false
27 return "</pre></small>", false
28 end
29 end
30 if pre then
31 return util.htmlEscape(line), false
32 end
34 if line:match("^%s*$") then
35 return "<br/>", false
36 end
38 local link, desc
39 link = line:match('^=>%s*([^%s]+)%s*$')
40 if not link then
41 link,desc = line:match('^=>%s*([^%s]+)%s+(.+)$')
42 end
43 if link then
44 local purl = url.parse(link)
45 desc = desc or link
46 desc = util.htmlEscape(desc)
47 if purl.scheme and purl.scheme ~= "gemini" then
48 desc = desc .. T(" <em>[%1]</em>", purl.scheme)
49 end
50 return '<li><a href="' .. link .. '">' .. desc .. '</a></li>', true
51 end
53 local headers, text
54 headers,text = line:match('^(#+)%s*(.*)$')
55 if headers then
56 local level = headers:len()
57 if level <= 3 then
58 return "<h" .. level .. ">" .. util.htmlEscape(text) .. "</h" .. level .. ">", false
59 end
60 end
62 text = line:match("^%*%s+(.*)$")
63 if text then
64 return "<li>" .. util.htmlEscape(text) .. "</li>", true
65 end
67 text = line:match('^>(.*)$')
68 if text then
69 return "<blockquote>" .. util.htmlEscape(text) .. "</blockquote>", true
70 end
72 return "<p>" .. util.htmlEscape(line) .. "</p>"
73 end
75 o:write("<html>\n")
76 -- Override css to not require page breaks on headers,
77 -- and improve contrast for links.
78 o:write([[
79 <head><style>
80 h1, h2, h3 {
81 page-break-before: auto;
83 a {
84 text-decoration: underline; color: #505050;
86 </style></head>
87 ]])
88 o:write("<body>\n")
89 local in_list = false
90 local list
91 local written_line = false
92 for line in i:lines() do
93 line, list = parseLine(line)
94 if list and not in_list then
95 line = "<ul>" .. line
96 elseif in_list and not list then
97 line = "</ul>\n" .. line
98 end
99 in_list = list
100 o:write(line .. "\n")
101 written_line = true
103 i:close()
104 if not written_line then
105 -- work around CRE not rendering empty html documents properly
106 o:write(_("[Empty gemini document]\n"))
108 o:write("</body></html>")
111 function GeminiDocument:init()
112 self.tmp_html_file = os.tmpname() .. ".html"
113 if not self.tmp_html_file then
114 error(_("Failed to create temporary file for gmi -> html conversion."))
116 local i = io.open(self.file, "r")
117 local o = io.open(self.tmp_html_file, "w")
118 convertGmi(i, o)
119 o:close()
120 local gemfile = self.file
121 self.file = self.tmp_html_file
122 CreDocument.init(self)
123 self.file = gemfile
125 -- XXX: hack; uses that only these methods read self.file
126 for _i,method in ipairs({"loadDocument","getNativePageDimensions","_readMetadata","getFullPageHash","renderPage"}) do
127 self[method] = function(slf, ...)
128 slf.file = slf.tmp_html_file
129 local ok, re = pcall(CreDocument[method], slf, ...)
130 slf.file = gemfile
131 if ok then
132 return re
133 else
134 logger.err("wrapped credocument call failed", method, re)
135 return false
141 function GeminiDocument:close()
142 CreDocument.close(self)
143 FileManager:deleteFile(self.tmp_html_file, true)
146 return GeminiDocument