diff options
author | Norbert Preining <norbert@preining.info> | 2020-01-24 03:00:54 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2020-01-24 03:00:54 +0000 |
commit | e1192611f0655a1ccaff0dff2f53c7c65fa5db07 (patch) | |
tree | 232f5be17a2221a57cc6515bfe8692772b3a3e89 /support | |
parent | f7e5d81fe9880913485f7f8c541eeca7adf4c195 (diff) |
CTAN sync 202001240300
Diffstat (limited to 'support')
-rw-r--r-- | support/make4ht/README | 18 | ||||
-rw-r--r-- | support/make4ht/changelog.tex | 64 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-booktabs.lua | 82 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-joincharacters.lua | 113 | ||||
-rw-r--r-- | support/make4ht/domfilters/make4ht-tablerows.lua | 14 | ||||
-rw-r--r-- | support/make4ht/extensions/make4ht-ext-common_domfilters.lua | 2 | ||||
-rw-r--r-- | support/make4ht/extensions/make4ht-ext-detect_engine.lua | 91 | ||||
-rwxr-xr-x | support/make4ht/make4ht | 2 | ||||
-rw-r--r-- | support/make4ht/make4ht-doc.pdf | bin | 128196 -> 130605 bytes | |||
-rw-r--r-- | support/make4ht/make4ht-htlatex.lua | 21 | ||||
-rw-r--r-- | support/make4ht/make4ht-indexing.lua | 3 | ||||
-rw-r--r-- | support/make4ht/make4ht-odtfilter.lua | 33 | ||||
-rw-r--r-- | support/make4ht/mkparams.lua | 9 | ||||
-rw-r--r-- | support/make4ht/mkutils.lua | 5 | ||||
-rw-r--r-- | support/make4ht/readme.tex | 15 | ||||
-rw-r--r-- | support/pdfbook2/README | 12 | ||||
-rwxr-xr-x | support/pdfbook2/pdfbook2 | 380 | ||||
-rw-r--r-- | support/pdfbook2/pdfbook2.1 | 6 |
18 files changed, 648 insertions, 222 deletions
diff --git a/support/make4ht/README b/support/make4ht/README index 3b2e6b0085..78ec73056c 100644 --- a/support/make4ht/README +++ b/support/make4ht/README @@ -238,6 +238,16 @@ common\_domfilters `common_filters`. Used DOM filters are `fixinlines`, `idcolons`, `joincharacters`, and `tablerows`. +detect\_engine + +: detect engine and format necessary for the document compilation from the + magic comments supported by \LaTeX\ editors such as TeXShop or TeXWorks. + Add something like the following line at the beginning of the main \TeX\ file: + + `%!TEX TS-program = xelatex` + + It supports also Plain \TeX, use for example `tex` or `luatex` as the program name. + dvisvgm\_hashes : efficient generation of SVG pictures using Dvisvgm. It can utilize @@ -387,6 +397,10 @@ detect compilation errors in the TeX log file. : One call to the TeX engine with special configuration for loading of the `tex4ht.sty` package. +`Make:httex` + +: Variant of `Make:htlatex` suitable for Plain \TeX. + `Make:latexmk` : Use `Latexmk` for the document compilation. `tex4ht.sty` will be loaded automatically. @@ -571,6 +585,10 @@ aeneas : [Aeneas](https://www.readbeyond.it/aeneas/) is a tool for automagical synchronization of text and audio. This filter modifies the HTML code to support synchronization. +booktabs + +: fix lines produced by the `\cmidrule` command provided by the Booktabs package. + collapsetoc : collapse table of contents to contain only top-level sectioning level and sections on the current page. diff --git a/support/make4ht/changelog.tex b/support/make4ht/changelog.tex index 8fdd699e83..d67418081f 100644 --- a/support/make4ht/changelog.tex +++ b/support/make4ht/changelog.tex @@ -3,6 +3,70 @@ \begin{itemize} \item + 2020/01/22 + + \begin{itemize} + \tightlist + \item + version \texttt{0.3d} released. + \item + added \texttt{Make:httex} command for Plain TeX support. + \item + added \texttt{detect\_engine} extension. It supports detection of + the used engine and format from TeX Shop or TeXWorks magic comments. + These comments can look like: + \texttt{\%!TEX\ TS-program\ =\ xelatex}. + \end{itemize} +\item + 2020/01/22 + + \begin{itemize} + \tightlist + \item + fixed support for multiple indices in \texttt{make4ht-indexing.lua}. + \end{itemize} +\item + 2019/12/29 + + \begin{itemize} + \tightlist + \item + use the \texttt{mathvariant="italic"} attribute for joined + \texttt{\textless{}mi\textgreater{}} elements. + \item + fixed comparison of element attributes in \texttt{joincharacters} + DOM filter. + \end{itemize} +\item + 2019/12/28 + + \begin{itemize} + \tightlist + \item + print warning if the input file doesn't exist. + \end{itemize} +\item + 2019/12/17 + + \begin{itemize} + \tightlist + \item + added \texttt{booktabs} DOM filter. + \item + load the \texttt{booktabs} in \texttt{common\_domfilters} by + default. + \end{itemize} +\item + 2019/12/14 + + \begin{itemize} + \tightlist + \item + fixed bug in the \texttt{tablerows} DOM filter -- it could remove + table rows if they contained only one column with elements that + contained no text content. + \end{itemize} +\item 2019/11/28 \begin{itemize} diff --git a/support/make4ht/domfilters/make4ht-booktabs.lua b/support/make4ht/domfilters/make4ht-booktabs.lua new file mode 100644 index 0000000000..edec8adbd8 --- /dev/null +++ b/support/make4ht/domfilters/make4ht-booktabs.lua @@ -0,0 +1,82 @@ + +local function find_cmidrules(current_rows) + -- save rows with cmidrules here + local matched_rows = {} + local continue = false + for row_no, row in ipairs(current_rows) do + local columnposition = 1 + local matched_cmidrule = false + + for _, col in ipairs(row:query_selector("td")) do + -- keep track of culumns + local span = tonumber(col:get_attribute("colspan")) or 1 + local cmidrule = col:query_selector(".cmidrule") + -- column contain cmidrule + if #cmidrule > 0 then + -- remove any child elements, we don't need them anymore + col._children = {} + -- only one cmidrule can be on each row, save the position, column span and all attributes + matched_rows[row_no] = {attributes = col._attr, column = columnposition, span = span, continue = continue} + matched_cmidrule = true + end + columnposition = columnposition + span + end + if matched_cmidrule then + -- save the row number of the first cmidrule on the current row + continue = continue or row_no + else + continue = false + end + + end + -- save the table rows count, so we can loop over them sequentially later + matched_rows.length = #current_rows + return matched_rows +end + +local function update_row(current_rows, match, newspan, i) + local row_to_update = current_rows[match.continue] + -- insert spanning column if necessary + if newspan > 0 then + local td = row_to_update:create_element("td", {colspan=tostring(newspan), span="nazdar"}) + row_to_update:add_child_node(td) + end + -- insert the rule column + local td = row_to_update:create_element("td", match.attributes) + row_to_update:add_child_node(td) + -- remove unnecessary row + current_rows[i]:remove_node() +end + +local function join_rows(matched_rows,current_rows) + for i = 1, matched_rows.length do + local match = matched_rows[i] + if match then + -- we only need to process rows that place subsequent cmidrules on the same row + local continue = match.continue + if continue then + local prev_row = matched_rows[continue] + -- find column where the previous cmidrule ends + local prev_end = prev_row.column + prev_row.span + local newspan = match.column - prev_end + update_row(current_rows, match, newspan, i) + -- update the current row position + prev_row.column = match.column + prev_row.span = match.span + end + end + end +end + +local function process_booktabs(dom) + local tables = dom:query_selector("table") + for _, tbl in ipairs(tables) do + local current_rows = tbl:query_selector("tr") + local matched_rows = find_cmidrules(current_rows) + join_rows(matched_rows, current_rows) + end + return dom +end + +return process_booktabs + diff --git a/support/make4ht/domfilters/make4ht-joincharacters.lua b/support/make4ht/domfilters/make4ht-joincharacters.lua index 5830eae78e..4493aef023 100644 --- a/support/make4ht/domfilters/make4ht-joincharacters.lua +++ b/support/make4ht/domfilters/make4ht-joincharacters.lua @@ -3,14 +3,35 @@ local log = logging.new("joincharacters") local charclasses = { span=true, mn = true, - mi = true } +local function update_mathvariant(curr) + -- when we join several <mi> elements, they will be rendered incorrectly + -- we must set the mathvariant attribute + local parent = curr:get_parent() + -- set mathvariant only if it haven't been set by the parent element + if not parent:get_attribute("mathvariant") then + -- curr._attr = curr._attr or {} + local mathvariant = "italic" + -- the joined elements don't have attributes + curr._attr = curr._attr or {} + curr:set_attribute("mathvariant", mathvariant) + end +end + +local table_count = function(tbl) + local tbl = tbl or {} + local i = 0 + for k,v in pairs(tbl) do i = i + 1 end + return i +end + + local has_matching_attributes = function (el, next_el) local el_attr = el._attr or {} local next_attr = next_el._attr or {} -- if the number of attributes doesn't match, elements don't match - if #next_attr ~= #el_attr then return false end + if table_count(next_attr) ~= table_count(el_attr) then return false end for k, v in pairs(el_attr) do -- if any attribute doesn't match, elements don't match if v~=next_attr[k] then return false end @@ -18,6 +39,7 @@ local has_matching_attributes = function (el, next_el) return true end + local function join_characters(obj,par) -- join adjanced span and similar elements inserted by -- tex4ht to just one object. @@ -25,45 +47,48 @@ local function join_characters(obj,par) local options = get_filter_settings "joincharacters" local charclasses = options.charclasses or par.charclasses or charclasses - - obj:traverse_elements(function(el) - local get_name = function(curr) - return string.lower(curr:get_element_name()) - end - local get_class = function(next_el) - return next_el:get_attribute("class") or next_el:get_attribute("mathvariant") - end - local is_span = function(next_el) - return charclasses[get_name(next_el)] + local get_name = function(curr) + return string.lower(curr:get_element_name()) + end + local get_class = function(next_el) + return next_el:get_attribute("class") or next_el:get_attribute("mathvariant") + end + local is_span = function(next_el) + return charclasses[get_name(next_el)] + 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 + el:add_child_node(child) end + -- remove the next element + next_el:remove_node() + end - local function get_next(curr, class) - local next_el = curr:get_next_node() - if next_el and next_el:is_element() and is_span(next_el) then - return next_el - -- if the next node is space followed by a matching element, we should add this space - elseif next_el and next_el:is_text() and get_next(next_el, class) then - local text = next_el._text - -- match only text containing just whitespace - if text:match("^%s+$") then return next_el end - end + local function get_next(curr, class) + local next_el = curr:get_next_node() + if next_el and next_el:is_element() and is_span(next_el) then + return next_el + -- if the next node is space followed by a matching element, we should add this space + elseif next_el and next_el:is_text() and get_next(next_el, class) then + local text = next_el._text + -- match only text containing just whitespace + if text:match("^%s+$") then return next_el end end + end + + 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 local next_el = get_next(el) -- loop over the following elements and test whether they are of the same type -- as the current one - while next_el do + while next_el do -- save the next element because we will remove it later local real_next = get_next(next_el) if get_name(el) == get_name(next_el) and has_matching_attributes(el,next_el) and not el:get_attribute("id") then - -- it the following element match, copy it's children to the current element - for _, child in ipairs(next_el:get_children()) do - el:add_child_node(child) - end - -- remove the next element - next_el:remove_node() + join_elements(el, next_el) -- add the whitespace elseif next_el:is_text() then local s = next_el._text @@ -80,6 +105,36 @@ local function join_characters(obj,par) end end) + -- process <mi> elements + obj:traverse_elements(function(el) + local function get_next_mi(curr) + local next_el = curr:get_next_node() + if next_el and next_el:is_element() then + return next_el + end + end + local function has_no_attributes(x) + return table_count(x._attr) == 0 + end + + -- join only subsequential <mi> elements with no attributes + if get_name(el) == "mi" and has_no_attributes(el) then + local next_el = get_next_mi(el) + while next_el do + local real_next = get_next_mi(next_el) + if get_name(next_el) == "mi" and has_no_attributes(next_el) then + join_elements(el, next_el) + -- set math variant to italic + -- (if the parent <mstyle> element doesn't set it to something else) + update_mathvariant(el) + else + -- break the loop otherwise + real_next = nil + end + next_el = real_next + end + end + end) -- join text nodes in an element into one obj:traverse_elements(function(el) -- save the text diff --git a/support/make4ht/domfilters/make4ht-tablerows.lua b/support/make4ht/domfilters/make4ht-tablerows.lua index 155c24529a..3e3931b368 100644 --- a/support/make4ht/domfilters/make4ht-tablerows.lua +++ b/support/make4ht/domfilters/make4ht-tablerows.lua @@ -1,5 +1,16 @@ local log = logging.new ("tablerows") return function(dom) + local has_child_elements = function(child) + -- detect if the element contains child elements + local child_elements = 0 + local children = child:get_children() + for _, el in ipairs(children) do + local step = el:is_element() and 1 or 0 + -- log:info("element name", el._name) + child_elements = child_elements + step + end + return child_elements > 0 + end local is_empty_row = function(row) local not_empty = false local element_count = 0 @@ -11,7 +22,8 @@ return function(dom) if child:is_element() then element_count = element_count + 1 -- empty rows contain only one element, it is not empty otherwise - if element_count > 1 then return false end + if element_count > 1 or has_child_elements(child) then return false end + -- detect if it contains only whitespace not_empty = child:get_text():gsub("%s","") ~= "" or not_empty end diff --git a/support/make4ht/extensions/make4ht-ext-common_domfilters.lua b/support/make4ht/extensions/make4ht-ext-common_domfilters.lua index ec687cd042..63232b22ce 100644 --- a/support/make4ht/extensions/make4ht-ext-common_domfilters.lua +++ b/support/make4ht/extensions/make4ht-ext-common_domfilters.lua @@ -26,7 +26,7 @@ function M.modify_build(make) make:match("4om$", process, {charclasses= charclasses}) count = 2 else - local process = filter {"fixinlines", "idcolons", "joincharacters", "mathmlfixes", "tablerows"} + local process = filter {"fixinlines", "idcolons", "joincharacters", "mathmlfixes", "tablerows","booktabs"} make:match("html$", process) count = 1 end diff --git a/support/make4ht/extensions/make4ht-ext-detect_engine.lua b/support/make4ht/extensions/make4ht-ext-detect_engine.lua new file mode 100644 index 0000000000..8bb91d3a69 --- /dev/null +++ b/support/make4ht/extensions/make4ht-ext-detect_engine.lua @@ -0,0 +1,91 @@ +-- support magic comments used by TeXShop and TeXWorks to detect used engine and format +-- +local M = {} +local log = logging.new("detect engine") +local htlatex = require "make4ht-htlatex" + +-- we must change build sequence when Plain TeX is requested +local change_table = { + tex = { + htlatex = "etex", + command = htlatex.httex + }, + pdftex = { + htlatex = "etex", + command = htlatex.httex + }, + etex = { + htlatex = "etex", + command = htlatex.httex + }, + luatex = { + htlatex = "dviluatex", + command = htlatex.httex + }, + xetex = { + htlatex = "xetex -no-pdf", + command = htlatex.httex + }, + xelatex = { + htlatex = "xelatex -no-pdf", + }, + lualatex = { + htlatex = "dvilualatex", + }, + pdflatex = { + htlatex = "latex" + } + +} + +local function find_magic_program(filename) + -- find the magic line containing program name + local get_comment = function(line) + return line:match("%s*%%%s*(.+)") + end + local empty_line = function(line) return line:match("^%s*$") end + for line in io.lines(filename) do + local comment = get_comment(line) + -- read line after line from the file, break the processing after first non comment or non empty line + if not comment and not empty_line(line) then return nil, "Cannot find program name" end + comment = comment or "" -- comment is nil for empty lines + local program = comment:match("!%s*[Tt][Ee][Xx].-program%s*=%s*([^%s]+)") + if program then return program:lower() end + end +end + +-- update htlatex entries with detected program +local function update_build_sequence(program, build_seq) + -- handle Plain TeX + local replaces = change_table[program] or {} + local is_xetex = program:match("xe") -- we must handle xetex in tex4ht + for pos, entry in ipairs(build_seq) do + if entry.name == "htlatex" then + -- handle httex + entry.command = replaces.command or entry.command + local params = entry.params or {} + params.htlatex = replaces.htlatex or params.htlatex + entry.params = params + elseif is_xetex and entry.name == "tex4ht" then + -- tex4ht must process .xdv file if the TeX file was compiled by XeTeX + entry.params.tex4ht_par = entry.params.tex4ht_par .. " -.xdv" + end + end +end + + +function M.modify_build(make) + -- find magic comments in the TeX file + local build_seq = make.build_seq + local tex_file = make.params.tex_file + local program, msg = find_magic_program(tex_file) + if program then + log:info("Found program name", program) + update_build_sequence(program, build_seq) + else + log:warning("Cannot find magic line with the program name") + end + return make +end + +return M diff --git a/support/make4ht/make4ht b/support/make4ht/make4ht index 10311088d7..d90bd5b970 100755 --- a/support/make4ht/make4ht +++ b/support/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.3c" +local version = "v0.3d" mkparams.version_number = version local args = mkparams.get_args() diff --git a/support/make4ht/make4ht-doc.pdf b/support/make4ht/make4ht-doc.pdf Binary files differindex 949fa599c6..287a06722a 100644 --- a/support/make4ht/make4ht-doc.pdf +++ b/support/make4ht/make4ht-doc.pdf diff --git a/support/make4ht/make4ht-htlatex.lua b/support/make4ht/make4ht-htlatex.lua index 214972e8bb..f141070406 100644 --- a/support/make4ht/make4ht-htlatex.lua +++ b/support/make4ht/make4ht-htlatex.lua @@ -51,11 +51,17 @@ Make.latex_command = "${htlatex} --interaction=${interaction} ${latex_par} '\\ma "\\documentstyle[tex4ht]}}}\\makeatother\\HCode ${tex4ht_sty_par}.a.b.c.".. "\\input \"\\detokenize{${tex_file}}\"'" +Make.plain_command = '${htlatex} --interaction=${interaction} ${latex_par}' .. +"'\\def\\Link#1.a.b.c.{\\expandafter\\def\\csname tex4ht\\endcsname{\\expandafter\\def\\csname tex4ht\\endcsname{#1,html}\\input tex4ht.sty }}" .. +"\\def\\HCode{\\futurelet\\HCode\\HChar}\\def\\HChar{\\ifx\"\\HCode\\def\\HCode\"##1\"{\\Link##1}\\expandafter\\HCode\\else\\expandafter\\Link\\fi}" .. +"\\HCode ${tex4ht_sty_par}.a.b.c.\\input \"\\detokenize{${tex_file}}\"'" + local m = {} -function m.htlatex(par) - local command = Make.latex_command +function m.htlatex(par, latex_command) + -- latex_command can be also plain_command for Plain TeX + local command = latex_command or Make.latex_command local devnull = " > /dev/null 2>&1" if os.type == "windows" then command = command:gsub("'",'') @@ -72,4 +78,15 @@ function m.htlatex(par) return Make.testlogfile(par) end +function m.httex(par) + local newpar = {} + for k,v in pairs(par) do newpar[k] = v end + -- change executable name from *latex to *tex + newpar.htlatex = newpar.htlatex:gsub("latex", "tex") + -- plain tex command doesn't support etex extensions + -- which are necessary for TeX4ht. just quick hack to fix this + if newpar.htlatex == "tex" then newpar.htlatex = "etex" end + return m.htlatex(newpar, Make.plain_command) +end + return m diff --git a/support/make4ht/make4ht-indexing.lua b/support/make4ht/make4ht-indexing.lua index 5801df509b..072536556a 100644 --- a/support/make4ht/make4ht-indexing.lua +++ b/support/make4ht/make4ht-indexing.lua @@ -177,6 +177,9 @@ local run_indexing_command = function(command, par) if not status then xindylog:warning(msg) end -- remove the temporary idx file os.remove(newidxfile) + -- null the indfile, it is necessary in order to support + -- multiple indices + par.indfile = nil end M.get_utf8 = get_utf8 diff --git a/support/make4ht/make4ht-odtfilter.lua b/support/make4ht/make4ht-odtfilter.lua deleted file mode 100644 index 97f0b8df67..0000000000 --- a/support/make4ht/make4ht-odtfilter.lua +++ /dev/null @@ -1,33 +0,0 @@ -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/support/make4ht/mkparams.lua b/support/make4ht/mkparams.lua index eff8c99670..0254de9ac2 100644 --- a/support/make4ht/mkparams.lua +++ b/support/make4ht/mkparams.lua @@ -134,6 +134,11 @@ local function handle_jobname(input, args) return latex_params, input end +local function tex_file_not_exits(tex_file) + -- try to find the input file, return false if we cannot find it + return not (kpse.find_file(tex_file, "tex") or kpse.find_file(tex_file .. ".tex", "tex")) +end + -- use standard input instead of file if the filename is just `-` -- return the filename and status if it is a tmp name local function handle_input_file(filename) @@ -191,6 +196,10 @@ local function process_args(args) local compiler = args.lua and "dvilualatex" or args.xetex and "xelatex --no-pdf" or "latex" local tex_file, is_tmp_file = handle_input_file(args.filename) + -- test if the file exists + if not is_tmp_file and tex_file_not_exits(tex_file) then + log:warning("Cannot find input file: " .. tex_file) + end local input = mkutils.remove_extension(tex_file) -- the output file name can be influneced using -jobname parameter passed to the TeX engine local latex_params, input = handle_jobname(input, args) diff --git a/support/make4ht/mkutils.lua b/support/make4ht/mkutils.lua index 944ab99cc7..a4f16fb71a 100644 --- a/support/make4ht/mkutils.lua +++ b/support/make4ht/mkutils.lua @@ -338,7 +338,10 @@ 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, { + htlatex = "etex", + correct_exit=0 +}) env.Make:add("latexmk", function(par) local settings = get_filter_settings "htlatex" or {} diff --git a/support/make4ht/readme.tex b/support/make4ht/readme.tex index a326ce1010..b221473e02 100644 --- a/support/make4ht/readme.tex +++ b/support/make4ht/readme.tex @@ -303,6 +303,16 @@ clean the output HTML files using filters. clean the HTML file using DOM filters. It is more powerful than \texttt{common\_filters}. Used DOM filters are \texttt{fixinlines}, \texttt{idcolons}, \texttt{joincharacters}, and \texttt{tablerows}. +\item[detect\_engine] +detect engine and format necessary for the document compilation from the +magic comments supported by \LaTeX~editors such as TeXShop or TeXWorks. +Add something like the following line at the beginning of the main +\TeX~file: + +\texttt{\%!TEX\ TS-program\ =\ xelatex} + +It supports also Plain \TeX, use for example \texttt{tex} or +\texttt{luatex} as the program name. \item[dvisvgm\_hashes] efficient generation of SVG pictures using Dvisvgm. It can utilize multiple processor cores and generates only changed images. @@ -468,6 +478,8 @@ the TeX log file. \item[\texttt{Make:htlatex}] One call to the TeX engine with special configuration for loading of the \texttt{tex4ht.sty} package. +\item[\texttt{Make:httex}] +Variant of \texttt{Make:htlatex} suitable for Plain \TeX. \item[\texttt{Make:latexmk}] Use \texttt{Latexmk} for the document compilation. \texttt{tex4ht.sty} will be loaded automatically. @@ -639,6 +651,9 @@ Available DOM filters: \href{https://www.readbeyond.it/aeneas/}{Aeneas} is a tool for automagical synchronization of text and audio. This filter modifies the HTML code to support synchronization. +\item[booktabs] +fix lines produced by the \texttt{\textbackslash{}cmidrule} command +provided by the Booktabs package. \item[collapsetoc] collapse table of contents to contain only top-level sectioning level and sections on the current page. diff --git a/support/pdfbook2/README b/support/pdfbook2/README index 2b72d40f06..e27738188b 100644 --- a/support/pdfbook2/README +++ b/support/pdfbook2/README @@ -1,8 +1,8 @@ pdfbook2 - transform pdf files to booklets ========================================== - pdfbook2 v1.3 (https://github.com/jenom/pdfbook2) - (c) 2015 - 2019 Johannes Neumann (http://www.neumannjo.de) + pdfbook2 v1.4 (https://github.com/jenom/pdfbook2) + (c) 2015 - 2020 Johannes Neumann (http://www.neumannjo.de) licensed under GPLv3 (http://www.gnu.org/licenses/gpl-3.0) based on pdfbook by David Firth with help from Marco Pessotto @@ -45,7 +45,7 @@ INSTALLATION REQUIREMENTS - python 2.7, pdfjam, pdfcrop and their dependencies. + python 3.6, pdfjam, pdfcrop and their dependencies. EXAMPLES @@ -115,6 +115,12 @@ OPTIONS CHANGELOG + 1.4 2020/01/20 + + - migration to Python 3 + - fixed bug if the input document had only one page + - fix for signature option not working + 1.3 2019/08/12 - removed wait after popen to prevent deadlock with very large documents diff --git a/support/pdfbook2/pdfbook2 b/support/pdfbook2/pdfbook2 index c5bc8677db..f9bb012cc7 100755 --- a/support/pdfbook2/pdfbook2 +++ b/support/pdfbook2/pdfbook2 @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ pdfbook2 - transform pdf files to booklets This program is free software: you can redistribute it and/or modify @@ -16,219 +16,303 @@ """ -import sys -import subprocess import os -from optparse import OptionParser, OptionGroup, HelpFormatter import shutil +import subprocess +import sys +from optparse import HelpFormatter, OptionGroup, OptionParser - -#=============================================================================== +# =============================================================================== # Create booklet for file $name -#=============================================================================== +# =============================================================================== -def booklify( name, opts ): - #------------------------------------------------------ Check if file exists - print "\nProcessing", name - if not os.path.isfile( name ): - print "SKIP: file not found." + +def booklify(name, opts): + # ------------------------------------------------------ Check if file exists + print("\nProcessing", name) + if not os.path.isfile(name): + print("SKIP: file not found.") return - print "Getting bounds...", + print("Getting bounds...", end=" ") sys.stdout.flush() - #---------------------------------------------------------- useful constants - bboxName = "%%HiResBoundingBox:" + # ---------------------------------------------------------- useful constants + bboxName = b"%%HiResBoundingBox:" tmpFile = ".crop-tmp.pdf" - #------------------------------------------------- find min/max bounding box + # ------------------------------------------------- find min/max bounding box if opts.crop: - p = subprocess.Popen( ["pdfcrop", "--verbose", - "--resolution", repr( opts.resolution ), - name, tmpFile], - stdout = subprocess.PIPE, - stderr = subprocess.PIPE ) + p = subprocess.Popen( + ["pdfcrop", "--verbose", "--resolution", repr(opts.resolution), name, tmpFile], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) out, err = p.communicate() - if len( err ) != 0: - print err - print "\n\nABORT: Problem getting bounds" - sys.exit( 1 ) + if len(err) != 0: + print(err) + print("\n\nABORT: Problem getting bounds") + sys.exit(1) lines = out.splitlines() - bboxes = [s[len( bboxName ) + 1:] for s in lines if s.startswith( bboxName )] - bounds = [[float( x ) for x in bbox.split()] for bbox in bboxes ] - minLOdd = min( [bound[0] for bound in bounds[::2] ] ) - maxROdd = max( [bound[2] for bound in bounds[::2] ] ) - minLEven = min( [bound[0] for bound in bounds[1::2] ] ) - maxREven = max( [bound[2] for bound in bounds[1::2] ] ) - minT = min( [bound[1] for bound in bounds ] ) - maxB = max( [bound[3] for bound in bounds ] ) + bboxes = [s[len(bboxName) + 1 :] for s in lines if s.startswith(bboxName)] + bounds = [[float(x) for x in bbox.split()] for bbox in bboxes] + minLOdd = min([bound[0] for bound in bounds[::2]]) + maxROdd = max([bound[2] for bound in bounds[::2]]) + if len(bboxes) > 1: + minLEven = min([bound[0] for bound in bounds[1::2]]) + maxREven = max([bound[2] for bound in bounds[1::2]]) + else: + minLEven = minLOdd + maxREven = maxROdd + minT = min([bound[1] for bound in bounds]) + maxB = max([bound[3] for bound in bounds]) widthOdd = maxROdd - minLOdd widthEven = maxREven - minLEven - maxWidth = max( widthOdd, widthEven ) + maxWidth = max(widthOdd, widthEven) minLOdd -= maxWidth - widthOdd maxREven += maxWidth - widthEven - print "done" + print("done") sys.stdout.flush() - #--------------------------------------------- crop file to area of interest - print "cropping...", + # --------------------------------------------- crop file to area of interest + print("cropping...", end=" ") sys.stdout.flush() - p = subprocess.Popen( ["pdfcrop", - "--bbox-odd", "{L} {T} {R} {B}".format( L = minLOdd - opts.innerMargin / 2, - T = minT - opts.topMargin, - R = maxROdd + opts.outerMargin, - B = maxB + opts.outerMargin ), - "--bbox-even", "{L} {T} {R} {B}".format( L = minLEven - opts.outerMargin, - T = minT - opts.topMargin, - R = maxREven + opts.innerMargin / 2, - B = maxB + opts.outerMargin ), - "--resolution", repr( opts.resolution ), - name, - tmpFile], - stdout = subprocess.PIPE, - stderr = subprocess.PIPE ) + p = subprocess.Popen( + [ + "pdfcrop", + "--bbox-odd", + "{L} {T} {R} {B}".format( + L=minLOdd - opts.innerMargin / 2, + T=minT - opts.topMargin, + R=maxROdd + opts.outerMargin, + B=maxB + opts.outerMargin, + ), + "--bbox-even", + "{L} {T} {R} {B}".format( + L=minLEven - opts.outerMargin, + T=minT - opts.topMargin, + R=maxREven + opts.innerMargin / 2, + B=maxB + opts.outerMargin, + ), + "--resolution", + repr(opts.resolution), + name, + tmpFile, + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) out, err = p.communicate() - if len( err ) != 0: - print err - print "\n\nABORT: Problem with cropping" - sys.exit( 1 ) - print "done" + if len(err) != 0: + print(err) + print("\n\nABORT: Problem with cropping") + sys.exit(1) + print("done") sys.stdout.flush() else: - shutil.copy( name, tmpFile ) + shutil.copy(name, tmpFile) - #-------------------------------------------------------- create the booklet - print "create booklet...", + # -------------------------------------------------------- create the booklet + print("create booklet...", end=" ") sys.stdout.flush() - pdfJamCallList = [ "pdfjam", - "--booklet", "true", - "--landscape", - "--suffix", "book", - "--signature", repr( opts.signature ), - tmpFile ] + pdfJamCallList = [ + "pdfjam", + "--landscape", + "--suffix", + "book", + tmpFile, + ] + + # add option signature if it is defined else booklet + if opts.signature != 0: + pdfJamCallList.append("--signature") + pdfJamCallList.append(repr(opts.signature)) + else: + pdfJamCallList.append("--booklet") + pdfJamCallList.append("true") # add option --paper to call if opts.paper is not None: - pdfJamCallList.append( "--paper" ) - pdfJamCallList.append( opts.paper ) + pdfJamCallList.append("--paper") + pdfJamCallList.append(opts.paper) # add option --short-edge to call if opts.shortedge: # check if everyshi.sty exists as texlive recommends - p = subprocess.Popen( ["kpsewhich", "everyshi.sty"], - stdout = subprocess.PIPE, - stderr = subprocess.PIPE ) + p = subprocess.Popen( + ["kpsewhich", "everyshi.sty"], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) out, err = p.communicate() - if len( out ) == 0: - print "\n\nABORT: The everyshi.sty latex package is needed for short-edge." - sys.exit( 1 ) + if len(out) == 0: + print("\n\nABORT: The everyshi.sty latex package is needed for short-edge.") + sys.exit(1) else: - pdfJamCallList.append( "--preamble" ) - pdfJamCallList.append( r"\usepackage{everyshi}\makeatletter\EveryShipout{\ifodd\c@page\pdfpageattr{/Rotate 180}\fi}\makeatother" ) + pdfJamCallList.append("--preamble") + pdfJamCallList.append( + r"\usepackage{everyshi}\makeatletter\EveryShipout{\ifodd\c@page\pdfpageattr{/Rotate 180}\fi}\makeatother" + ) # run call to pdfJam to make booklet - p = subprocess.Popen( pdfJamCallList, - stdout = subprocess.PIPE, - stderr = subprocess.PIPE ) + p = subprocess.Popen(pdfJamCallList, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() - #-------------------------------------------- move file and remove temp file - os.rename( tmpFile[:-4] + "-book.pdf", name[:-4] + "-book.pdf" ) - os.remove( tmpFile ) - print "done" + # -------------------------------------------- move file and remove temp file + os.rename(tmpFile[:-4] + "-book.pdf", name[:-4] + "-book.pdf") + os.remove(tmpFile) + print("done") sys.stdout.flush() -#=============================================================================== +# =============================================================================== # Help formatter -#=============================================================================== +# =============================================================================== + -class MyHelpFormatter ( HelpFormatter ): +class MyHelpFormatter(HelpFormatter): """Format help with indented section bodies. """ - def __init__( self, - indent_increment = 4, - max_help_position = 16, - width = None, - short_first = 0 ): - HelpFormatter.__init__( - self, indent_increment, max_help_position, width, short_first ) + def __init__(self, indent_increment=4, max_help_position=16, width=None, short_first=0): + HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first) - def format_usage( self, usage ): - return ( "USAGE\n\n%*s%s\n" ) % ( self.indent_increment, "", usage ) + def format_usage(self, usage): + return ("USAGE\n\n%*s%s\n") % (self.indent_increment, "", usage) - def format_heading( self, heading ): - return "%*s%s\n\n" % ( self.current_indent, "", heading.upper() ) + def format_heading(self, heading): + return "%*s%s\n\n" % (self.current_indent, "", heading.upper()) -#=============================================================================== +# =============================================================================== # main programm -#=============================================================================== +# =============================================================================== if __name__ == "__main__": - #------------------------------------------------------------ useful strings + # ------------------------------------------------------------ useful strings usageString = "Usage: %prog [options] file1 [file2 ...]" versionString = """ - %prog v1.3 (https://github.com/jenom/pdfbook2) - (c) 2015 Johannes Neumann (http://www.neumannjo.de) + %prog v1.4 (https://github.com/jenom/pdfbook2) + (c) 2015 - 2020 Johannes Neumann (http://www.neumannjo.de) licensed under GPLv3 (http://www.gnu.org/licenses/gpl-3.0) based on pdfbook by David Firth with help from Marco Pessotto\n""" defaultString = " (default: %default)" - #------------------------------------------------- create commandline parser - parser = OptionParser( usage = usageString, version = versionString, - formatter = MyHelpFormatter( indent_increment = 4 ) ) - - generalGroup = OptionGroup( parser, "General" ) - generalGroup.add_option( "-p", "--paper", dest = "paper", type = "str", action = "store", - metavar = "STR", - help = "Format of the output paper dimensions as latex keyword (e.g. a4paper, letterpaper, legalpaper, ...)" ) - generalGroup.add_option( "-s", "--short-edge", dest = "shortedge", action = "store_true", - help = "Format the booklet for short-edge double-sided printing", - default = False ) - generalGroup.add_option( "-n", "--no-crop", dest = "crop", action = "store_false", - help = "Prevent the cropping to the content area", - default = True ) - parser.add_option_group( generalGroup ) - - marginGroup = OptionGroup( parser, "Margins" ) - marginGroup.add_option( "-o", "--outer-margin", type = "int", default = 40, - dest = "outerMargin", action = "store", metavar = "INT", - help = "Defines the outer margin in the booklet" + defaultString ) - marginGroup.add_option( "-i", "--inner-margin", type = "int", default = 150, - dest = "innerMargin", action = "store", metavar = "INT", - help = "Defines the inner margin between the pages in the booklet" + defaultString ) - marginGroup.add_option( "-t", "--top-margin", type = "int", default = 30, - dest = "topMargin", action = "store", metavar = "INT", - help = "Defines the top margin in the booklet" + defaultString ) - marginGroup.add_option( "-b", "--bottom-margin", type = "int", default = 30, metavar = "INT", - dest = "bottomMargin", action = "store", - help = "Defines the bottom margin in the booklet" + defaultString ) - parser.add_option_group( marginGroup ) - - advancedGroup = OptionGroup( parser, "Advanced" ) - advancedGroup.add_option( "--signature", dest = "signature", action = "store", type = "int", - help = "Define the signature for the booklet handed to pdfjam, needs to be multiple of 4" + defaultString, - default = 4, metavar = "INT" ) - advancedGroup.add_option( "--signature*", dest = "signature", action = "store", type = "int", - help = "Same as --signature", metavar = "INT" ) - advancedGroup.add_option( "--resolution", dest = "resolution", action = "store", type = "int", - help = "Resolution used by ghostscript in bp" + defaultString, - metavar = "INT", default = 72 ) - parser.add_option_group( advancedGroup ) + # ------------------------------------------------- create commandline parser + parser = OptionParser( + usage=usageString, version=versionString, formatter=MyHelpFormatter(indent_increment=4) + ) + + generalGroup = OptionGroup(parser, "General") + generalGroup.add_option( + "-p", + "--paper", + dest="paper", + type="str", + action="store", + metavar="STR", + help="Format of the output paper dimensions as latex keyword (e.g. a4paper, letterpaper, legalpaper, ...)", + ) + generalGroup.add_option( + "-s", + "--short-edge", + dest="shortedge", + action="store_true", + help="Format the booklet for short-edge double-sided printing", + default=False, + ) + generalGroup.add_option( + "-n", + "--no-crop", + dest="crop", + action="store_false", + help="Prevent the cropping to the content area", + default=True, + ) + parser.add_option_group(generalGroup) + + marginGroup = OptionGroup(parser, "Margins") + marginGroup.add_option( + "-o", + "--outer-margin", + type="int", + default=40, + dest="outerMargin", + action="store", + metavar="INT", + help="Defines the outer margin in the booklet" + defaultString, + ) + marginGroup.add_option( + "-i", + "--inner-margin", + type="int", + default=150, + dest="innerMargin", + action="store", + metavar="INT", + help="Defines the inner margin between the pages in the booklet" + defaultString, + ) + marginGroup.add_option( + "-t", + "--top-margin", + type="int", + default=30, + dest="topMargin", + action="store", + metavar="INT", + help="Defines the top margin in the booklet" + defaultString, + ) + marginGroup.add_option( + "-b", + "--bottom-margin", + type="int", + default=30, + metavar="INT", + dest="bottomMargin", + action="store", + help="Defines the bottom margin in the booklet" + defaultString, + ) + parser.add_option_group(marginGroup) + + advancedGroup = OptionGroup(parser, "Advanced") + advancedGroup.add_option( + "--signature", + dest="signature", + action="store", + type="int", + help="Define the signature for the booklet handed to pdfjam, needs to be multiple of 4" + + defaultString, + default=0, + metavar="INT", + ) + advancedGroup.add_option( + "--signature*", + dest="signature", + action="store", + type="int", + help="Same as --signature", + metavar="INT", + ) + advancedGroup.add_option( + "--resolution", + dest="resolution", + action="store", + type="int", + help="Resolution used by ghostscript in bp" + defaultString, + metavar="INT", + default=72, + ) + parser.add_option_group(advancedGroup) opts, args = parser.parse_args() - #------------------------------------ show help if started without arguments - if len( args ) == 0: + # ------------------------------------ show help if started without arguments + if len(args) == 0: parser.print_version() parser.print_help() - print "" - sys.exit( 2 ) + print("") + sys.exit(2) - #------------------------------------------- run for each provided file name + # ------------------------------------------- run for each provided file name parser.print_version() for arg in args: - booklify( arg, opts ) + booklify(arg, opts) diff --git a/support/pdfbook2/pdfbook2.1 b/support/pdfbook2/pdfbook2.1 index a6c61c0f90..c761447b53 100644 --- a/support/pdfbook2/pdfbook2.1 +++ b/support/pdfbook2/pdfbook2.1 @@ -1,4 +1,4 @@ -.TH pdfbook2 1 "August 12, 2019" "" "pdfbook2 - transform pdf files to booklets" +.TH pdfbook2 1 "January 22, 2020" "" "pdfbook2 - transform pdf files to booklets" .SH NAME pdfbook2 \- transform pdf files into booklets for double-sided printing @@ -107,9 +107,9 @@ Resolution used by ghostscript in bp (default: 72) .BR pdfcrop (1) .SH ABOUT -pdfbook2 v1.3 (https://github.com/jenom/pdfbook2) +pdfbook2 v1.4 (https://github.com/jenom/pdfbook2) .br -(c) 2015 - 2019 Johannes Neumann (http://www.neumannjo.de) +(c) 2015 - 2020 Johannes Neumann (http://www.neumannjo.de) .br licensed under GPLv3 (http://www.gnu.org/licenses/gpl-3.0) .br |