summaryrefslogtreecommitdiff
path: root/support/make4ht/filters/make4ht-mjcli.lua
blob: f610246439256efebbc02cac0172944578945059 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
local mkutils = require "mkutils"
local log = logging.new("mjcli")
-- other possible value is page2svg
local mathnodepath = "mjcli"
-- options for MathJax command
local options = ""
-- math fonts position
-- don't alter fonts if not set
local fontdir = nil
-- if we copy fonts 
local fontdest = nil
local fontformat = "woff"
local cssfilename =  "mathjax-chtml.css"

local function compile(filename, options)
  -- local tmpfile = os.tmpname()
  log:info("Compile using MathJax")
  local command =  mathnodepath .. " ".. options .. " " .. filename
  log:info(command)
  local commandhandle, msg = io.popen(command,"r") 
  if not commandhandle then return nil, msg end
  local content = commandhandle:read("*all")
  commandhandle:close()
  return content
end

local saved_styles = {}
local used_styles = {}

local function make_css(saved_styles)
  -- this buffer contains lines of the new CSS file
  local buffer = {}
  -- process table with saved CSS rules and make CSS file again
  for _, rule in ipairs(saved_styles) do
    buffer[#buffer+1] = rule.selector .. " {"
    -- save CSS properties
    for _, line in ipairs(rule.content) do
      buffer[#buffer+1] = line
    end
    buffer[#buffer+1] = "}"
    buffer[#buffer+1] = "" -- add blank line
  end
  return table.concat(buffer, "\n")
end

-- MathJax generated CSS contains reusable declarations but also
-- declarations of fixes for elements in the current file
-- the idea is to reuse the common declarations and to save each
-- fix. 
local function parse_css(css, file_class)
  local status = "init"
  local current = {}
  local current_selector
  for line in css:gmatch("([^\n]+)") do
    if status == "init" then
      local selector, rest = line:match("%s*(.-)%s*{(.-)")
      if selector then 
        current_selector = selector
        -- if the current selector contains class, we must prepend the current file class
        -- as the joined CSS file could contain multiple rules with the same class otherwise
        if current_selector:match("%.") then current_selector = "." .. file_class .. " " .. current_selector end
        status = "record"
      end
    elseif status == "record" then
      -- find end of the CSS rule
      if line:match("}%s*$") then
        status = "init"
        if not used_styles[current_selector] then
          table.insert(saved_styles, {selector = current_selector, content = current})
        end
        current = {}
        used_styles[current_selector] = true
      else
        table.insert(current, line)
      end
    end

  end
  -- save combined CSS for all files
  return make_css(saved_styles)
end

local function make_file_class(name)
  -- clean the filename to make it safe as a class name
  return name:gsub("[%s%p%s]", "_")
end

-- set class attribute in the body element of the current file
-- this is necessary for the updated CSS file
local function set_body_class(content, file_class)
  content = content:gsub("<body(.-)>", function(body)
    if body:match("class") then
      -- add new class if there already is one
      body = body:gsub("(class.-[\"'])", "%1" .. file_class .. " ")
    else
      body = body .. ' class="' .. file_class .. '"'
    end
    return "<body" ..body ..">"
  end)
  return content
end


-- save the css code from the html page generated by MathJax
local function extract_css(contents, currentfilename)
  local css
  local filename = cssfilename
  local file_class = make_file_class(currentfilename)
  -- detect all <style> tags
  contents = contents:gsub('<style [^>]+>(.-)</style>', function(style)
    -- replace only the style for mathjax
    if style:match "mjx%-container" then
      css = parse_css(style, file_class)
      return '<link rel="stylesheet" type="text/css" href="'..filename ..'" />'
    end
  end)
  contents = set_body_class(contents, file_class)
  return filename, contents, css
end

-- Update the paths to fonts to use the local versions
local function use_fonts(css)
  local family_pattern = "font%-family:%s*(.-);.-%/([^%/]+)%.".. fontformat
  local family_build = "@font-face {font-family: %s; src: url('%s/%s.%s') format('%s')}"
  local fontdir = fontdir:gsub("/$","")
  css = css:gsub("(@font%-face%s*{.-})", function(face)
    if not face:match("url%(") then return face end
    -- print(face)
    local family, filename = face:match(family_pattern)
    log:info("use font: ",family, filename)
    local newfile = string.format("%s/%s.%s", fontdir, filename, fontformat)
    Make:add_file(newfile)
    return family_build:format(family, fontdir, filename, fontformat, fontformat)
    -- return face
  end)
  return css
end


local function save_css(filename, css)
  local f = io.open(filename, "w")
  f:write(css)
  f:close()
end

return function(text, arguments)
  -- if arguments.prg then mathnodepath = arguments.prg end
  local extoptions = mkutils.get_filter_settings "mjcli" or {}
  local arguments = arguments or {}
  mathnodepath = arguments.prg or extoptions.prg or  mathnodepath
  local options      = arguments.options or extoptions.options or options
  fontdir      = arguments.fontdir or extoptions.fontdir or fontdir
  -- the following ne is unused ATM
  fontdest     = arguments.fontdest or extoptions.fontdest or fontdest
  fontformat   = arguments.fontformat or extoptions.fontformat or fontformat
  cssfilename  = arguments.cssfilename or extoptions.cssfilename or arguments.input .. "-mathjax.css"
  local is_latex = arguments.latex or extoptions.latex or false
  local filename = arguments.filename

  -- modify options to use LaTeX syntax by MathJax
  if is_latex then
    options = options .. " -l"
  end

  -- compile current html file with mathjax
  local newtext, msg = compile(filename, options)
  if not newtext then 
    log:error(msg)
    return text
  end
  -- save CSS to a standalone file
  local cssfile, newtext,  css = extract_css(newtext, filename)
  -- use local font files if fontdir is present
  if fontdir then
    css = use_fonts(css)
  end
  if css then
    save_css(cssfile, css)
    Make:add_file(cssfile)
    -- print(css)
    log:info("CSS file: " .. cssfile)
  end
  return newtext
end