From c401f68497eea2603e3c5a8742b032335fc6dfce Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Sat, 20 Mar 2021 22:20:13 +0000 Subject: make4ht (20mar21) git-svn-id: svn://tug.org/texlive/trunk@58563 c570f23f-e606-0410-a88d-b1316a301751 --- .../make4ht/domfilters/make4ht-joincharacters.lua | 16 +- .../make4ht/domfilters/make4ht-mathmlfixes.lua | 67 ++++++++ .../extensions/make4ht-ext-build_changed.lua | 68 ++++++++ .../make4ht/extensions/make4ht-ext-mathjaxnode.lua | 6 + .../make4ht/extensions/make4ht-ext-mjcli.lua | 36 ++++ .../make4ht/extensions/make4ht-ext-staticsite.lua | 3 +- .../scripts/make4ht/filters/make4ht-mjcli.lua | 183 +++++++++++++++++++++ .../scripts/make4ht/formats/make4ht-odt.lua | 9 +- Master/texmf-dist/scripts/make4ht/make4ht | 3 +- .../texmf-dist/scripts/make4ht/make4ht-logging.lua | 13 +- .../scripts/make4ht/make4ht-odtfilter.lua | 33 ++++ Master/texmf-dist/scripts/make4ht/mkutils.lua | 9 +- 12 files changed, 434 insertions(+), 12 deletions(-) create mode 100644 Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-build_changed.lua create mode 100644 Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mjcli.lua create mode 100644 Master/texmf-dist/scripts/make4ht/filters/make4ht-mjcli.lua create mode 100755 Master/texmf-dist/scripts/make4ht/make4ht-odtfilter.lua (limited to 'Master/texmf-dist/scripts') diff --git a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua index 4493aef023a..34737f921aa 100644 --- a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua +++ b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua @@ -56,6 +56,18 @@ local function join_characters(obj,par) local is_span = function(next_el) return charclasses[get_name(next_el)] end + local has_children = function(curr) + -- don't process spans that have child elements + local children = curr:get_children() or {} + -- if there is more than one child, we can be sure that it has child elements + if #children > 1 then + return true + elseif #children == 1 then + -- test if the child is an element + return children[1]:is_element() + end + return false + end local join_elements = function(el, next_el) -- it the following element match, copy it's children to the current element for _, child in ipairs(next_el:get_children()) do @@ -79,8 +91,8 @@ local function join_characters(obj,par) obj:traverse_elements(function(el) -- loop over all elements and test if the current element is in a list of - -- processed elements (charclasses) - if is_span(el) then + -- processed elements (charclasses) and if it doesn't contain children + if is_span(el) and not has_children(el) then local next_el = get_next(el) -- loop over the following elements and test whether they are of the same type -- as the current one diff --git a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-mathmlfixes.lua b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-mathmlfixes.lua index 3e044b94453..55c0da379b0 100644 --- a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-mathmlfixes.lua +++ b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-mathmlfixes.lua @@ -37,8 +37,75 @@ local function fix_nested_mstyle(el) end end +local function get_fence(el, attr, form) + -- convert fence attribute to element + -- attr: open | close + -- form: prefix | postfix + local char = el:get_attribute(attr) + local mo + if char then + mo = el:create_element("mo", {fence="true", form = form}) + mo:add_child_node(mo:create_text_node(char)) + end + return mo +end + + +local function fix_mfenced(el) + -- TeX4ht uses in some cases element which is deprecated in MathML. + -- Firefox doesn't support it already. + if el:get_element_name() == "mfenced" then + -- we must replace it by startend + local open = get_fence(el, "open", "prefix") + local close = get_fence(el, "close", "postfix") + -- there can be also separator attribute, but it is not used in TeX4ht + -- change to and remove all attributes + el._name = "mrow" + el._attr = {} + -- open must be first child, close needs to be last + if open then el:add_child_node(open, 1) end + if close then el:add_child_node(close) end + end +end + +local function is_fence(el) + return el:get_element_name() == "mo" and el:get_attribute("fence") == "true" +end + +local function fix_mo_to_mfenced(el) + -- LibreOffice NEEDS element. so we need to convert + -- to . ouch. + if is_fence(el) then + local parent = el:get_parent() + local open = el:get_text():gsub("%s*", "") -- convert mo content to text, so it can be used in + -- close needs to be the last element in the sibling list of the current element + local siblings = el:get_siblings() + el:remove_node() -- we don't need this element anymore + local close + for i = #siblings, 1, -1 do + last = siblings[i] + if last:is_element() then + if is_fence(last) then -- set close attribute only if the last element is fence + close = last:get_text():gsub("%s*", "") + last:remove_node() -- remove + end + break -- break looping over elements once we find last element + end + end + -- convert parent to + parent._name = "mfenced" + parent._attr = {open = open, close = close} + end +end + return function(dom) dom:traverse_elements(function(el) + if settings.output_format ~= "odt" then + -- LibreOffice needs , but Firefox doesn't + fix_mfenced(el) + else + fix_mo_to_mfenced(el) + end fix_token_elements(el) fix_nested_mstyle(el) end) diff --git a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-build_changed.lua b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-build_changed.lua new file mode 100644 index 00000000000..e0fc5ec6eb2 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-build_changed.lua @@ -0,0 +1,68 @@ +-- this make4ht build file tries to recompile modified blog article sources +-- + +-- disable any compilation +Make.build_seq = {} +Make:add("tex4ht", "") +Make:add("t4ht", "") + +local log = logging.new "compile newest" + + + +-- construct the name of the generated HTML file from the .published file +local function get_generated_html_name(published_file, directory, file_pattern) + local f = io.open(directory .. "/" .. published_file, "r") + local content = f:read("*all") + f:close() + local timestamp = tonumber(content) + local basename = mkutils.remove_extension(published_file) + local tex_file = basename .. ".tex" + -- expand fillename in the file_pattern + local expanded = file_pattern % {input = basename} + -- expand date in the file_pattern + expanded = os.date(expanded, timestamp) + log:status("found source files :", directory, basename, expanded) + return {directory = directory, tex_file = tex_file, generated = expanded .. ".html"} +end + +-- process subdirectories of the basedir and look for the filename.published files +local function find_published(basedir, file_pattern) + local published = {} + for f in lfs.dir(basedir) do + local fullname = basedir .. "/" .. f + local attributes = lfs.attributes(fullname) + -- process directories, but ignore . and .. + if attributes.mode == "directory" and f ~= "." and f~= ".." then + for name in lfs.dir(fullname) do + if name:match("published$") then + published[#published + 1] = get_generated_html_name(name, fullname, file_pattern) + end + + end + end + end + return published +end + +-- find tex files that were modified later than the generated HTML files +local function find_changed(published, site_root) + local keep = {} + for _, entry in ipairs(published) do + local source_attributes = lfs.attributes(entry.directory .. "/" .. entry.tex_file) + local dest_attributes = lfs.attributes(site_root .. "/" .. entry.generated) + -- + print(entry.tex_file, entry.generated, source_attributes.change < dest_attributes.change) + end +end + + +Make:add("rebuild", function(par) + local config = get_filter_settings "staticsite" + -- how the generated HTML files are named + local file_pattern = config.file_pattern or "%Y-%m-%d-${input}" + local published = find_published(par.tex_dir, file_pattern) + local changed = find_changed(published, config.site_root) +end) + +Make:rebuild{tex_dir = "posts"} diff --git a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mathjaxnode.lua b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mathjaxnode.lua index c2c4928aeb9..b9a44491bad 100644 --- a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mathjaxnode.lua +++ b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mathjaxnode.lua @@ -7,8 +7,14 @@ function M.test(format) return true end +function M.prepare_parameters(params) + params.tex4ht_sty_par = params.tex4ht_sty_par .. ",mathml" + return params + +end function M.modify_build(make) local mathjax = filter { "mathjaxnode"} + -- this extension needs mathml enabled make:match("html?$",mathjax) return make end diff --git a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mjcli.lua b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mjcli.lua new file mode 100644 index 00000000000..e59b419cdf4 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-mjcli.lua @@ -0,0 +1,36 @@ +local M = {} + + +local filter = require "make4ht-filter" +function M.test(format) + -- this extension works only for formats based on HTML, as it produces + -- custom HTML tags that would be ilegal in XML + if not format:match("html5?$") then return false end + return true +end + +-- +local detected_latex = false +function M.prepare_parameters(params) + -- mjcli supports both MathML and LaTeX math input + -- LaTeX math is keep if user uses "mathjax" option for make4ht + -- "mathjax" option used in \Preamble in the .cfg file doesn't work + if params.tex4ht_sty_par:match("mathjax") then + detected_latex = true + else + params.tex4ht_sty_par = params.tex4ht_sty_par .. ",mathml" + end + return params + +end +function M.modify_build(make) + local mathjax = filter { "mjcli"} + local params = {} + if detected_latex then + params.latex = true + end + make:match("html?$",mathjax, params) + return make +end + +return M diff --git a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-staticsite.lua b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-staticsite.lua index a4204bc1733..5704959970a 100644 --- a/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-staticsite.lua +++ b/Master/texmf-dist/scripts/make4ht/extensions/make4ht-ext-staticsite.lua @@ -125,8 +125,7 @@ function M.modify_build(make) -- match.params.outdir = outdir -- print(match.pattern, match.params.outdir) -- end - -- make the YAML header only for the main HTML file - make:match(mainfile .. ".html", process) + make:match("html?$", process) make:match(".*", copy_files, {slug=slug}) end) diff --git a/Master/texmf-dist/scripts/make4ht/filters/make4ht-mjcli.lua b/Master/texmf-dist/scripts/make4ht/filters/make4ht-mjcli.lua new file mode 100644 index 00000000000..c7b3c46507a --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/filters/make4ht-mjcli.lua @@ -0,0 +1,183 @@ +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 + if line:match("%}") 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("", 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 "" + 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 ', function(style) + -- replace only the style for mathjax + if style:match "mjx%-container" then + css = parse_css(style, file_class) + return '' + 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 diff --git a/Master/texmf-dist/scripts/make4ht/formats/make4ht-odt.lua b/Master/texmf-dist/scripts/make4ht/formats/make4ht-odt.lua index 30fdc0744da..4d5d768ee75 100644 --- a/Master/texmf-dist/scripts/make4ht/formats/make4ht-odt.lua +++ b/Master/texmf-dist/scripts/make4ht/formats/make4ht-odt.lua @@ -128,6 +128,13 @@ local function exec_group(groups, name, fn) end end +-- remove ", "") +end + function M.modify_build(make) local executed = false -- execute xtpipes from the build file, instead of t4ht. this fixes issues with wrong paths @@ -142,7 +149,7 @@ function M.modify_build(make) -- execute it before xtpipes, because we don't want xtpipes to mess with t4htlink elements move_matches(make) -- convert XML entities for Unicode characters produced by Xtpipes to characters - local fixentities = filter {"entities-to-unicode"} + local fixentities = filter {"entities-to-unicode", remove_xtpipes} make:match("4oo", fixentities) make:match("4om", fixentities) diff --git a/Master/texmf-dist/scripts/make4ht/make4ht b/Master/texmf-dist/scripts/make4ht/make4ht index 079c464ecd2..3ae56f049a5 100755 --- a/Master/texmf-dist/scripts/make4ht/make4ht +++ b/Master/texmf-dist/scripts/make4ht/make4ht @@ -29,7 +29,7 @@ make4ht [options] filename ["tex4ht.sty op." "tex4ht op." "t4ht op" "latex op"] -- set version number. the template should be replaced by the -- actual version number by the build script -local version = "v0.3f" +local version = "v0.3g" mkparams.version_number = version local args = mkparams.get_args() @@ -123,4 +123,5 @@ end) make:run() log:status("Conversion finished") +logging.exit_status() diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-logging.lua b/Master/texmf-dist/scripts/make4ht/make4ht-logging.lua index c1d2b24a577..63b11433ea4 100755 --- a/Master/texmf-dist/scripts/make4ht/make4ht-logging.lua +++ b/Master/texmf-dist/scripts/make4ht/make4ht-logging.lua @@ -6,6 +6,7 @@ local levels = {} -- level of bugs that should be shown local show_level = 1 local max_width = 0 +local max_status = 0 logging.use_colors = true @@ -14,8 +15,8 @@ logging.modes = { {name = "info", color = 32}, {name = "status", color = 37}, {name = "warning", color = 33}, - {name = "error", color = 31}, - {name = "fatal", color = 35} + {name = "error", color = 31, status = 1}, + {name = "fatal", color = 35, status = 2} } -- prepare table with mapping between mode names and corresponding levels @@ -62,7 +63,10 @@ function logging.new(module) for _, mode in ipairs(logging.modes) do local name = mode.name local color = mode.color + local status = mode.status or 0 obj[name] = function(self, ...) + -- set make4ht exit status + max_status = math.max(status, max_status) -- max width is saved in logging.prepare_levels if mode.level >= show_level then -- support variable number of parameters @@ -82,6 +86,11 @@ function logging.new(module) end +-- exit make4ht with maximal error status +function logging.exit_status() + os.exit(max_status) +end + -- prepare default levels logging.prepare_levels() diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-odtfilter.lua b/Master/texmf-dist/scripts/make4ht/make4ht-odtfilter.lua new file mode 100755 index 00000000000..97f0b8df670 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/make4ht-odtfilter.lua @@ -0,0 +1,33 @@ +local mkutils = require "mkutils" +local zip = require "zip" + + +-- use function to change contents of the ODT file +local function update_odt(odtfilename, file_path, fn) + -- get name of the odt file + local odtname = mkutils.remove_extension(odtfilename) .. ".odt" + -- open and read contents of the requested file inside ODT file + local odtfile = zip.open(odtname) + local local_file = odtfile:open(file_path) + local content = local_file:read("*all") + local_file:close() + odtfile:close() + -- update the content using user function + content = fn(content) + -- write the updated file + local local_file_file = io.open(file_path,"w") + local_file_file:write(content) + local_file_file:close() + os.execute("zip " .. odtname .. " " .. file_path) + os.remove(file_path) +end + +Make:match("tmp$", function(name, par) + update_odt(name, "content.xml", function(content) + return content:gsub("%&%#x([A-Fa-f0-9]+);", function(entity) + -- convert hexadecimal entity to Unicode + print(entity,utfchar(tonumber(entity, 16))) + return utfchar(tonumber(entity, 16)) + end) + end) +end) diff --git a/Master/texmf-dist/scripts/make4ht/mkutils.lua b/Master/texmf-dist/scripts/make4ht/mkutils.lua index 38a49663157..c182bfd9dd8 100755 --- a/Master/texmf-dist/scripts/make4ht/mkutils.lua +++ b/Master/texmf-dist/scripts/make4ht/mkutils.lua @@ -343,7 +343,7 @@ env.Make:add("test","test the variables: ${tex4ht_sty_par} ${htlatex} ${input} local htlatex = require "make4ht-htlatex" env.Make:add("htlatex", htlatex.htlatex ,{correct_exit=0}) -env.Make:add("htttex", htlatex.httex, { +env.Make:add("httex", htlatex.httex, { htlatex = "etex", correct_exit=0 }) @@ -498,7 +498,7 @@ function load_extension(name,format) local extension_library = "make4ht.extensions.make4ht-ext-" .. name local is_extension_file = find_lua_file(extension_library) -- don't try to load the extension if it doesn't exist - if not is_extension_file then return nil end + if not is_extension_file then return nil, "cannot fint extension " .. name end local extension = require("make4ht.extensions.make4ht-ext-".. name) -- extensions can test if the current output format is supported local test = extension.test @@ -507,7 +507,7 @@ function load_extension(name,format) return extension end -- if the test fail return nil - return nil + return nil, "extension " .. name .. " is not supported in the " .. format .. " format" end -- if the extension doesn't provide the test function, we will assume that -- it supports every output format @@ -538,12 +538,13 @@ function load_extensions(extensions, format) -- the extension can be inserted into the extension_sequence, but disabled -- later. if module_names[name] == true then - local extension = load_extension(name,format) + local extension, msg= load_extension(name,format) if extension then log:info("Load extension", name) table.insert(extension_table, extension) else log:warning("Cannot load extension: ".. name) + log:warning(msg) end end end -- cgit v1.2.3