diff options
author | Karl Berry <karl@freefriends.org> | 2018-04-09 21:09:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2018-04-09 21:09:51 +0000 |
commit | 4d2d4764dde3caf40817a1ef84719e1319b10d2f (patch) | |
tree | 096967b6679e1e110b75e42e1747936323508fc4 /Master | |
parent | 1e0147a88c7bc3a3e2986968f697ef5fbcef2bcb (diff) |
make4ht (9apr18)
git-svn-id: svn://tug.org/texlive/trunk@47398 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
28 files changed, 3014 insertions, 791 deletions
diff --git a/Master/texmf-dist/doc/support/make4ht/README b/Master/texmf-dist/doc/support/make4ht/README index 9d7c10c75b4..924456189d8 100644 --- a/Master/texmf-dist/doc/support/make4ht/README +++ b/Master/texmf-dist/doc/support/make4ht/README @@ -1,175 +1,320 @@ +% [![Build Status](https://travis-ci.org/michal-h21/make4ht.svg?branch=master)](https://travis-ci.org/michal-h21/make4ht) + # Introduction -`make4ht` is a simple build system for tex4ht. It is both executable which -simplifies `tex4ht` execution and a library which can be used to create -customized conversion programs. An example of such conversion program is [tex4ebook](https://github.com/michal-h21/tex4ebook) +`make4ht` is a simple build system for `tex4ht`, \TeX\ to XML converter. It provides a command line tool +that drive the conversion process. It also provides a library which can be used to create +customized conversion tools. An example of such conversion tool is +[tex4ebook](https://github.com/michal-h21/tex4ebook) for conversion of \TeX\ to +ePub and other e-book formats. -## License -Permission is granted to copy, distribute and/or modify this software -under the terms of the LaTeX Project Public License, version 1.3. +## How it works + +### The issues with default `tex4ht` conversion commands -How it works ------------- -Default compilation script for `tex4ht`, named `htlatex` compiles LaTeX files to `HTML` with this command sequence: +`tex4ht` system supports several output formats, most notably `XHTML`, `HTML 5` and `ODT`. +The conversion can be invoked using several commands. These commands invoke LaTeX\ or Plain TeX +with special instructions to load `tex4ht.sty` package. The \TeX\ run produces special `DVI` file +which contains the code for desired output format. The `DVI` file is then processed and +desired output files are created. + +The basic command provided by `tex4ht` is named `htlatex`. It compiles \LaTeX\ +files to `HTML` with this command sequence: latex $latex_options 'code for loading tex4ht.sty \input{filename}' latex $latex_options 'code for loading tex4ht.sty \input{filename}' latex $latex_options 'code for loading tex4ht.sty \input{filename}' - tex4ht options filename - t4ht options filename - -The problem is that this is inefficient when you need to run program which -interact with LaTeX, such as `Makeindex` or `Bibtex`. In that case, you need to -create new script based on the default one, or run `htlatex` twice, which means -six LaTeX runs. - -Another problem is with `t4ht` application. It reads file `filename.lg`, -generated by `tex4ht`, where are instructions about generated files, `CSS` -instructions, calls to external applications, instructions for image -conversions etc. It can be instructed to copy generated files to some output -directory, but it doesn't preserve directory structure, so when you -have images in some subdirectory, they will be copied to the output directory, -but links will be pointing to non existing subdirectory. - -Image conversion is directed with the -[env file](http://www.tug.org/applications/tex4ht/mn35.html#index35-73001), -with really strange syntax based on whitespace and + tex4ht $tex4ht_options filename + t4ht $t4ht_options filename + +The options for various parts of the system can be passed on the command line: + + htlatex filename "tex4ht.sty options" "tex4ht_options" "t4ht_options" "latex_options" + +For basic `HTML` conversion it is possible to use the most basic invocation: + + htlatex filename.tex + +It can be much more involved for `HTML 5` output in `UTF-8` encoding: + + htlatex filename.tex "xhtml,html5,charset=utf-8" "-cmozhtf -utf8" + +`make4ht` can simplify it: + + make4ht -uf html5 filename.tex + +Another issue is the fixed compilation order and hard-coded number of LaTeX invocations. + +When you need to run a program which interact with LaTeX, such as `Makeindex` +or `Bibtex`, you need to create a new script based on `htlatex`, or run +`htlatex` twice, which means that LaTeX will be invoked six times. +This can lead to significantly long compilation times. `make4ht` provides build files and +extensions, which can be used for interaction with external tools. + +It is also possible to have several compilation modes. When you just add new text to a document, +which doesn't contain cross-references, don't add new stuff to the table of contents, etc., +it is possible to use the `draft` mode which will invoke LaTeX only once. It +can save quite a lot of the compilation time: + + make4ht -um draft -f html5 filename.tex + +There are also issues with a behaviour of the `t4ht` application. It reads file +`filename.lg`, generated by `tex4ht`, where are instructions about generated +files, `CSS` instructions, calls to external applications, instructions for +image conversions etc. It can be instructed to copy generated files to some +output directory, but it doesn't preserve directory structure, so when you +have images in a subdirectory, they will be copied to the output directory. +Links will be pointing to a non-existing subdirectory. The following command +should copy all output files to the correct destinations. + + make4ht -d outputdir filename.tex + + + +The image conversion is configured in the +[env file](http://www.tug.org/applications/tex4ht/mn35.html#index35-73001), +which has really strange syntax based and the rules are [os dependent](http://www.tug.org/applications/tex4ht/mn-unix.html#index27-69005). +`make4ht` provides simpler means for the image conversion in the build files. + With `make4ht` build files, we have simple mean to fix these issues. We can -change image conversion parameters without need to modify the `env file`, -and call actions on the output files. These actions can be either external +change image conversion parameters without the need to modify the `env file`, +or execute actions on the output files. These actions can be either external programs such as `xslt` processors or `HTML tidy` or `Lua` functions. The idea is to make system controlled by a build file. Because `Lua` interpreter is included in modern TeX distributions and `Lua` is ideal language -for such task, it was chosen as language in which build script are written. +for such task, it was chosen as language in which the build scripts are written. + +# Output file formats and extensions + +The default output format used by `make4ht` is `html5`. Different +format can be requested using the `--format` option. Supported formats are: + + - `xhtml` + - `html5` + - `odt` + +The `--format` option can be also used for extension loading. + +## Extensions +Extensions can be used to modify the build process without the need to use a build file. They +may post-process the output files or request additional commands for the compilation. + +The extensions can be enabled or disabled by appending `+EXTENSION` or `-EXTENSION` after +the output format name: + + make4ht -uf html5+tidy filename.tex + +Available extensions: + +latexmk + +: use `Latexmk` for \LaTeX\ compilation. + +tidy + +: clean the `HTML` files using the `tidy` command. + +common\_filters + +: clean the output HTML files using filters. + +common\_domfilters + +: Clean the HTML file using DOM filters. It is more powerful than +`common_filters`. Used DOM filters are `fixinlines`, `idcolons` and +`joincharacters`. + +mathjaxnode + +: use [mathjax-node-page](https://github.com/pkra/mathjax-node-page/) to + convert from MathML code to HTML + CSS or SVG. See [the available + settings](#mathjaxsettings). # Build files -## Commands +`make4ht` supports build files. These are `Lua` scripts which can adjust +the build process. You can request external applications like `bibtex` or `makeindex`, +pass options to the commands, modify the image conversion process, or post-process the +generated files. -By default, build file is saved in file named `filename + .mk4 extension`. +`make4ht` tries to load default build file named as `filename + .mk4 extension`. You can choose different build file with `-e` or `--build-file` command line option. -Sample: +Sample build file: Make:htlatex() Make:match("html$", "tidy -m -xml -utf8 -q -i ${filename}") `Make:htlatex()` is preconfigured command for calling LaTeX with `tex4ht` -loaded on the input file. In this case it will be called one time. After -compilation, `tidy` command is executed on the output `html` file. +loaded on the input file. In this case, it will be called one time. After +compilation, the `tidy` command is executed on the output `HTML` file. Note that you don't have to call `tex4ht` and `t4ht` commands explicitly in the build file, they are called automatically. -You can add more commands like `Make:htlatex` with +## User commands + +You can add more commands like `Make:htlatex` using `Make:add` command: Make:add("name", "command", {parameters}, repetition) +The `name` and `command` parameters are required, rest of the parameters are optional. -it can be called with +This defines `name` command, which can be then executed as `Make:name()` command. - Make:name() +### Provided commands -`command` can be text template, or function: +`Make:htlatex` - Make:add("text", "hello, input file: ${input}") +: One call to TeX engine with special configuration for `tex4ht` loading. + +`Make:latexmk` + +: Use `Latexmk` for the document compilation. `tex4ht` will be loaded automatically. + +`Make:tex4ht` + +: Process the `DVI` file and creates the output files. + +`Make:t4ht` + +: Creates the CSS file. + + +### Command function + +The `command` parameter can be either string template or function: + + Make:add("text", "echo hello, input file: ${input}") Make:add("function", function(params) for k, v in pairs(params) do print(k..": "..v) - end + end, {custom="Hello world"} ) -`parameters` is a table or `nil` value. +The template can get variable value from the parameters table using a +`${var_name}` placeholder. Templates are executed using operating system, so +they should invoke existing OS commands. Function commands may execute system +commands using `os.execute` function. + + +### Parameters table + +`parameters` parameter is optional, it can be table or `nil` value, which +should be used if you want to use the `repetition` parameter, but don't want to +modify the parameters table. -Default parameters are: +The table with default parameters is passed to all commands, they can be accessed from command functions +or templates. When you specify your own parameters in the command definition, these additional +parameters are added to the default parameters table for this particular +command. You can override the default parameters in the parameters table. -htlatex + + +The default parameters are following: + +`htlatex` : used compiler -input +`input` : it is output file name in fact -tex_file +`tex_file` : input TeX file -latex_par +`latex_par` : parameters to `latex` -packages +`packages` -: insert additionl LaTeX code which is inserted before `\documentclass`. +: insert additional LaTeX code which is inserted before `\documentclass`. Useful for passing options to packages or additional packages loading -tex4ht_sty_par +`tex4ht_sty_par` : parameters to `tex4ht.sty` -tex4ht_par +`tex4ht_par` -: parameters to `tex4ht` application +: parameters to the `tex4ht` application -t4ht_par +`t4ht_par` -: parameters to `t4ht` application +: parameters to the `t4ht` application -outdir +`outdir` -: output directory +: the output directory -repetition +`repetition` : limit number of command execution. -correct_exit +`correct_exit` + +: expected `exit code` from the command. The compilation will be terminated + if the command `exit code` is different. -: expected `exit code` from the command. The compilation will be terminted - when the command `exit code` is different. +### Repetition -You may add your own parameters, they will be accessible in templates and -functions. +Repetition is number which specifies a maximal number of executions of the +particular command. This is used for instance for `tex4ht` and `t4ht` +commands, as they should be executed only once in the compilation. They would +be executed multiple times if you include them in the build file because they +are called by `make4ht` by default. Because these commands allow only one +`repetition`, the second execution will be blocked. -With `repetition`, you can limit number of command executions. Its value -should be number or `nil`. This is used in the case of `tex4ht` and `t4ht` -commands, as they should be executed only once and they would be executed -multiple times if you include them in the build file, because they would be -called also by `make4ht`. With `repetition`, second execution is blocked. +### Expected exit code -You can set expected exit code from a command with `correct_exit`. Compilation -is stopped when command returns different exit code. The situation is -different for LaTeX (for all TeX engines and formats, in fact), because it doesn't -differentiate between fatal and non fatal errors, and it returns the same exit code -in all cases. Log parsing is used because of that, error code `1` is returned -in the case of fatal error, `0` is used otherwise. +You can set the expected exit code from a command with a `correct_exit` key in the +parameters table. The compilation will be stopped when the command returns a +different exit code. + +This mechanism isn't used for LaTeX (for all TeX engines and formats, in +fact), because it doesn't differentiate between fatal and non-fatal errors, and +it returns the same exit code in all cases. Log parsing is used because of +that, error code `1` is returned in the case of fatal error, `0` is used +otherwise. The `Make.testlogfile` function can be used in the build file to +detect compilation errors in the TeX log file. ## File matches -Other type of action which can be specified in the build file are -matches which may be called on the generated files: +Another type of action which can be specified in the build file is +`match`. It can be called on the generated files: Make:match("html$", "tidy -m -xml -utf8 -q -i ${filename}") -It tests filenames with `Lua` pattern matching and on matched items will -execute command or function, specified in the second argument. For functions, -two arguments are passed, first one is the current filename, the second one -table with parameters. These parameters are the same as in previous section, -except for `filename`, which contains generated output name. +It tests output file names with `Lua` pattern matching and on matched items will +execute a command or a function, specified in the second argument. Commands may be +specified as strings, the templates will be expanded, `${var_name}` placeholders +will be replaced with corresponding variables from the `parameters` table, +described in the previous subsection. One additional variable is available: +`filename`. It contains the name of the current output file. + +The above example will clean all output `HTML` files using the `tidy` command. + +If function is used instead, it will get two parameters. +The first one is a current filename, the second one +table with parameters. + + ### Filters -Some default match actions which you can use are in the `filter` module. It -contains some functions which ares useful for fixing some `tex4ht` bugs or -shortcomings. +Some default `match` actions which can be used are available from the +`make4ht-filter` module. It contains some functions which are useful for +fixing some `tex4ht` bugs or shortcomings. Example: @@ -179,34 +324,35 @@ Example: Make:htlatex() Make:match("html$",process) - -Filter module is located in `make4ht-filter`. Function is returned, -which is used for building filter chains then. +The `make4ht-filter` module return a function which can be used for the filter +chain building. Multiple filters can be chained, each of them can modify the string +which was modified by the previous filters. The changes are then saved to the +processed file. Built-in filters are: -cleanspan +cleanspan : clean spurious span elements when accented characters are used -cleanspan-nat +cleanspan-nat : alternative clean span filter, provided by Nat Kuhn -fixligatures +fixligatures : decompose ligatures to base characters -hruletohr +hruletohr : `\hrule` commands are translated to series of underscore characters by `tex4ht`, this filter translate these underscores to `<hr>` elements -entites +entites : convert prohibited named entities to numeric entities (currently, only ` `, as it causes validation errors, and `tex4ht` is producing it - sometimes + sometimes) fix-links @@ -214,55 +360,239 @@ fix-links cross-reference commands may produce colons in internal links, which results in validation error. -Function `filter` accepts also function arguments, in this case this function -takes file contents as parameter and modified contents are returned. +mathjaxnode + +: use [mathjax-node-page](https://github.com/pkra/mathjax-node-page/) to + convert from MathML code to HTML + CSS or SVG. See [the available + settings](#mathjaxsettings). + +svg-height + +: some SVG images produced by `dvisvgm` seem to have wrong dimensions. This filter + tries to set the correct image size. + +Function `filter` accepts also function arguments, in this case this function +takes file contents as a parameter and modified contents are returned. Example: - local filter = require "make4ht-filter" + local filter = require "make4ht-filter" local changea = function(s) return s:gsub("a","z") end - local process = filter{"cleanspan", "fixligatures", changea} - Make:htlatex() + local process = filter{"cleanspan", "fixligatures", changea} Make:htlatex() - Make:match("html$",process) + Make:htlatex() + Make:match("html$",process) -In this case, spurious span elements are joined, ligatures are decomposed, +In this example, spurious span elements are joined, ligatures are decomposed, and then all letters 'a' are replaced with 'z' letters. +### DOM filters + +DOM filters use the [`LuaXML`](https://ctan.org/pkg/luaxml) library to modify +directly the XML object. This enables more powerful +operations than the regex based filters from the previous section. + +Example: + + local domfilter = require "make4ht-domfilter" + local process = domfilter {"joincharacters"} + Make:match("html$", process) + + +Available DOM filters: + +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 the synchronization. + +fixinlines + +: put all inline elements which are direct children of the `<body>` elements to a paragraph. + +idcolons + +: replace the colon (`:`) character in internal links and `id` attributes. They cause validation issues. + +joincharacters + +: join consecutive `<span>` or `<mn>` elements. + +### make4ht-aeneas-config package + +Companion for the `aeneas` DOM filter is the `make4ht-aeneas-config` plugin. It +can be used to write Aeneas configuration file or execute Aeneas on the +generated HTML files. + +Available functions: + +write\_job(parameters) + +: write Aenas job configuration to `config.xml` file. See the [Aeneas + documentation](https://www.readbeyond.it/aeneas/docs/clitutorial.html#processing-jobs) + for more information about jobs. + +execute(parameters) + +: execute Aeneas. + +process\_files(parameters) + +: process the audio and generated subtitle files. + + +By default, the `smil` file is created. It is assumed that there is audio file +in `mp3` format named as the TeX file. It is possible to use different formats +and file names using mapping. + +The configuration options can be passed directly to the functions or set using +`filter_settings "aeneas-config" {parameters}` function. + + +Available parameters: + + +lang + +: document language. It is interfered from the HTML file, so it is not necessary to set it. + +map + +: mapping between HTML, audio and subtitle files. More info bellow. + +text\_type + +: type of the input. The `aeneas` DOM filter produces `unparsed` text type. + +id\_sort + +: sorting of id attributes. Default value is `numeric`. + +id\_regex + +: regular expression to parse the id attributes. + +sub\_format + +: generated subtitle format. Default `smil`. + + +Additional parameters for the job configuration file: + +- description +- prefix +- config\_name +- keep\_config + + + +It is possible to generate multiple HTML files from the LaTeX source. For +example, `tex4ebook` generates separate file for each chapter or section. It is +possible to set options for each HTML file, in particular names of the +corresponding audio files. This mapping is done using `map` parameter. + +Example: + + filter_settings "aeneas-config" { + map = { + ["sampleli1.html"] = {audio_file="sample.mp3"}, + ["sample.html"] = false + } + } + +Table keys are the configured file names. It is necessary to insert them as +`["filename.html"]`, because of Lua syntax rules. + +This example maps audio file `sample.mp3` to a section subpage. The main HTML +file, which may contain title and table of contents doesn't have an +corresponding audio file. + +The filenames of sub files corresponds to chapter numbers, so they are not +stable when a new chapter is added. It is possible to request file names +interfered from the chapter titles using the `sec-filename` option or `tex4ht`. + +Available `map` options: + + +audio\_file + +: the corresponding audio file + +sub\_file + +: name of the generated subtitle file + +The following options are same as their counter-parts from the main parameters table and generally don't need to be set: + +- prefix +- file\_desc +- file\_id +- text\_type +- id\_sort +- id\_prefix +- sub\_format + + +Full example: + + + local domfilter = require "make4ht-domfilter" + local aeneas_config = require "make4ht-aeneas-config" + + filter_settings "aeneas-config" { + map = { + ["krecekli1.xhtml"] = {audio_file="krecek.mp3"}, + ["krecek.xhtml"] = false + } + } + + local process = domfilter {"aeneas"} + Make:match("html$", process) + + if mode == "draft" then + aeneas_config.process_files {} + else + aeneas_config.execute {} + end + + + + ## Image conversion -It is possible to convert parts of LaTeX input to pictures, it is used +It is possible to convert parts of LaTeX input to pictures, it is used for example for math or diagrams in `tex4ht`. -These pictures are stored in special `dvi` file, on which `dvi to image` -commands are called. +These pictures are stored in a special `dvi` file, which can be processed by +the `dvi to image` commands. -This conversion is normally configured in the `env file`, +This conversion is normally configured in the `env file`, which is system dependent and which has a bit unintuitive syntax. -This configuration is processed by `t4ht` application and conversion +This configuration is processed by the `t4ht` application and conversion commands are called for all pictures. -It is possible to disable `t4ht` image processing and configure image -conversion in the make file: +It is possible to disable `t4ht` image processing and configure image +conversion in the build file: Make:image("png$", - "dvipng -bg Transparent -T tight -o ${output} -pp ${page} ${source}") + "dvipng -bg Transparent -T tight -o ${output} -pp ${page} ${source}") `Make:image` takes two parameters, pattern to match image name and action. -Action can be either string template with conversion command, -or function which takes table with parameters as argument. +Action can be either string template with conversion command, +or function which takes a table with parameters as an argument. There are three parameters: - - output - output image file name - - source - `dvi` file with the pictures - - page - page number of the converted image + - `output` - output image file name + - `source` - `dvi` file with the pictures + - `page` - page number of the converted image ## The `mode` variable -The `mode` variable contains contents of `--mode` command line option. -It can be used to run some commands conditionally. For example: +There is global `mode` variable available in the build file. It contains +contents of the `--mode` command line option. It can be used to run some commands +conditionally. For example: if mode == "draft" then Make:htlatex{} @@ -281,13 +611,163 @@ LaTeX is called only once when `make4ht` is called with `draft` mode: You may want to access to the parameters also outside commands, file matches and image conversion functions. For example, if you want to convert your file to -the `OpenDocument Format`, you can use the following settings, based on `oolatex` +the `OpenDocument Format (ODT)`, you can use the following settings, based on the `oolatex` command: settings.tex4ht_sty_par = settings.tex4ht_sty_par ..",ooffice" settings.tex4ht_par = settings.tex4ht_par .. " ooffice/! -cmozhtf" settings.t4ht_par = settings.t4ht_par .. " -cooxtpipes -coo " +There are some functions to ease access to the settings: + +`set_settings{parameters}` + +: overwrite settings with values from a passed table + +`settings_add{parameters}` + +: add values to the current settings + +`filter_settings "filter name" {parameters}` + +: set settings for a filter + +`get_filter_settings(name)` + +: get settings for a filter + + +Using these functions, it is possible to simplify the settings for the `ODT` format: + + settings_add { + tex4ht_sty_par =",ooffice", + tex4ht_par = " ooffice/! -cmozhtf", + t4ht_par = " -cooxtpipes -coo " + } + +Settings for filters and extensions can be set using `filter_settings`: + + + filter_settings "test" { + hello = "world" + } + +These settings can be read in the extensions and filters using `get_filter_settings`: + + function test(input) + local options = get_filter_settings("test") + print(options.hello) + return input + end + +## List of available settings for filters and extensions. + +These settings may be set using `filter_settings` function. + +### The `tidy` extension + +options + +: command line options for the `tidy` command. Default value is `-m -utf8 -w 512 -q`. + +### The `fixinlines` dom filter + +inline\_elements + +: table of inline elements which shouldn't be direct descendants of the `body` element. The element names should be table keys, the values should be true. + +Example + + filter_settings "fixinlines" {inline_elements = {a = true, b = true}} + +### The `joincharacters` dom filter + +charelements + +: table of elements which should be joined if several instances with the same value of `class` attribute are side by side. + +Example + + filter_settings "joincharacters" { charclases = { span=true, mn = true}} + +### The `mathjaxnode` filter {#mathjaxsettings} + +options + +: command line options for the `mjpage` command. Default value is `--output CommonHTML` + +Example + + filter_settings "mathjaxnode" { + options="--output SVG --font Neo-Euler" + } + +cssfilename + +: `mjpage` puts some CSS code into the HTML pages. `mathjaxnode` extracts this information and saves it to a standalone CSS file. Default CSS filename is `mathjax-chtml.css` + +fontdir + +: directory with MathJax font files. This option enables use of local fonts, which + is usefull in Epub conversion, for example. The font directory should be + sub-directory of the current directory. Only TeX font is supported at the moment. + +Example + + + filter_settings "mathjaxnode" { + fontdir="fonts/TeX/woff/" + } + +### The `aeneas` filter + +skip\_elements + +: List of CSS selectors that match elements which shouldn't be processed. Default value: `{ "math", "svg"}`. + +id\_prefix + +: prefix used in the ID attribute forming. + +sentence\_match + +: Lua pattern used to match a sentence. Default value: `"([^%.^%?^!]*)([%.%?!]?)"`. + +# Configuration file {#configfile} + +It is possible to globally modify the build settings using the configuration +file. New compilation commands can be added, extensions can be loaded or +disabled and settings can be set. + +## Location + +The configuration file can be saved either in +`$HOME/.config/make4ht/config.lua` or in `.make4ht` in the current directory or +it's parents (up to `$HOME`). + +## Additional commands + +There are two additional commands: + +`Make:enable_extension(name)` + +: require extension + +`Make:disable_extension(name)` + +: disable extension + +## Example + +The following configuration add support for the `biber` command, requires +`common_domfilters` extension and requires MathML +output for math. + + Make:add("biber", "biber ${input}") + Make:enable_extension "common_domfilters" + settings_add { + tex4ht_sty_par =",mathml" + } # Command line options @@ -301,6 +781,7 @@ command: -d,--output-dir (default "") Output directory -e,--build-file (default nil) If build file name is different than `filename`.mk4 + -f,--format (default nil) Output file format -l,--lua Use lualatex for document compilation -m,--mode (default default) Switch which can be used in the makefile -n,--no-tex4ht Disable dvi file processing with tex4ht command @@ -311,16 +792,16 @@ command: <filename> (string) Input file name -You can still use `make4ht` in same way as `htlatex` +You can still invoke `make4ht` in the same way as `htlatex`: - make4ht filename "customcfg, charset=utf-8" " -cunihtf -utf8" " -dfoo" + make4ht filename "customcfg, charset=utf-8" "-cunihtf -utf8" "-dfoo" -Note that this will not use `make4ht` routines for output dir making and -copying If you want to use them, change the line above to: +Note that this will not use `make4ht` routines for output directory making and +copying. If you want to use them, change the line above to: - make4ht filename "customcfg, charset=utf-8" " -cunihtf -utf8" -d foo + make4ht -d foo filename "customcfg, charset=utf-8" "-cunihtf -utf8" -This call has the same effect as following: +This call has the same effect as the following: make4ht -u -c customcfg -d foo filename @@ -332,4 +813,32 @@ Specified path can be relative to current directory, or absolute: make4ht -d ~/gotohomedir filename make4ht -d c:\documents\windowspathsareworkingtoo filename +# Troubleshooting +## Incorrect handling of command line arguments for `tex4ht`, `t4ht` or `latex` + +Sometimes, you may get a similar error: + + make4ht:unrecognized parameter: i + +It may be caused by a following `make4ht` invocation: + + make4ht hello.tex "customcfg,charset=utf-8" "-cunihtf -utf8" -d foo + +The command line option parser is confused by mixing options for `make4ht` and +`tex4ht` in this case and tries to interpret the `-cunihtf -utf8`, which are +options for `tex4ht` command as `make4ht` options. To fix that, you can either +move the `-d foo` directly after `make4ht` command: + + make4ht -d foo hello.tex "customcfg,charset=utf-8" "-cunihtf -utf8" + +Another option is to add a space before `tex4ht` options: + + make4ht hello.tex "customcfg,charset=utf-8" " -cunihtf -utf8" -d foo + +The former way is preferable, though. + +# License + +Permission is granted to copy, distribute and/or modify this software +under the terms of the LaTeX Project Public License, version 1.3. diff --git a/Master/texmf-dist/doc/support/make4ht/changelog.tex b/Master/texmf-dist/doc/support/make4ht/changelog.tex index 752787d1594..26614e59491 100644 --- a/Master/texmf-dist/doc/support/make4ht/changelog.tex +++ b/Master/texmf-dist/doc/support/make4ht/changelog.tex @@ -1,11 +1,253 @@ -\section{Changelog}\label{changelog} +\hypertarget{changelog}{% +\section{Changelog}\label{changelog}} \begin{itemize} \item + 2018/04/09 + + \begin{itemize} + \tightlist + \item + released version \texttt{0.2} + \item + disabled default loading of \texttt{common\_domfilters} extension + \end{itemize} +\item + 2018/04/06 + + \begin{itemize} + \tightlist + \item + added \texttt{Make:enable\_extension} and + \texttt{Make:disable\_extension} functions + \item + documented the configuration file + \end{itemize} +\item + 2018/03/09 + + \begin{itemize} + \tightlist + \item + load the configuration file before extensions + \end{itemize} +\item + 2018/03/02 + + \begin{itemize} + \tightlist + \item + Aeneas execution works + \item + Aeneas documentation + \item + added support for \texttt{.make4ht} configuration file + \end{itemize} +\item + 2018/02/28 + + \begin{itemize} + \tightlist + \item + Aeneas configuration file creation works + \end{itemize} +\item + 2018/02/22 + + \begin{itemize} + \tightlist + \item + fixed bug in \texttt{fixinlines} DOM filter + \end{itemize} +\item + 2018/02/21 + + \begin{itemize} + \tightlist + \item + added Aeneas domfilter + \item + fixed bugs in \texttt{joincharacters} DOM filter + \end{itemize} +\item + 2018/02/20 + + \begin{itemize} + \tightlist + \item + fixed bug in \texttt{joincharacters} DOM filter + \item + make \texttt{woff} default font format for \texttt{mathjaxnode} + \item + added documentation for \texttt{mathjaxnode} settings + \end{itemize} +\item + 2018/02/19 + + \begin{itemize} + \tightlist + \item + fixed bug in filter loading + \item + added \texttt{mathjaxnode} extension + \end{itemize} +\item + 2018/02/15 + + \begin{itemize} + \tightlist + \item + use HTML5 as a default format + \item + use \texttt{common\_domfilters} implicitly for the XHTML and HTML5 + formats + \end{itemize} +\item + 2018/02/12 + + \begin{itemize} + \tightlist + \item + added \texttt{common\_domfilters} extension + \item + documented DOM filters + \end{itemize} +\item + 2018/02/12 + + \begin{itemize} + \tightlist + \item + handle XML parsing errors in the DOM handler + \item + enable extension loading in Formatters + \end{itemize} +\item + 2018/02/11 + + \begin{itemize} + \tightlist + \item + fixed Tidy extension output to support LuaXML + \item + fixed white space issues with \texttt{joincharacters} DOM filter + \end{itemize} +\item + 2018/02/09 + + \begin{itemize} + \tightlist + \item + fixed issues with the Mathjax filter + \item + documented basic info about thd DOM filters + \item + DOM filter optimalizations + \end{itemize} +\item + 2018/02/08 + + \begin{itemize} + \tightlist + \item + make Tidy extension configurable + \item + documented filter settings + \end{itemize} +\item + 2018/02/07 + + \begin{itemize} + \tightlist + \item + added filter for Mathjax-node + \end{itemize} +\item + 2018/02/06 + + \begin{itemize} + \tightlist + \item + created DOM filter function + \item + added DOM filter for spurious inlinine elements + \end{itemize} +\item + 2018/02/03 + + \begin{itemize} + \tightlist + \item + added settings handling functions + \item + settings made available for extensions and filters + \end{itemize} +\item + 2017/12/08 + + \begin{itemize} + \tightlist + \item + fixed the \texttt{mk4} build file loading when it is placed in the + current working dir and another one with same filename somewhere in + the TEXMF tree. + \end{itemize} +\item + 2017/11/10 + + \begin{itemize} + \tightlist + \item + Added new filter: \texttt{svg-height}. It tries to fix height of + some of the images produced by \texttt{dvisvgm} + \end{itemize} +\item + 2017/10/06 + + \begin{itemize} + \tightlist + \item + Added support for output format selection. Supported formats are + \texttt{xhtml}, \texttt{html5} and \texttt{odt} + \item + Added support for extensions + \end{itemize} +\item + 2017/09/10 + + \begin{itemize} + \tightlist + \item + Added support for Latexmk + \item + Added support of \texttt{math} library and \texttt{tonumber} + function in the build files + \end{itemize} +\item + 2017/09/04 + + \begin{itemize} + \tightlist + \item + fixed bug caused by the previous change -- the --help and --version + didn't work + \end{itemize} +\item + 2017/08/22 + + \begin{itemize} + \tightlist + \item + fixed the command line option parsing for \texttt{tex4ht}, + \texttt{t4ht} and \texttt{latex} commands + \item + various grammar and factual fixes in the documentation + \end{itemize} +\item 2017/04/26 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item Released version \texttt{v0.1c} \end{itemize} @@ -13,15 +255,15 @@ 2017/03/16 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item - check for \texttt{TeX capacity exceeded} error in the \LaTeX~run. + check for \texttt{TeX\ capacity\ exceeded} error in the \LaTeX~run. \end{itemize} \item 2016/12/19 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item use full input name in \texttt{tex\_file} variable. This should enable use of files without \texttt{.tex} extension. @@ -30,7 +272,7 @@ 2016/10/22 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item new command available in the build file: \texttt{Make:add\_file(filename)}. This enables filters and commands @@ -43,7 +285,7 @@ 2016/10/18 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item new filter: replace colons in \texttt{id} and \texttt{href} attributes with underscores @@ -52,7 +294,7 @@ 2016/01/11 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item fixed bug in loading documents with full path specified \end{itemize} @@ -60,18 +302,18 @@ 2015/12/06 version 0.1b \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item - modifed lapp library to recognize \texttt{-{}-version} and + modifed lapp library to recognize \texttt{-\/-version} and \item - added \texttt{-{}-help} and \texttt{-{}-version} command line + added \texttt{-\/-help} and \texttt{-\/-version} command line options \end{itemize} \item 2015/11/30 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item use \texttt{kpse} library for build file locating \end{itemize} @@ -79,7 +321,7 @@ 2015/11/17 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item better \texttt{-jobname} handling \end{itemize} @@ -87,18 +329,18 @@ 2015/09/23 version 0.1a \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item various documentation updates + \item + \texttt{mozhtf} profile for unicode output is used, this should + prevent ligatures in the output files \end{itemize} \item - \texttt{mozhtf} profile for unicode output is used, this should - prevent ligatures in the output files -\item 2015/06/29 version 0.1 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item major README file update \end{itemize} @@ -106,7 +348,7 @@ 2015/06/26 \begin{itemize} - \itemsep1pt\parskip0pt\parsep0pt + \tightlist \item added Makefile \item diff --git a/Master/texmf-dist/doc/support/make4ht/extensions/common_domfilters.lua b/Master/texmf-dist/doc/support/make4ht/extensions/common_domfilters.lua new file mode 100644 index 00000000000..7c9a2dfaea4 --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/extensions/common_domfilters.lua @@ -0,0 +1,27 @@ +local M = {} + + +local filter = require "make4ht-domfilter" +-- local process = filter {"fixinlines", "idcolons", "joincharacters" } + +-- filters support only html formats +function M.test(format) + if format == "odt" then return false end + return true +end + +function M.modify_build(make) + local process = filter {"fixinlines", "idcolons", "joincharacters"} + make:match("html$", process) + local matches = make.matches + -- the filters should be first match to be executed, especially if tidy + -- should be executed as well + if #matches > 1 then + local last = matches[#matches] + table.insert(matches, 1, last) + matches[#matches] = nil + end + return make +end + +return M diff --git a/Master/texmf-dist/doc/support/make4ht/extensions/common_filters.lua b/Master/texmf-dist/doc/support/make4ht/extensions/common_filters.lua new file mode 100644 index 00000000000..513fbed34ee --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/extensions/common_filters.lua @@ -0,0 +1,26 @@ +local M = {} + + +local filter = require "make4ht-filter" +local process = filter {"cleanspan-nat", "fixligatures", "hruletohr", "entities", "fix-links"} + +-- filters support only html formats +function M.test(format) + if format == "odt" then return false end + return true +end + +function M.modify_build(make) + make:match("html$", process) + local matches = make.matches + -- the filters should be first match to be executed, especially if tidy + -- should be executed as well + if #matches > 1 then + local last = matches[#matches] + table.insert(matches, 1, last) + matches[#matches] = nil + end + return make +end + +return M diff --git a/Master/texmf-dist/doc/support/make4ht/extensions/latexmk.lua b/Master/texmf-dist/doc/support/make4ht/extensions/latexmk.lua new file mode 100644 index 00000000000..bf4dcdde5a4 --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/extensions/latexmk.lua @@ -0,0 +1,31 @@ +-- use Latexmk in first LaTeX call +-- only in the first call, because we don't need to execute biber, etc. in the subsequent +-- LaTeX calls, these are only for resolving the cross-references +local M = {} +function M.modify_build(make) + local used = false + local first + local build_seq = make.build_seq + -- find first htlatex call in the build sequence + for pos,v in ipairs(build_seq) do + if v.name == "htlatex" and not first then + first = pos + end + end + -- if htlatex was found + if first then + -- add dummy latexmk call to the build sequence + make:latexmk {} + -- replace name, command and type in the first htlatex + -- call with values from the dummy latexmk call + local replaced = build_seq[first] + local latexmk = build_seq[#build_seq] + replaced.name = latexmk.name + replaced.command = latexmk.command + replaced.type = latexmk.type + -- remove the dummy latexmk + table.remove(build_seq) + end + return make +end +return M diff --git a/Master/texmf-dist/doc/support/make4ht/extensions/mathjaxnode.lua b/Master/texmf-dist/doc/support/make4ht/extensions/mathjaxnode.lua new file mode 100644 index 00000000000..cbad897d4ec --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/extensions/mathjaxnode.lua @@ -0,0 +1,16 @@ +local M = {} + + +local filter = require "make4ht-filter" +function M.test(format) + if format == "odt" then return false end + return true +end + +function M.modify_build(make) + local mathjax = filter { "mathjaxnode"} + make:match("html$",mathjax) + return make +end + +return M diff --git a/Master/texmf-dist/doc/support/make4ht/extensions/tidy.lua b/Master/texmf-dist/doc/support/make4ht/extensions/tidy.lua new file mode 100644 index 00000000000..cf8ea9785a5 --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/extensions/tidy.lua @@ -0,0 +1,57 @@ +local M = {} + +function M.test(format) + if format == "odt" then return false end + return true +end + +local empty_elements = { + area=true, + base=true, + br=true, + col=true, + embed=true, + hr=true, + img=true, + input=true, + keygen=true, + link=true, + meta=true, + param=true, + source=true, + track=true, + wbr=true, +} + +-- LuaXML cannot read HTML with unclosed tags (like <meta name="hello" content="world">) +-- Tidy removes end slashes in the HTML output, so +-- this function will add them back +local function close_tags(s) + return s:gsub("<(%w+)([^>]-)>", function(tag, rest) + local endslash = "" + if empty_elements[tag] then endslash = " /" end + return string.format("<%s%s%s>", tag, rest, endslash) + end) +end + + + +function M.modify_build(make) + make:match("html$", function(filename, par) + local settings = get_filter_settings "tidy" or {} + par.options = par.options or settings.options or "-utf8 -w 512 -ashtml -q" + local command = "tidy ${options} ${filename}" % par + print("execute: ".. command) + -- os.execute(command) + local run = io.popen(command, "r") + local result = run:read("*all") + run:close() + result = close_tags(result) + local f = io.open(filename, "w") + f:write(result) + f:close() + end) + return make +end + +return M diff --git a/Master/texmf-dist/doc/support/make4ht/formats/html5.lua b/Master/texmf-dist/doc/support/make4ht/formats/html5.lua new file mode 100644 index 00000000000..633a45e7f34 --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/formats/html5.lua @@ -0,0 +1,17 @@ +local M = {} + +local mkutils = require "mkutils" + +function M.prepare_extensions(extensions) + -- return mkutils.add_extensions("+common_domfilters", extensions) + return extensions --mkutils.add_extensions("+tidy", extensions) +end + +function M.prepare_parameters(parameters,extensions) + parameters.tex4ht_sty_par = parameters.tex4ht_sty_par .. ",html5" + parameters = mkutils.extensions_prepare_parameters(extensions,parameters) + return parameters +end + + +return M diff --git a/Master/texmf-dist/doc/support/make4ht/formats/odt.lua b/Master/texmf-dist/doc/support/make4ht/formats/odt.lua new file mode 100644 index 00000000000..66105166f32 --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/formats/odt.lua @@ -0,0 +1,11 @@ +local M = {} +local mkutils = require "mkutils" + +function M.prepare_parameters(settings, extensions) + settings.tex4ht_sty_par = settings.tex4ht_sty_par ..",ooffice" + settings.tex4ht_par = settings.tex4ht_par .. " ooffice/! -cmozhtf" + settings.t4ht_par = settings.t4ht_par .. " -cooxtpipes -coo " + settings = mkutils.extensions_prepare_parameters(extensions, settings) + return settings +end +return M diff --git a/Master/texmf-dist/doc/support/make4ht/formats/xhtml.lua b/Master/texmf-dist/doc/support/make4ht/formats/xhtml.lua new file mode 100644 index 00000000000..4a4ac6689ca --- /dev/null +++ b/Master/texmf-dist/doc/support/make4ht/formats/xhtml.lua @@ -0,0 +1,16 @@ +local M = {} + +local mkutils = require "mkutils" + +function M.prepare_extensions(extensions) + -- return mkutils.add_extensions("+common_domfilters", extensions) + return extensions +end + +function M.prepare_parameters(parameters,extensions) + parameters = mkutils.extensions_prepare_parameters(extensions,parameters) + return parameters +end + + +return M diff --git a/Master/texmf-dist/doc/support/make4ht/make4ht-doc.pdf b/Master/texmf-dist/doc/support/make4ht/make4ht-doc.pdf Binary files differindex 2867bf29dc0..fa9039865ce 100644 --- a/Master/texmf-dist/doc/support/make4ht/make4ht-doc.pdf +++ b/Master/texmf-dist/doc/support/make4ht/make4ht-doc.pdf diff --git a/Master/texmf-dist/doc/support/make4ht/make4ht-doc.tex b/Master/texmf-dist/doc/support/make4ht/make4ht-doc.tex index 973a349c6ce..6815e50606f 100644 --- a/Master/texmf-dist/doc/support/make4ht/make4ht-doc.tex +++ b/Master/texmf-dist/doc/support/make4ht/make4ht-doc.tex @@ -9,6 +9,7 @@ \else \usepackage{fontspec} \setmainfont{TeX Gyre Schola} +\setmonofont[Scale=MatchLowercase]{Inconsolatazi4} \fi \usepackage{microtype} \providecommand\tightlist{\relax} diff --git a/Master/texmf-dist/doc/support/make4ht/readme.tex b/Master/texmf-dist/doc/support/make4ht/readme.tex index 77e1ecdc6b1..df67a151b87 100644 --- a/Master/texmf-dist/doc/support/make4ht/readme.tex +++ b/Master/texmf-dist/doc/support/make4ht/readme.tex @@ -1,68 +1,186 @@ -\section{Introduction}\label{introduction} +\hypertarget{introduction}{% +\section{Introduction}\label{introduction}} + +\texttt{make4ht} is a simple build system for \texttt{tex4ht}, \TeX~to +XML converter. It provides a command line tool that drive the conversion +process. It also provides a library which can be used to create +customized conversion tools. An example of such conversion tool is +\href{https://github.com/michal-h21/tex4ebook}{tex4ebook} for conversion +of \TeX~to ePub and other e-book formats. + +\hypertarget{how-it-works}{% +\subsection{How it works}\label{how-it-works}} + +\hypertarget{the-issues-with-default-tex4ht-conversion-commands}{% +\subsubsection{\texorpdfstring{The issues with default \texttt{tex4ht} +conversion +commands}{The issues with default tex4ht conversion commands}}\label{the-issues-with-default-tex4ht-conversion-commands}} + +\texttt{tex4ht} system supports several output formats, most notably +\texttt{XHTML}, \texttt{HTML\ 5} and \texttt{ODT}. The conversion can be +invoked using several commands. These commands invoke LaTeX~or Plain TeX +with special instructions to load \texttt{tex4ht.sty} package. The +\TeX~run produces special \texttt{DVI} file which contains the code for +desired output format. The \texttt{DVI} file is then processed and +desired output files are created. + +The basic command provided by \texttt{tex4ht} is named \texttt{htlatex}. +It compiles \LaTeX~ files to \texttt{HTML} with this command sequence: -\texttt{make4ht} is a simple build system for tex4ht. It is both -executable which simplifies \texttt{tex4ht} execution and a library -which can be used to create customized conversion programs. An example -of such conversion program is -\href{https://github.com/michal-h21/tex4ebook}{tex4ebook} +\begin{verbatim} +latex $latex_options 'code for loading tex4ht.sty \input{filename}' +latex $latex_options 'code for loading tex4ht.sty \input{filename}' +latex $latex_options 'code for loading tex4ht.sty \input{filename}' +tex4ht $tex4ht_options filename +t4ht $t4ht_options filename +\end{verbatim} -\subsection{License}\label{license} +The options for various parts of the system can be passed on the command +line: -Permission is granted to copy, distribute and/or modify this software -under the terms of the LaTeX Project Public License, version 1.3. +\begin{verbatim} +htlatex filename "tex4ht.sty options" "tex4ht_options" "t4ht_options" "latex_options" +\end{verbatim} -\subsection{How it works}\label{how-it-works} +For basic \texttt{HTML} conversion it is possible to use the most basic +invocation: -Default compilation script for \texttt{tex4ht}, named \texttt{htlatex} -compiles LaTeX files to \texttt{HTML} with this command sequence: +\begin{verbatim} +htlatex filename.tex +\end{verbatim} + +It can be much more involved for \texttt{HTML\ 5} output in +\texttt{UTF-8} encoding: \begin{verbatim} -latex $latex_options 'code for loading tex4ht.sty \input{filename}' -latex $latex_options 'code for loading tex4ht.sty \input{filename}' -latex $latex_options 'code for loading tex4ht.sty \input{filename}' -tex4ht options filename -t4ht options filename -\end{verbatim} - -The problem is that this is inefficient when you need to run program -which interact with LaTeX, such as \texttt{Makeindex} or -\texttt{Bibtex}. In that case, you need to create new script based on -the default one, or run \texttt{htlatex} twice, which means six LaTeX -runs. - -Another problem is with \texttt{t4ht} application. It reads file -\texttt{filename.lg}, generated by \texttt{tex4ht}, where are -instructions about generated files, \texttt{CSS} instructions, calls to -external applications, instructions for image conversions etc. It can be -instructed to copy generated files to some output directory, but it -doesn't preserve directory structure, so when you have images in some -subdirectory, they will be copied to the output directory, but links -will be pointing to non existing subdirectory. - -Image conversion is directed with the +htlatex filename.tex "xhtml,html5,charset=utf-8" "-cmozhtf -utf8" +\end{verbatim} + +\texttt{make4ht} can simplify it: + +\begin{verbatim} +make4ht -uf html5 filename.tex +\end{verbatim} + +Another issue is the fixed compilation order and hard-coded number of +LaTeX invocations. + +When you need to run a program which interact with LaTeX, such as +\texttt{Makeindex} or \texttt{Bibtex}, you need to create a new script +based on \texttt{htlatex}, or run \texttt{htlatex} twice, which means +that LaTeX will be invoked six times. This can lead to significantly +long compilation times. \texttt{make4ht} provides build files and +extensions, which can be used for interaction with external tools. + +It is also possible to have several compilation modes. When you just add +new text to a document, which doesn't contain cross-references, don't +add new stuff to the table of contents, etc., it is possible to use the +\texttt{draft} mode which will invoke LaTeX only once. It can save quite +a lot of the compilation time: + +\begin{verbatim} +make4ht -um draft -f html5 filename.tex +\end{verbatim} + +There are also issues with a behaviour of the \texttt{t4ht} application. +It reads file \texttt{filename.lg}, generated by \texttt{tex4ht}, where +are instructions about generated files, \texttt{CSS} instructions, calls +to external applications, instructions for image conversions etc. It can +be instructed to copy generated files to some output directory, but it +doesn't preserve directory structure, so when you have images in a +subdirectory, they will be copied to the output directory. Links will be +pointing to a non-existing subdirectory. The following command should +copy all output files to the correct destinations. + +\begin{verbatim} +make4ht -d outputdir filename.tex +\end{verbatim} + +The image conversion is configured in the \href{http://www.tug.org/applications/tex4ht/mn35.html\#index35-73001}{env -file}, with really strange syntax based on whitespace and +file}, which has really strange syntax based and the rules are \href{http://www.tug.org/applications/tex4ht/mn-unix.html\#index27-69005}{os -dependent}. With \texttt{make4ht} build files, we have simple mean to -fix these issues. We can change image conversion parameters without need -to modify the \texttt{env file}, and call actions on the output files. +dependent}. \texttt{make4ht} provides simpler means for the image +conversion in the build files. + +With \texttt{make4ht} build files, we have simple mean to fix these +issues. We can change image conversion parameters without the need to +modify the \texttt{env\ file}, or execute actions on the output files. These actions can be either external programs such as \texttt{xslt} -processors or \texttt{HTML tidy} or \texttt{Lua} functions. +processors or \texttt{HTML\ tidy} or \texttt{Lua} functions. The idea is to make system controlled by a build file. Because \texttt{Lua} interpreter is included in modern TeX distributions and \texttt{Lua} is ideal language for such task, it was chosen as language -in which build script are written. +in which the build scripts are written. + +\hypertarget{output-file-formats-and-extensions}{% +\section{Output file formats and +extensions}\label{output-file-formats-and-extensions}} + +The default output format used by \texttt{make4ht} is \texttt{html5}. +Different format can be requested using the \texttt{-\/-format} option. +Supported formats are: + +\begin{itemize} +\tightlist +\item + \texttt{xhtml} +\item + \texttt{html5} +\item + \texttt{odt} +\end{itemize} + +The \texttt{-\/-format} option can be also used for extension loading. + +\hypertarget{extensions}{% +\subsection{Extensions}\label{extensions}} -\section{Build files}\label{build-files} +Extensions can be used to modify the build process without the need to +use a build file. They may post-process the output files or request +additional commands for the compilation. -\subsection{Commands}\label{commands} +The extensions can be enabled or disabled by appending +\texttt{+EXTENSION} or \texttt{-EXTENSION} after the output format name: -By default, build file is saved in file named -\texttt{filename + .mk4 extension}. You can choose different build file -with \texttt{-e} or \texttt{-{}-build-file} command line option. +\begin{verbatim} + make4ht -uf html5+tidy filename.tex +\end{verbatim} + +Available extensions: + +\begin{description} +\item[latexmk] +use \texttt{Latexmk} for \LaTeX~compilation. +\item[tidy] +clean the \texttt{HTML} files using the \texttt{tidy} command. +\item[common\_filters] +clean the output HTML files using filters. +\item[common\_domfilters] +Clean the HTML file using DOM filters. It is more powerful than +\texttt{common\_filters}. Used DOM filters are \texttt{fixinlines}, +\texttt{idcolons} and \texttt{joincharacters}. +\item[mathjaxnode] +use \href{https://github.com/pkra/mathjax-node-page/}{mathjax-node-page} +to convert from MathML code to HTML + CSS or SVG. See +\protect\hyperlink{mathjaxsettings}{the available settings}. +\end{description} + +\hypertarget{build-files}{% +\section{Build files}\label{build-files}} -Sample: +\texttt{make4ht} supports build files. These are \texttt{Lua} scripts +which can adjust the build process. You can request external +applications like \texttt{bibtex} or \texttt{makeindex}, pass options to +the commands, modify the image conversion process, or post-process the +generated files. + +\texttt{make4ht} tries to load default build file named as +\texttt{filename\ +\ .mk4\ extension}. You can choose different build +file with \texttt{-e} or \texttt{-\/-build-file} command line option. + +Sample build file: \begin{verbatim} Make:htlatex() @@ -70,107 +188,166 @@ Make:match("html$", "tidy -m -xml -utf8 -q -i ${filename}") \end{verbatim} \texttt{Make:htlatex()} is preconfigured command for calling LaTeX with -\texttt{tex4ht} loaded on the input file. In this case it will be called -one time. After compilation, \texttt{tidy} command is executed on the -output \texttt{html} file. +\texttt{tex4ht} loaded on the input file. In this case, it will be +called one time. After compilation, the \texttt{tidy} command is +executed on the output \texttt{HTML} file. Note that you don't have to call \texttt{tex4ht} and \texttt{t4ht} commands explicitly in the build file, they are called automatically. -You can add more commands like \texttt{Make:htlatex} with +\hypertarget{user-commands}{% +\subsection{User commands}\label{user-commands}} + +You can add more commands like \texttt{Make:htlatex} using +\texttt{Make:add} command: \begin{verbatim} Make:add("name", "command", {parameters}, repetition) \end{verbatim} -it can be called with +The \texttt{name} and \texttt{command} parameters are required, rest of +the parameters are optional. -\begin{verbatim} -Make:name() -\end{verbatim} +This defines \texttt{name} command, which can be then executed as +\texttt{Make:name()} command. -\texttt{command} can be text template, or function: +\hypertarget{provided-commands}{% +\subsubsection{Provided commands}\label{provided-commands}} + +\begin{description} +\item[\texttt{Make:htlatex}] +One call to TeX engine with special configuration for \texttt{tex4ht} +loading. +\item[\texttt{Make:latexmk}] +Use \texttt{Latexmk} for the document compilation. \texttt{tex4ht} will +be loaded automatically. +\item[\texttt{Make:tex4ht}] +Process the \texttt{DVI} file and creates the output files. +\item[\texttt{Make:t4ht}] +Creates the CSS file. +\end{description} + +\hypertarget{command-function}{% +\subsubsection{Command function}\label{command-function}} + +The \texttt{command} parameter can be either string template or +function: \begin{verbatim} -Make:add("text", "hello, input file: ${input}") +Make:add("text", "echo hello, input file: ${input}") Make:add("function", function(params) for k, v in pairs(params) do print(k..": "..v) - end + end, {custom="Hello world"} ) \end{verbatim} -\texttt{parameters} is a table or \texttt{nil} value. +The template can get variable value from the parameters table using a +\texttt{\$\{var\_name\}} placeholder. Templates are executed using +operating system, so they should invoke existing OS commands. Function +commands may execute system commands using \texttt{os.execute} function. + +\hypertarget{parameters-table}{% +\subsubsection{Parameters table}\label{parameters-table}} -Default parameters are: +\texttt{parameters} parameter is optional, it can be table or +\texttt{nil} value, which should be used if you want to use the +\texttt{repetition} parameter, but don't want to modify the parameters +table. + +The table with default parameters is passed to all commands, they can be +accessed from command functions or templates. When you specify your own +parameters in the command definition, these additional parameters are +added to the default parameters table for this particular command. You +can override the default parameters in the parameters table. + +The default parameters are following: \begin{description} -\item[htlatex] +\item[\texttt{htlatex}] used compiler -\item[input] +\item[\texttt{input}] it is output file name in fact -\item[tex\_file] +\item[\texttt{tex\_file}] input TeX file -\item[latex\_par] +\item[\texttt{latex\_par}] parameters to \texttt{latex} -\item[packages] -insert additionl LaTeX code which is inserted before +\item[\texttt{packages}] +insert additional LaTeX code which is inserted before \texttt{\textbackslash{}documentclass}. Useful for passing options to packages or additional packages loading -\item[tex4ht\_sty\_par] +\item[\texttt{tex4ht\_sty\_par}] parameters to \texttt{tex4ht.sty} -\item[tex4ht\_par] -parameters to \texttt{tex4ht} application -\item[t4ht\_par] -parameters to \texttt{t4ht} application -\item[outdir] -output directory -\item[repetition] +\item[\texttt{tex4ht\_par}] +parameters to the \texttt{tex4ht} application +\item[\texttt{t4ht\_par}] +parameters to the \texttt{t4ht} application +\item[\texttt{outdir}] +the output directory +\item[\texttt{repetition}] limit number of command execution. -\item[correct\_exit] -expected \texttt{exit code} from the command. The compilation will be -terminted when the command \texttt{exit code} is different. +\item[\texttt{correct\_exit}] +expected \texttt{exit\ code} from the command. The compilation will be +terminated if the command \texttt{exit\ code} is different. \end{description} -You may add your own parameters, they will be accessible in templates -and functions. +\hypertarget{repetition}{% +\subsubsection{Repetition}\label{repetition}} + +Repetition is number which specifies a maximal number of executions of +the particular command. This is used for instance for \texttt{tex4ht} +and \texttt{t4ht} commands, as they should be executed only once in the +compilation. They would be executed multiple times if you include them +in the build file because they are called by \texttt{make4ht} by +default. Because these commands allow only one \texttt{repetition}, the +second execution will be blocked. -With \texttt{repetition}, you can limit number of command executions. -Its value should be number or \texttt{nil}. This is used in the case of -\texttt{tex4ht} and \texttt{t4ht} commands, as they should be executed -only once and they would be executed multiple times if you include them -in the build file, because they would be called also by -\texttt{make4ht}. With \texttt{repetition}, second execution is blocked. +\hypertarget{expected-exit-code}{% +\subsubsection{Expected exit code}\label{expected-exit-code}} -You can set expected exit code from a command with -\texttt{correct\_exit}. Compilation is stopped when command returns -different exit code. The situation is different for LaTeX (for all TeX -engines and formats, in fact), because it doesn't differentiate between -fatal and non fatal errors, and it returns the same exit code in all -cases. Log parsing is used because of that, error code \texttt{1} is -returned in the case of fatal error, \texttt{0} is used otherwise. +You can set the expected exit code from a command with a +\texttt{correct\_exit} key in the parameters table. The compilation will +be stopped when the command returns a different exit code. -\subsection{File matches}\label{file-matches} +This mechanism isn't used for LaTeX (for all TeX engines and formats, in +fact), because it doesn't differentiate between fatal and non-fatal +errors, and it returns the same exit code in all cases. Log parsing is +used because of that, error code \texttt{1} is returned in the case of +fatal error, \texttt{0} is used otherwise. The \texttt{Make.testlogfile} +function can be used in the build file to detect compilation errors in +the TeX log file. -Other type of action which can be specified in the build file are -matches which may be called on the generated files: +\hypertarget{file-matches}{% +\subsection{File matches}\label{file-matches}} + +Another type of action which can be specified in the build file is +\texttt{match}. It can be called on the generated files: \begin{verbatim} Make:match("html$", "tidy -m -xml -utf8 -q -i ${filename}") \end{verbatim} -It tests filenames with \texttt{Lua} pattern matching and on matched -items will execute command or function, specified in the second -argument. For functions, two arguments are passed, first one is the -current filename, the second one table with parameters. These parameters -are the same as in previous section, except for \texttt{filename}, which -contains generated output name. +It tests output file names with \texttt{Lua} pattern matching and on +matched items will execute a command or a function, specified in the +second argument. Commands may be specified as strings, the templates +will be expanded, \texttt{\$\{var\_name\}} placeholders will be replaced +with corresponding variables from the \texttt{parameters} table, +described in the previous subsection. One additional variable is +available: \texttt{filename}. It contains the name of the current output +file. + +The above example will clean all output \texttt{HTML} files using the +\texttt{tidy} command. -\subsubsection{Filters}\label{filters} +If function is used instead, it will get two parameters. The first one +is a current filename, the second one table with parameters. -Some default match actions which you can use are in the \texttt{filter} -module. It contains some functions which ares useful for fixing some -\texttt{tex4ht} bugs or shortcomings. +\hypertarget{filters}{% +\subsubsection{Filters}\label{filters}} + +Some default \texttt{match} actions which can be used are available from +the \texttt{make4ht-filter} module. It contains some functions which are +useful for fixing some \texttt{tex4ht} bugs or shortcomings. Example: @@ -182,8 +359,10 @@ Make:htlatex() Make:match("html$",process) \end{verbatim} -Filter module is located in \texttt{make4ht-filter}. Function is -returned, which is used for building filter chains then. +The \texttt{make4ht-filter} module return a function which can be used +for the filter chain building. Multiple filters can be chained, each of +them can modify the string which was modified by the previous filters. +The changes are then saved to the processed file. Built-in filters are: @@ -201,73 +380,260 @@ underscores to \texttt{\textless{}hr\textgreater{}} elements \item[entites] convert prohibited named entities to numeric entities (currently, only \texttt{\ }, as it causes validation errors, and \texttt{tex4ht} is -producing it sometimes +producing it sometimes) \item[fix-links] replace colons in local links and \texttt{id} attributes with underscores. Some cross-reference commands may produce colons in internal links, which results in validation error. +\item[mathjaxnode] +use \href{https://github.com/pkra/mathjax-node-page/}{mathjax-node-page} +to convert from MathML code to HTML + CSS or SVG. See +\protect\hyperlink{mathjaxsettings}{the available settings}. +\item[svg-height] +some SVG images produced by \texttt{dvisvgm} seem to have wrong +dimensions. This filter tries to set the correct image size. \end{description} Function \texttt{filter} accepts also function arguments, in this case -this function takes file contents as parameter and modified contents are -returned. +this function takes file contents as a parameter and modified contents +are returned. Example: \begin{verbatim} -local filter = require "make4ht-filter" +local filter = require "make4ht-filter" local changea = function(s) return s:gsub("a","z") end -local process = filter{"cleanspan", "fixligatures", changea} -Make:htlatex() +local process = filter{"cleanspan", "fixligatures", changea} +Make:htlatex() Make:htlatex() -Make:match("html$",process) +Make:match("html$",process) \end{verbatim} -In this case, spurious span elements are joined, ligatures are +In this example, spurious span elements are joined, ligatures are decomposed, and then all letters `a' are replaced with `z' letters. -\subsection{Image conversion}\label{image-conversion} +\hypertarget{dom-filters}{% +\subsubsection{DOM filters}\label{dom-filters}} + +DOM filters use the \href{https://ctan.org/pkg/luaxml}{\texttt{LuaXML}} +library to modify directly the XML object. This enables more powerful +operations than the regex based filters from the previous section. + +Example: + +\begin{verbatim} +local domfilter = require "make4ht-domfilter" +local process = domfilter {"joincharacters"} +Make:match("html$", process) +\end{verbatim} + +Available DOM filters: + +\begin{description} +\item[aeneas] +\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 the synchronization. +\item[fixinlines] +put all inline elements which are direct children of the +\texttt{\textless{}body\textgreater{}} elements to a paragraph. +\item[idcolons] +replace the colon (\texttt{:}) character in internal links and +\texttt{id} attributes. They cause validation issues. +\item[joincharacters] +join consecutive \texttt{\textless{}span\textgreater{}} or +\texttt{\textless{}mn\textgreater{}} elements. +\end{description} + +\hypertarget{make4ht-aeneas-config-package}{% +\subsubsection{make4ht-aeneas-config +package}\label{make4ht-aeneas-config-package}} + +Companion for the \texttt{aeneas} DOM filter is the +\texttt{make4ht-aeneas-config} plugin. It can be used to write Aeneas +configuration file or execute Aeneas on the generated HTML files. + +Available functions: + +\begin{description} +\item[write\_job(parameters)] +write Aenas job configuration to \texttt{config.xml} file. See the +\href{https://www.readbeyond.it/aeneas/docs/clitutorial.html\#processing-jobs}{Aeneas +documentation} for more information about jobs. +\item[execute(parameters)] +execute Aeneas. +\item[process\_files(parameters)] +process the audio and generated subtitle files. +\end{description} + +By default, the \texttt{smil} file is created. It is assumed that there +is audio file in \texttt{mp3} format named as the TeX file. It is +possible to use different formats and file names using mapping. + +The configuration options can be passed directly to the functions or set +using \texttt{filter\_settings\ "aeneas-config"\ \{parameters\}} +function. + +Available parameters: + +\begin{description} +\item[lang] +document language. It is interfered from the HTML file, so it is not +necessary to set it. +\item[map] +mapping between HTML, audio and subtitle files. More info bellow. +\item[text\_type] +type of the input. The \texttt{aeneas} DOM filter produces +\texttt{unparsed} text type. +\item[id\_sort] +sorting of id attributes. Default value is \texttt{numeric}. +\item[id\_regex] +regular expression to parse the id attributes. +\item[sub\_format] +generated subtitle format. Default \texttt{smil}. +\end{description} + +Additional parameters for the job configuration file: + +\begin{itemize} +\tightlist +\item + description +\item + prefix +\item + config\_name +\item + keep\_config +\end{itemize} + +It is possible to generate multiple HTML files from the LaTeX source. +For example, \texttt{tex4ebook} generates separate file for each chapter +or section. It is possible to set options for each HTML file, in +particular names of the corresponding audio files. This mapping is done +using \texttt{map} parameter. + +Example: + +\begin{verbatim} +filter_settings "aeneas-config" { + map = { + ["sampleli1.html"] = {audio_file="sample.mp3"}, + ["sample.html"] = false + } +} +\end{verbatim} + +Table keys are the configured file names. It is necessary to insert them +as \texttt{{[}"filename.html"{]}}, because of Lua syntax rules. + +This example maps audio file \texttt{sample.mp3} to a section subpage. +The main HTML file, which may contain title and table of contents +doesn't have an corresponding audio file. + +The filenames of sub files corresponds to chapter numbers, so they are +not stable when a new chapter is added. It is possible to request file +names interfered from the chapter titles using the \texttt{sec-filename} +option or \texttt{tex4ht}. + +Available \texttt{map} options: + +\begin{description} +\item[audio\_file] +the corresponding audio file +\item[sub\_file] +name of the generated subtitle file +\end{description} + +The following options are same as their counter-parts from the main +parameters table and generally don't need to be set: + +\begin{itemize} +\tightlist +\item + prefix +\item + file\_desc +\item + file\_id +\item + text\_type +\item + id\_sort +\item + id\_prefix +\item + sub\_format +\end{itemize} + +Full example: + +\begin{verbatim} +local domfilter = require "make4ht-domfilter" +local aeneas_config = require "make4ht-aeneas-config" + +filter_settings "aeneas-config" { + map = { + ["krecekli1.xhtml"] = {audio_file="krecek.mp3"}, + ["krecek.xhtml"] = false + } +} + +local process = domfilter {"aeneas"} +Make:match("html$", process) + +if mode == "draft" then + aeneas_config.process_files {} +else + aeneas_config.execute {} +end +\end{verbatim} + +\hypertarget{image-conversion}{% +\subsection{Image conversion}\label{image-conversion}} It is possible to convert parts of LaTeX input to pictures, it is used for example for math or diagrams in \texttt{tex4ht}. -These pictures are stored in special \texttt{dvi} file, on which -\texttt{dvi to image} commands are called. +These pictures are stored in a special \texttt{dvi} file, which can be +processed by the \texttt{dvi\ to\ image} commands. -This conversion is normally configured in the \texttt{env file}, which +This conversion is normally configured in the \texttt{env\ file}, which is system dependent and which has a bit unintuitive syntax. This -configuration is processed by \texttt{t4ht} application and conversion -commands are called for all pictures. +configuration is processed by the \texttt{t4ht} application and +conversion commands are called for all pictures. It is possible to disable \texttt{t4ht} image processing and configure -image conversion in the make file: +image conversion in the build file: \begin{verbatim} Make:image("png$", -"dvipng -bg Transparent -T tight -o ${output} -pp ${page} ${source}") +"dvipng -bg Transparent -T tight -o ${output} -pp ${page} ${source}") \end{verbatim} \texttt{Make:image} takes two parameters, pattern to match image name and action. Action can be either string template with conversion -command, or function which takes table with parameters as argument. +command, or function which takes a table with parameters as an argument. There are three parameters: \begin{itemize} -\itemsep1pt\parskip0pt\parsep0pt +\tightlist \item - output - output image file name + \texttt{output} - output image file name \item - source - \texttt{dvi} file with the pictures + \texttt{source} - \texttt{dvi} file with the pictures \item - page - page number of the converted image + \texttt{page} - page number of the converted image \end{itemize} -\subsection{The \texttt{mode} variable}\label{the-mode-variable} +\hypertarget{the-mode-variable}{% +\subsection{\texorpdfstring{The \texttt{mode} +variable}{The mode variable}}\label{the-mode-variable}} -The \texttt{mode} variable contains contents of \texttt{-{}-mode} -command line option. It can be used to run some commands conditionally. -For example: +There is global \texttt{mode} variable available in the build file. It +contains contents of the \texttt{-\/-mode} command line option. It can +be used to run some commands conditionally. For example: \begin{verbatim} if mode == "draft" then @@ -287,12 +653,14 @@ called with \texttt{draft} mode: make4ht -m draft filename \end{verbatim} -\subsection{The \texttt{settings} table}\label{the-settings-table} +\hypertarget{the-settings-table}{% +\subsection{\texorpdfstring{The \texttt{settings} +table}{The settings table}}\label{the-settings-table}} You may want to access to the parameters also outside commands, file matches and image conversion functions. For example, if you want to -convert your file to the \texttt{OpenDocument Format}, you can use the -following settings, based on \texttt{oolatex} command: +convert your file to the \texttt{OpenDocument\ Format\ (ODT)}, you can +use the following settings, based on the \texttt{oolatex} command: \begin{verbatim} settings.tex4ht_sty_par = settings.tex4ht_sty_par ..",ooffice" @@ -300,7 +668,196 @@ settings.tex4ht_par = settings.tex4ht_par .. " ooffice/! -cmozhtf" settings.t4ht_par = settings.t4ht_par .. " -cooxtpipes -coo " \end{verbatim} -\section{Command line options}\label{command-line-options} +There are some functions to ease access to the settings: + +\begin{description} +\item[\texttt{set\_settings\{parameters\}}] +overwrite settings with values from a passed table +\item[\texttt{settings\_add\{parameters\}}] +add values to the current settings +\item[\texttt{filter\_settings\ "filter\ name"\ \{parameters\}}] +set settings for a filter +\item[\texttt{get\_filter\_settings(name)}] +get settings for a filter +\end{description} + +Using these functions, it is possible to simplify the settings for the +\texttt{ODT} format: + +\begin{verbatim} +settings_add { + tex4ht_sty_par =",ooffice", + tex4ht_par = " ooffice/! -cmozhtf", + t4ht_par = " -cooxtpipes -coo " +} +\end{verbatim} + +Settings for filters and extensions can be set using +\texttt{filter\_settings}: + +\begin{verbatim} +filter_settings "test" { + hello = "world" +} +\end{verbatim} + +These settings can be read in the extensions and filters using +\texttt{get\_filter\_settings}: + +\begin{verbatim} +function test(input) + local options = get_filter_settings("test") + print(options.hello) + return input +end + +\end{verbatim} + +\hypertarget{list-of-available-settings-for-filters-and-extensions.}{% +\subsection{List of available settings for filters and +extensions.}\label{list-of-available-settings-for-filters-and-extensions.}} + +These settings may be set using \texttt{filter\_settings} function. + +\hypertarget{the-tidy-extension}{% +\subsubsection{\texorpdfstring{The \texttt{tidy} +extension}{The tidy extension}}\label{the-tidy-extension}} + +\begin{description} +\item[options] +command line options for the \texttt{tidy} command. Default value is +\texttt{-m\ -utf8\ -w\ 512\ -q}. +\end{description} + +\hypertarget{the-fixinlines-dom-filter}{% +\subsubsection{\texorpdfstring{The \texttt{fixinlines} dom +filter}{The fixinlines dom filter}}\label{the-fixinlines-dom-filter}} + +\begin{description} +\item[inline\_elements] +table of inline elements which shouldn't be direct descendants of the +\texttt{body} element. The element names should be table keys, the +values should be true. +\end{description} + +Example + +\begin{verbatim} +filter_settings "fixinlines" {inline_elements = {a = true, b = true}} +\end{verbatim} + +\hypertarget{the-joincharacters-dom-filter}{% +\subsubsection{\texorpdfstring{The \texttt{joincharacters} dom +filter}{The joincharacters dom filter}}\label{the-joincharacters-dom-filter}} + +\begin{description} +\item[charelements] +table of elements which should be joined if several instances with the +same value of \texttt{class} attribute are side by side. +\end{description} + +Example + +\begin{verbatim} +filter_settings "joincharacters" { charclases = { span=true, mn = true}} +\end{verbatim} + +\hypertarget{mathjaxsettings}{% +\subsubsection{\texorpdfstring{The \texttt{mathjaxnode} +filter}{The mathjaxnode filter}}\label{mathjaxsettings}} + +\begin{description} +\item[options] +command line options for the \texttt{mjpage} command. Default value is +\texttt{-\/-output\ CommonHTML} +\end{description} + +Example + +\begin{verbatim} +filter_settings "mathjaxnode" { + options="--output SVG --font Neo-Euler" +} +\end{verbatim} + +\begin{description} +\item[cssfilename] +\texttt{mjpage} puts some CSS code into the HTML pages. +\texttt{mathjaxnode} extracts this information and saves it to a +standalone CSS file. Default CSS filename is \texttt{mathjax-chtml.css} +\item[fontdir] +directory with MathJax font files. This option enables use of local +fonts, which is usefull in Epub conversion, for example. The font +directory should be sub-directory of the current directory. Only TeX +font is supported at the moment. +\end{description} + +Example + +\begin{verbatim} +filter_settings "mathjaxnode" { + fontdir="fonts/TeX/woff/" +} +\end{verbatim} + +\hypertarget{the-aeneas-filter}{% +\subsubsection{\texorpdfstring{The \texttt{aeneas} +filter}{The aeneas filter}}\label{the-aeneas-filter}} + +\begin{description} +\item[skip\_elements] +List of CSS selectors that match elements which shouldn't be processed. +Default value: \texttt{\{\ "math",\ "svg"\}}. +\item[id\_prefix] +prefix used in the ID attribute forming. +\item[sentence\_match] +Lua pattern used to match a sentence. Default value: +\texttt{"({[}\^{}\%.\^{}\%?\^{}!{]}*)({[}\%.\%?!{]}?)"}. +\end{description} + +\hypertarget{configfile}{% +\section{Configuration file}\label{configfile}} + +It is possible to globally modify the build settings using the +configuration file. New compilation commands can be added, extensions +can be loaded or disabled and settings can be set. + +\hypertarget{location}{% +\subsection{Location}\label{location}} + +The configuration file can be saved either in +\texttt{\$HOME/.config/make4ht/config.lua} or in \texttt{.make4ht} in +the current directory or it's parents (up to \texttt{\$HOME}). + +\hypertarget{additional-commands}{% +\subsection{Additional commands}\label{additional-commands}} + +There are two additional commands: + +\begin{description} +\item[\texttt{Make:enable\_extension(name)}] +require extension +\item[\texttt{Make:disable\_extension(name)}] +disable extension +\end{description} + +\hypertarget{example}{% +\subsection{Example}\label{example}} + +The following configuration add support for the \texttt{biber} command, +requires \texttt{common\_domfilters} extension and requires MathML +output for math. + +\begin{verbatim} +Make:add("biber", "biber ${input}") +Make:enable_extension "common_domfilters" +settings_add { + tex4ht_sty_par =",mathml" +} +\end{verbatim} + +\hypertarget{command-line-options}{% +\section{Command line options}\label{command-line-options}} \begin{verbatim} make4ht - build system for tex4ht @@ -313,6 +870,7 @@ make4ht [options] filename ["tex4ht.sty op." "tex4ht op." -d,--output-dir (default "") Output directory -e,--build-file (default nil) If build file name is different than `filename`.mk4 +-f,--format (default nil) Output file format -l,--lua Use lualatex for document compilation -m,--mode (default default) Switch which can be used in the makefile -n,--no-tex4ht Disable dvi file processing with tex4ht command @@ -323,20 +881,22 @@ make4ht [options] filename ["tex4ht.sty op." "tex4ht op." <filename> (string) Input file name \end{verbatim} -You can still use \texttt{make4ht} in same way as \texttt{htlatex} +You can still invoke \texttt{make4ht} in the same way as +\texttt{htlatex}: \begin{verbatim} -make4ht filename "customcfg, charset=utf-8" " -cunihtf -utf8" " -dfoo" +make4ht filename "customcfg, charset=utf-8" "-cunihtf -utf8" "-dfoo" \end{verbatim} -Note that this will not use \texttt{make4ht} routines for output dir -making and copying If you want to use them, change the line above to: +Note that this will not use \texttt{make4ht} routines for output +directory making and copying. If you want to use them, change the line +above to: \begin{verbatim} -make4ht filename "customcfg, charset=utf-8" " -cunihtf -utf8" -d foo +make4ht -d foo filename "customcfg, charset=utf-8" "-cunihtf -utf8" \end{verbatim} -This call has the same effect as following: +This call has the same effect as the following: \begin{verbatim} make4ht -u -c customcfg -d foo filename @@ -352,3 +912,47 @@ make4ht -d ../gotoparrentdir filename make4ht -d ~/gotohomedir filename make4ht -d c:\documents\windowspathsareworkingtoo filename \end{verbatim} + +\hypertarget{troubleshooting}{% +\section{Troubleshooting}\label{troubleshooting}} + +\hypertarget{incorrect-handling-of-command-line-arguments-for-tex4ht-t4ht-or-latex}{% +\subsection{\texorpdfstring{Incorrect handling of command line arguments +for \texttt{tex4ht}, \texttt{t4ht} or +\texttt{latex}}{Incorrect handling of command line arguments for tex4ht, t4ht or latex}}\label{incorrect-handling-of-command-line-arguments-for-tex4ht-t4ht-or-latex}} + +Sometimes, you may get a similar error: + +\begin{verbatim} +make4ht:unrecognized parameter: i +\end{verbatim} + +It may be caused by a following \texttt{make4ht} invocation: + +\begin{verbatim} +make4ht hello.tex "customcfg,charset=utf-8" "-cunihtf -utf8" -d foo +\end{verbatim} + +The command line option parser is confused by mixing options for +\texttt{make4ht} and \texttt{tex4ht} in this case and tries to interpret +the \texttt{-cunihtf\ -utf8}, which are options for \texttt{tex4ht} +command as \texttt{make4ht} options. To fix that, you can either move +the \texttt{-d\ foo} directly after \texttt{make4ht} command: + +\begin{verbatim} +make4ht -d foo hello.tex "customcfg,charset=utf-8" "-cunihtf -utf8" +\end{verbatim} + +Another option is to add a space before \texttt{tex4ht} options: + +\begin{verbatim} +make4ht hello.tex "customcfg,charset=utf-8" " -cunihtf -utf8" -d foo +\end{verbatim} + +The former way is preferable, though. + +\hypertarget{license}{% +\section{License}\label{license}} + +Permission is granted to copy, distribute and/or modify this software +under the terms of the LaTeX Project Public License, version 1.3. diff --git a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-aeneas.lua b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-aeneas.lua new file mode 100644 index 00000000000..0b93c958404 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-aeneas.lua @@ -0,0 +1,144 @@ +-- DOM filter for Aeneas, tool for automatical text and audio synchronization +-- https://github.com/readbeyond/aeneas +-- It adds elements with id attributes for text chunks, in sentence length. +-- +-- +local cssquery = require "luaxml-cssquery" +local mkutils = require "mkutils" + +-- Table of CSS selectors to be skipped. +local skip_elements = { "math", "svg"} + +-- The id attribute format is configurable +-- Aeneas must be told to search for the ID pattern using is_text_unparsed_id_regex +-- option in Aneas configuration file +local id_prefix = "ast" + +-- Pattern to mach a sentence. It should match two groups, first is actual +-- sentence, the second optional interpunction mark. +local sentence_match = "([^%.^%?^!]*)([%.%?!]?)" + +-- convert table with selectors to a query list +local function prepare_selectors(skips) + local css = cssquery() + for _, selector in ipairs(skips) do + css:add_selector(selector) + end + return css +end + +-- save the HTML language +local function save_config(dom, saves) + local get_lang = function(d) + local html = d:query_selector("html")[1] or {} + return html:get_attribute("lang") + end + local saves = saves or {} + local config = get_filter_settings "aeneas_config" + if config.language then return end + saves.lang = get_lang(dom) + filter_settings "aeneas-config" (saves) +end +-- make span element with unique id for a sentence +local function make_span(id,parent, text) + local newobj = parent:create_element("span", {id=id }) + newobj.processed = true -- to disable multiple processing of the node + local text_node = newobj:create_text_node(text) + newobj:add_child_node(text_node) + return newobj +end + +-- make the id attribute and update the id value +local function make_id(lastid, id_prefix) + local id = id_prefix .. lastid + lastid = lastid + 1 + return id, lastid +end + +-- parse text for sentences and add spans +local function make_ids(parent, text, lastid, id_prefix) + local t = {} + local id + for chunk, punct in text:gmatch(sentence_match) do + id, lastid = make_id(lastid, id_prefix) + local newtext = chunk..punct + -- the newtext is empty string sometimes. we can skipt it then. + if newtext~="" then + table.insert(t, make_span(id, parent, newtext)) + end + end + return t, lastid +end + + + +-- test if the DOM element is in list of skipped CSS selectors +local function is_skipped(el, css) + local matched = css:match_querylist(el) + return #matched > 0 +end + +local function aeneas(dom, par) + local par = par or {} + local id = 1 + local options = get_filter_settings "aeneas" + local skip_elements = options.skip_elements or par.skip_elements or skip_elements + local id_prefix = options.id_prefix or par.id_prefix or id_prefix + local skip_object = prepare_selectors(skip_elements) + sentence_match = options.sentence_match or par.sentence_match or sentence_match + + local body = dom:query_selector("body")[1] + -- process only the document body + if not body then return dom end + -- save information for aeneas_config + save_config(dom, {id_prefix = id_prefix}) + body:traverse_elements(function(el) + -- skip disabled elements + if(is_skipped(el, skip_object)) then return false end + -- skip already processed elements + if el.processed then return false end + local newchildren = {} -- this will contain the new elements + local children = el:get_children() + local first_child = children[1] + + -- if the element contains only text, doesn't already have an id attribute and the text is short, + -- the id is set directly on that element. + if #children == 1 + and first_child:is_text() + and not el:get_attribute("id") + and string.len(first_child._text) < 20 + and el._attr + then + local idtitle + idtitle, id = make_id(id, id_prefix) + print(el._name, first_child._text) + el:set_attribute("id", idtitle) + return el + end + + for _, child in ipairs(children) do + -- process only non-empty text + if child:is_text() and child._text:match("%a+") then + local newnodes + newnodes, id = make_ids(child, child._text, id, id_prefix) + for _, node in ipairs(newnodes) do + table.insert(newchildren, node or {}) + end + else + -- insert the current processing element to the new element list + -- if it isn't only text + table.insert(newchildren, child or {}) + end + end + -- replace element children with the new ones + if #newchildren > 0 then + el._children = {} + for _, c in ipairs(newchildren) do + el:add_child_node(c) + end + end + end) + return dom +end + +return aeneas diff --git a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-fixinlines.lua b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-fixinlines.lua new file mode 100644 index 00000000000..25489f59ca5 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-fixinlines.lua @@ -0,0 +1,57 @@ +local inline_elements = { + b=true, + big=true, + i=true, + small=true, + tt=true, + abbr=true, + acronym=true, + cite=true, + code=true, + dfn=true, + em=true, + kbd=true, + strong=true, + samp=true, + time=true, + var=true, + a=true, + bdo=true, + br=true, + img=true, + map=true, + object=true, + q=true, + script=true, + span=true, + sub=true, + sup=true, + button=true, + input=true, + label=true, + select=true, + textarea=true, + mn=true, + mi=true +} + + +local function fix_inlines(obj) + local settings = get_filter_settings "fixinlines" + local inline_elements = settings.inline_elements or inline_elements + local nodes = obj:get_path("html body") + obj:traverse_node_list(nodes, function(jej) + if jej._type == "ELEMENT" then + local name = string.lower(jej._name) + -- local parent = jej:get_parent_node() + if inline_elements[name] then + local new = obj:create_element("p" ) + new:add_child_node(obj:copy_node(jej)) + jej:replace_node(new) + end + end + end) + return obj +end + +return fix_inlines diff --git a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-idcolons.lua b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-idcolons.lua new file mode 100644 index 00000000000..ccdafbd6d41 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-idcolons.lua @@ -0,0 +1,20 @@ +local function id_colons(obj) + -- replace : characters in links and ids with unserscores + obj:traverse_elements(function(el) + local name = string.lower(obj:get_element_name(el)) + if name == "a" then + local href = obj:get_attribute(el, "href") + if href and not href:match("[a-z]%://") then + obj:set_attribute(el, "href", href:gsub(":", "_")) + end + end + local id = obj:get_attribute( el , "id") + if id then + obj:set_attribute(el, "id", id:gsub(":", "_")) + end + -- local id = obj:get_attribute(el, "id") + end) + return obj +end + +return id_colons diff --git a/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua new file mode 100644 index 00000000000..8116a970da0 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/domfilters/make4ht-joincharacters.lua @@ -0,0 +1,87 @@ +local charclases = { + span=true, + mn = true, +} + +local function join_characters(obj,par) + -- join adjanced span and similar elements inserted by + -- tex4ht to just one object. + local par = par or {} + local options = get_filter_settings "joincharacters" + local charclases = options.charclases or par.charclases or charclases + + 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") + end + local is_span = function(next_el) + return charclases[get_name(next_el)] + 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 + -- loop over all elements and test if the current element is in a list of + -- processed elements (charclases) + 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 + -- 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 get_class(el) == get_class(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() + -- add the whitespace + elseif next_el:is_text() then + local s = next_el._text + -- this is needed to fix newlines inserted by Tidy + s = s:gsub("\n", "") + -- we must create a new node + el:add_child_node(el:create_text_node(s)) + next_el:remove_node() + real_next = nil + else + real_next = nil + end + -- use the saved element as a next object + next_el = real_next + end + end + + end) + -- join text nodes in an element into one + obj:traverse_elements(function(el) + -- save the text + local t = {} + local children = el:get_children() + for _, x in ipairs(children) do + if x:is_text() then + t[#t+1] = x._text + else + return nil + end + end + el._text = table.concat(t) + return el + end) + return obj +end + +return join_characters diff --git a/Master/texmf-dist/scripts/make4ht/filters/make4ht-domfilter.lua b/Master/texmf-dist/scripts/make4ht/filters/make4ht-domfilter.lua new file mode 100644 index 00000000000..27d9ed871d1 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/filters/make4ht-domfilter.lua @@ -0,0 +1,63 @@ +local filter_lib = require "make4ht-filterlib" +local dom = require "luaxml-domobject" +local mkutils = require "mkutils" + +local function load_filter(filtername) + return require("domfilters.make4ht-"..filtername) +end + +-- save processed names, in order to block multiple executions of the filter +-- sequence on a same file +local processed = {} + +local function filter(filters, name) + -- because XML parsing to DOM is potentially expensive operation + -- this filter will use cache for it's sequence + -- all requests to the domfilter will add new filters to the + -- one sequence, which will be executed on one DOM object. + -- it is possible to request a different sequence using + -- unique name parameter + local name = name or "domfilter" + local settings = mkutils.get_filter_settings(name) or {} + local sequence = settings.sequence or {} + local local_sequence = filter_lib.load_filters(filters, load_filter) + for _, filter in ipairs(local_sequence) do + table.insert(sequence, filter) + end + settings.sequence = sequence + mkutils.filter_settings (name) (settings) + + return function(filename, parameters) + -- load processed files for the current filter name + local processed_files = processed[name] or {} + -- don't process the file again + if processed_files[filename] then + return nil + end + local input = filter_lib.load_input_file(filename) + if not input then return nil, "Cannot load the input file" end + -- we need to use pcall, because XML error would break the whole build process + -- domobject will be error object if DOM parsing failed + local status, domobject = pcall(function() + return dom.parse(input) + end) + if not status then + print("DOM parsing of " .. filename .. " failed:") + print(domobject) + return nil, "DOM parsing failed" + end + for _,f in pairs(sequence) do + domobject = f(domobject,parameters) + end + local output = domobject:serialize() + if output then + filter_lib.save_input_file(filename, output) + else + print("DOM filter failed on ".. filename) + end + -- mark the filename as processed + processed_files[filename] = true + processed[name] = processed_files + end +end +return filter diff --git a/Master/texmf-dist/scripts/make4ht/filters/make4ht-filter.lua b/Master/texmf-dist/scripts/make4ht/filters/make4ht-filter.lua index 4c7ce409a07..bef44f585b5 100644 --- a/Master/texmf-dist/scripts/make4ht/filters/make4ht-filter.lua +++ b/Master/texmf-dist/scripts/make4ht/filters/make4ht-filter.lua @@ -1,41 +1,19 @@ -local filters = {} +local filter_lib = require "make4ht-filterlib" local function load_filter(filtername) return require("filters.make4ht-"..filtername) end -function filter(filters) - local sequence = {} - if type(filters) == "string" then - table.insert(sequence,load_filter(filters)) - elseif type(filters) == "table" then - for _,n in pairs(filters) do - if type(n) == "string" then - table.insert(sequence,load_filter(n)) - elseif type(n) == "function" then - table.insert(sequence, n) - end - end - elseif type(filters) == "function" then - table.insert(sequence, filters) - else - return false, "Argument to filter must be either\ntable with filter names, or single filter name" - end +function filter(filters,parameters) + local sequence = filter_lib.load_filters(filters, load_filter) return function(filename) if not filename then return false, "filters: no filename" end - local input = nil - - if filename then - local file = io.open(filename,"r") - input = file:read("*all") - file:close() - end + local input = filter_lib.load_input_file(filename) + if not input then return nil, "Cannot load the input file" end for _,f in pairs(sequence) do - input = f(input) + input = f(input,parameters) end - local file = io.open(filename,"w") - file:write(input) - file:close() + filter_lib.save_input_file(filename, input) end end return filter diff --git a/Master/texmf-dist/scripts/make4ht/filters/make4ht-mathjaxnode.lua b/Master/texmf-dist/scripts/make4ht/filters/make4ht-mathjaxnode.lua new file mode 100644 index 00000000000..2b453e4255b --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/filters/make4ht-mathjaxnode.lua @@ -0,0 +1,94 @@ +local mkutils = require "mkutils" +-- other possible value is page2svg +local mathnodepath = "mjpage" +-- options for MathJax command +local options = "--output CommonHTML" +-- 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(text) + local tmpfile = os.tmpname() + print("Compile using MathJax") + local command = mathnodepath .. " ".. options .. " > " .. tmpfile + print(command) + local commandhandle = io.popen(command,"w") + commandhandle:write(text) + commandhandle:close() + print("Result written to: ".. tmpfile) + local f = io.open(tmpfile) + local content = f:read("*all") + f:close() + os.remove(tmpfile) + return content +end + +-- save the css code from the html page generated by MathJax +local function extract_css(contents) + local css = "" + local filename = cssfilename + contents = contents:gsub('<style [^>]+>(.-)</style>', function(style) + -- replace only the style for mathjax + if style:match "%.mjx%-math" then + css = style + return '<link rel="stylesheet" type="text/css" href="'..filename ..'" />' + end + end) + -- local x = assert(io.open(file, "w")) + -- x:write(contents) + -- x:close() + 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) + print(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 "mathjaxnode" or {} + local arguments = arguments or {} + mathnodepath = arguments.prg or extoptions.prg or mathnodepath + 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 cssfilename + local newtext = compile(text) + local cssfile, newtext, css = extract_css(newtext) + -- use local font files if fontdir is present + if fontdir then + css = use_fonts(css) + end + save_css(cssfile, css) + Make:add_file(cssfile) + -- print(css) + print(cssfile) + return newtext +end diff --git a/Master/texmf-dist/scripts/make4ht/filters/make4ht-svg-height.lua b/Master/texmf-dist/scripts/make4ht/filters/make4ht-svg-height.lua new file mode 100644 index 00000000000..44e98748ea9 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/filters/make4ht-svg-height.lua @@ -0,0 +1,43 @@ + +-- Make:image("svg$", "dvisvgm -n -a -p ${page} -b preview -c 1.4,1.4 -s ${source} > ${output}") + +local max = function(a,b) + return a > b and a or b +end + +local function get_height(svg) + local height = svg:match("height='([0-9%.]+)pt'") + return tonumber(height) +end + +local function get_max_height(path,max_number) + local coordinates = {} + for number in path:gmatch("(%-?[0-9%.]+)") do + table.insert(coordinates, tonumber(number)) + end + for i = 2, #coordinates, 2 do + max_number = max(max_number, coordinates[i]) + end + return max_number +end + +local function update_height(svg, height) + return svg:gsub("height='.-pt'", "height='"..height .."pt'") +end + +-- we need to fix the svg height +return function(svg) + local max_height = 0 + local height = get_height(svg) + for path in svg:gmatch("path d='([^']+)'") do + -- find highest height in all paths in the svg file + max_height = get_max_height(path, max_height) + end + -- update the height only if the max_height is larger than height set in the SVG file + print(max_height, height) + if max_height > height then + svg = update_height(svg, max_height) + end + return svg +end + diff --git a/Master/texmf-dist/scripts/make4ht/make4ht b/Master/texmf-dist/scripts/make4ht/make4ht index 15bda54aca8..0da3c30549c 100755 --- a/Master/texmf-dist/scripts/make4ht/make4ht +++ b/Master/texmf-dist/scripts/make4ht/make4ht @@ -9,6 +9,7 @@ local make4ht = require("make4ht-lib") local lapp = require("lapp-mk4") local mkutils = require("mkutils") local mkparams = require("mkparams") +local mk_config = require("make4ht-config") -- args string is here just as sample, we dont pass it it to -- mkparams.get_args() so default args string is used local args = [[ @@ -26,7 +27,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.1c" +local version = "v0.2" mkparams.version_number = version local args = mkparams.get_args() @@ -36,6 +37,35 @@ local parameters = mkparams.process_args(args) local mode = parameters.mode local build_file = parameters.build_file +-- handle output formats +local allowed_output_formats = {xhtml = true, html5=true, odt = true} +-- formatter is Lua library which must provide at least prepare_parameters +-- and process_build_sequence functions +local formatter +local output_format = parameters.output_format +if allowed_output_formats[ output_format ] then + formatter = mkutils.load_output_format(output_format) +else + -- load html5 as default output format + if output_format then + print("Cannot load output format: ".. output_format) + end + formatter = mkutils.load_output_format("html5") +end +-- find make4ht configuration file +local configname = "make4ht" +local conffile = mk_config.find_config(configname) or mk_config.find_xdg_config(configname) +if conffile then + print("Using configuration file: " .. conffile) + mkutils.load_config(parameters, conffile) +end +local extensions = formatter.prepare_extensions(parameters.extensions) +extensions = mkutils.load_extensions(extensions, output_format) + + + +-- run extensions with prepare_parameters function +parameters = formatter.prepare_parameters(parameters,extensions) local make = mkutils.load_config(parameters, build_file)["Make"] make.params = parameters if make:length() < 1 then @@ -58,6 +88,10 @@ if #make.image_patterns > 0 then make.params.t4ht_par = make.params.t4ht_par .. " -p" end make:t4ht {ext = ext} +-- run extensions which modify the build sequence +if #extensions > 0 then + make = mkutils.extensions_modify_build(extensions, make) +end make:match("tmp$", function() return false,"tmp file" end) make:match(".*",function(filename,par) local outdir = '' --par["outdir"] and par["outdir"] .."/" or '' @@ -67,4 +101,5 @@ make:match(".*",function(filename,par) mkutils.copy(filename,outfilename) return true end) + make:run() diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-aeneas-config.lua b/Master/texmf-dist/scripts/make4ht/make4ht-aeneas-config.lua new file mode 100755 index 00000000000..d0b4a1360d5 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/make4ht-aeneas-config.lua @@ -0,0 +1,215 @@ +local M = {} + +local mkutils = require "mkutils" + +local task_template = [[ +<task> + <task_language>${lang}</task_language> + <task_description>${file_desc}</task_description> + <task_custom_id>${file_id}</task_custom_id> + <is_text_file>${prefix}${html_file}</is_text_file> + <is_text_type>${text_type}</is_text_type> + <is_audio_file>${prefix}${audio_file}</is_audio_file> + <is_text_unparsed_id_sort>${id_sort}</is_text_unparsed_id_sort> + <is_text_unparsed_id_regex>${id_regex}</is_text_unparsed_id_regex> + <os_task_file_name>${sub_file}</os_task_file_name> + <os_task_file_format>${sub_format}</os_task_file_format> + <os_task_file_smil_page_ref>${html_file}</os_task_file_smil_page_ref> + <os_task_file_smil_audio_ref>${audio_file}</os_task_file_smil_audio_ref> +</task> +]] + +-- get html files +local function get_html_files(config) + local config = config or {} + local files = {} + local filematch = config.file_match or "html$" + -- this is a trick to get list of files from the LG file + for _, file in ipairs(Make.lgfile.files) do + if file:match(filematch) then table.insert(files, file) end + end + return files +end + +-- prepare filename for the audio +local function get_audio_file(filename, config) + local extension = config.audio_extension or "mp3" + local base = mkutils.remove_extension(filename) + return base .. "." .. extension +end + +local function get_sub_file(filename, config) + local extension = config.sub_format or "smil" + local base = mkutils.remove_extension(filename) + return base .. "." .. extension +end + + +-- create task record for each HTML file +local function prepare_tasks(files, configuration) + local tasks = {} + -- the map can contain info for particular files, otherwise we will interfere default values + local map = configuration.map or {} + -- task_template should be configurable + local task_template = configuration.task_template or task_template + for i, filename in ipairs(files) do + local filemap = map[filename] + if filemap ~= false then + filemap = filemap or {} + local taskconfig = configuration + taskconfig.html_file = filename + taskconfig.prefix = filemap.prefix or configuration.prefix + taskconfig.file_desc = filemap.description or configuration.description .. " " .. i + taskconfig.file_id = filemap.id or filename:gsub("[%/%.]", "_") + taskconfig.text_type = filemap.text_type or configuration.text_type + taskconfig.audio_file = filemap.audio_file or get_audio_file(filename, configuration) + taskconfig.sub_file = filemap.sub_file or get_sub_file(filename, configuration) + taskconfig.id_sort= filemap.id_sort or configuration.id_sort + taskconfig.id_prefix = filemap.id_regex or configuration.id_regex + taskconfig.sub_format = filemap.sub_format or configuration.sub_format + tasks[#tasks+1] = task_template % taskconfig + Make:add_file(taskconfig.audio_file) + Make:add_file(taskconfig.sub_file) + end + end + return tasks --table.concat(tasks, "\n") +end +-- from https://www.readbeyond.it/aeneas/docs/clitutorial.html#xml-config-file-config-xml +local config_template = [[ +<job> + <job_language>${lang}</job_language> + <job_description>${description}</job_description> + <tasks> + ${tasks} + </tasks> + <os_job_file_name>output_example4</os_job_file_name> + <os_job_file_container>zip</os_job_file_container> + <os_job_file_hierarchy_type>flat</os_job_file_hierarchy_type> + <os_job_file_hierarchy_prefix>${prefix}</os_job_file_hierarchy_prefix> +</job> +]] + +-- check if the config file exists +local function is_config(filename) + return mkutils.file_exists(filename) +end + +-- prepare Aeneas configuration +local function prepare_configuration(parameters) + local config = parameters or {} + config.lang = parameters.lang + config.tasks = table.concat(prepare_tasks(parameters.files, config), "\n") + return config +end + +-- write Aeneeas configuration file in the XML format +local function write_config(filename, configuration) + local cfg = config_template % configuration + print(cfg) + local f = io.open(filename, "w") + f:write(cfg) + f:close() +end + + +local function make_default_options(options) + local configuration = {} + local par = get_filter_settings "aeneas-config" + configuration.lang = options.lang or par.lang or "en" + configuration.description = options.description or par.description or "Aeneas job" + configuration.map = options.map or par.map or {} + configuration.text_type = options.text_type or par.text_type or "unparsed" + configuration.id_sort = options.id_sort or par.id_sort or "numeric" + configuration.id_regex = options.id_regex or par.id_regex or par.id_prefix .. "[0-9]+" + configuration.sub_format = options.sub_format or par.sub_format or "smil" + configuration.prefix = options.prefix or par.prefix or "./" + configuration.config_name = options.config_name or par.config_name or "config.xml" + configuration.keep_config = options.keep_config or par.keep_config + return configuration +end + + +local function configure_job(options) + local configuration = make_default_options(options) + local config_name = configuration.config_name + -- prepare the configuration in every case + configuration.files = get_html_files() + local configuration = prepare_configuration(configuration) + -- write the configuration only if the config file doesn't exist + -- and keep_config option is set to true + if is_config(config_name) and configuration.keep_config==true then + else + write_config(config_name, configuration) + end +end + +local function execute_job(options) + local par = get_filter_settings "aeneas-config" + local configuration = make_default_options(options) + configuration.files = get_html_files() + -- we need to configure prepare_tasks to return calls to aeneas task convertor + configuration.python = options.python or par.python or "python3" + configuration.module = options.module or par.module or "aeneas.tools.execute_task" + configuration.task_template = '${python} -m "${module}" "${audio_file}" "${html_file}" "is_text_type=${text_type}|os_task_file_smil_audio_ref=${audio_file}|os_task_file_smil_page_ref=${html_file}|task_language=${lang}|is_text_unparsed_id_sort=${id_sort}|is_text_unparsed_id_regex=${id_regex}|os_task_file_format=${sub_format}" "${sub_file}"' + local tasks = prepare_tasks(configuration.files, configuration) + -- execute the tasks + for _, v in ipairs(tasks) do + print("task", v) + local proc = io.popen(v, "r") + local result = proc:read("*all") + proc:close() + print(result) + end +end + +-- the aeneas configuration must be executed at last processed file, after all filters +-- have been executed +local function get_last_lg_file() + local t = Make.lgfile.files + for i = #t, 1, -1 do + -- find last html file or the tmp file + local x = t[i] + if x:match "html$" or x:match "tmp$" then + return x + end + end + return t[#t] +end + +-- write Aeneas job configuration file +-- it doesn't execute Aeneas +function M.write_job(par) + -- configuration table for Aeneas job + Make:match("tmp$", function() + configure_job(par) + end) +end + +-- execute Aeneas directly +function M.execute(par) + Make:match("tmp$", function(current_name) + -- there may be html files after the .tmp file + -- the aeneas must be executed after the Aeneas filter inserts the id + -- attributes, so it is necessary to execute this code as very last one + local last = get_last_lg_file() + -- execute the job if there are no HTML files after the tmp file + if current_name == last then + execute_job(par) + end + Make:match(last, function() + execute_job(par) + end) + end) +end + +-- only register the audio and smil files as processed files +function M.process_files(par) + Make:match("tmp$", function() + local configuration = make_default_options(par) + local files = get_html_files() + prepare_tasks(files, configuration) + end) +end + + +return M diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-config.lua b/Master/texmf-dist/scripts/make4ht/make4ht-config.lua new file mode 100755 index 00000000000..bd83632a574 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/make4ht-config.lua @@ -0,0 +1,92 @@ +local m = {} + +local mkutils = require "mkutils" + +local file_exists = mkutils.file_exists +-- function file_exists(name) +-- local f=io.open(name,"r") +-- if f~=nil then io.close(f) return true else return false end +-- end + + +local make_name = function(name) + return table.concat(name, "/") + -- return name:gsub("//","/") +end + +-- find the config file in XDG_CONFIG_HOME or in the HOME directry +-- the XDG tree is looked up first, the $HOME is used only when it cannot be +-- find in the former +local xdg_config = function(filename, xdg_config_name) + local dotfilename = "." .. filename + local xdg_config_name = xdg_config_name or "config.lua" + local xdg = os.getenv("XDG_CONFIG_HOME") or ((os.getenv("HOME") or "") .. "/.config") + local home = os.getenv("HOME") + if xdg then + -- filename like ~/.config/make4ht/config.lua + local fn = make_name{ xdg ,filename , xdg_config_name } + if file_exists(fn) then + return fn + end + end + if home then + -- ~/.make4ht + local fn = make_name{ home, dotfilename } + if file_exists(fn) then + return fn + end + end +end + +local find_config = function(filename) + local filename = "." .. filename + local current = lfs.currentdir() + local path = {} + current:gsub("([^/]+)", function(s) table.insert(path,s) end) + local begin = os.type == "windows" and "" or "/" + for i = #path, 1, -1 do + local fn =begin .. table.concat(path,"/") .. "/".. filename + -- print("testing",fn) + if file_exists(fn) then return fn end + table.remove(path) + end + return false +end + +local function load_config(filename, default) + local default = default or {} + default.table = table + default.string = string + default.io = io + default.os = os + default.math = math + default.print = print + default.ipairs = ipairs + default.pairs = pairs + local f = io.open(filename, "r") + local contents = f:read("*all") + f:close() + load(contents,"sandbox config","bt", default)() + return default +end + +--[[ +local function load_config(filename, default) + local default = default or {} + if ~file_exists(filename) then + return nil, "Cannot load config file "..filename + end + local section = "default" + local file = io.open(filename, "r") + if ~file then return nil, "Error opening config file"..filename end + for line in file:lines() do + local ts = line:match("") + end + file:close() +end +--]] + +m.find_config = find_config +m.find_xdg_config = xdg_config +m.load_config = load_config +return m diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-dom.lua b/Master/texmf-dist/scripts/make4ht/make4ht-dom.lua deleted file mode 100755 index fc90bf67e7c..00000000000 --- a/Master/texmf-dist/scripts/make4ht/make4ht-dom.lua +++ /dev/null @@ -1,448 +0,0 @@ ---- DOM module for LuaXML --- @module make4ht-dom -local dom = {} -local xml = require("luaxml-mod-xml") -local handler = require("luaxml-mod-handler") -local query = require("make4ht-parse-query") - - -local void = {area = true, base = true, br = true, col = true, hr = true, img = true, input = true, link = true, meta = true, param = true} - -local actions = { - TEXT = {text = "%s"}, - COMMENT = {start = "<!-- ", text = "%s", stop = " -->"}, - ELEMENT = {start = "<%s%s>", stop = "</%s>", void = "<%s%s />"}, - DECL = {start = "<?%s %s?>"}, - DTD = {start = "<!DOCTYPE ", text = "%s" , stop=">"} -} - ---- It serializes the DOM object back to XML --- @function serialize_dom --- @param parser DOM object --- @param current --- @param level --- @param output --- @return table -function serialize_dom(parser, current,level, output) - local output = output or {} - local function get_action(typ, action) - local ac = actions[typ] or {} - local format = ac[action] or "" - return format - end - local function insert(format, ...) - table.insert(output, string.format(format, ...)) - end - local function prepare_attributes(attr) - local t = {} - local attr = attr or {} - for k, v in pairs(attr) do - t[#t+1] = string.format("%s='%s'", k, v) - end - if #t == 0 then return "" end - -- add space before attributes - return " " .. table.concat(t, " ") - end - local function start(typ, el, attr) - local format = get_action(typ, "start") - insert(format, el, prepare_attributes(attr)) - end - local function text(typ, text) - local format = get_action(typ, "text") - insert(format, text) - end - local function stop(typ, el) - local format = get_action(typ, "stop") - insert(format,el) - end - local level = level or 0 - local spaces = string.rep(" ",level) - local root= current or parser._handler.root - local name = root._name or "unnamed" - local xtype = root._type or "untyped" - local text_content = root._text or "" - local attributes = root._attr or {} - -- if xtype == "TEXT" then - -- print(spaces .."TEXT : " .. root._text) - -- elseif xtype == "COMMENT" then - -- print(spaces .. "Comment : ".. root._text) - -- else - -- print(spaces .. xtype .. " : " .. name) - -- end - -- for k, v in pairs(attributes) do - -- print(spaces .. " ".. k.."="..v) - -- end - if xtype == "DTD" then - text_content = string.format('%s %s "%s" "%s"', name, attributes["_type"], attributes._name, attributes._uri ) - attributes = {} - elseif xtype == "ELEMENT" and void[name] then - local format = get_action(xtype, "void") - insert(format, name, prepare_attributes(attributes)) - return output - end - - start(xtype, name, attributes) - text(xtype,text_content) - local children = root._children or {} - for _, child in ipairs(children) do - output = serialize_dom(parser,child, level + 1, output) - end - stop(xtype, name) - return output -end - -local parse = function(x) - local domHandler = handler.domHandler() - local Parser = xml.xmlParser(domHandler) - -- preserve whitespace - Parser.options.stripWS = nil - Parser:parse(x) - Parser.current = Parser._handler.root - Parser.__index = Parser - - local function save_methods(element) - setmetatable(element,Parser) - local children = element._children or {} - for _, x in ipairs(children) do - save_methods(x) - end - end - local parser = setmetatable({}, Parser) - - --- @class Parser - function Parser.root_node(self) - return self._handler.root - end - - - function Parser.get_element_type(self, el) - local el = el or self - return el._type - end - function Parser.is_element(self, el) - local el = el or self - return self:get_element_type(el) == "ELEMENT" - end - - function Parser.is_text(self, el) - local el = el or self - return self:get_element_type(el) == "TEXT" - end - - local lower = string.lower - - function Parser.get_element_name(self, el) - local el = el or self - return el._name or "unnamed" - end - - function Parser.get_attribute(self, name) - local el = self - if self:is_element(el) then - local attr = el._attr or {} - return attr[name] - end - end - - function Parser.set_attribute(self, name, value) - local el = self - if self:is_element(el) then - el._attr[name] = value - return true - end - end - - - function Parser.serialize(self, current) - local current = current - -- if no current element is added and self is not plain parser object - -- (_type is then nil), use the current object as serialized root - if not current and self._type then - current = self - end - return table.concat(serialize_dom(self, current)) - end - - function Parser.get_path(self,path, current) - local function traverse_path(path_elements, current, t) - local t = t or {} - if #path_elements == 0 then - -- for _, x in ipairs(current._children or {}) do - -- table.insert(t,x) - -- end - table.insert(t,current) - return t - end - local current_path = table.remove(path_elements, 1) - for _, x in ipairs(self:get_children(current)) do - if self:is_element(x) then - local name = string.lower(self:get_element_name(x)) - if name == current_path then - t = traverse_path(path_elements, x, t) - end - end - end - return t - end - local current = current or self:root_node() -- self._handler.root - local path_elements = {} - local path = string.lower(path) - for el in path:gmatch("([^%s]+)") do table.insert(path_elements, el) end - return traverse_path(path_elements, current) - end - - function Parser.calculate_specificity(self, query) - local query = query or {} - local specificity = 0 - for _, item in ipairs(query.query or {}) do - for key, value in pairs(item) do - if key == "id" then - specificity = specificity + 100 - elseif key == "tag" then - specificity = specificity + 1 - else - specificity = specificity + 10 - end - end - end - return specificity - end - - function Parser.match_querylist(self, querylist) - local matches = {} - local querylist = querylist - - local function test_part(key, value, el) - -- print("testing", key, value, el:get_element_name()) - if key == "tag" then - return el:get_element_name() == value - elseif key == "id" then - local id = el:get_attribute "id" - return id and id == value - elseif key == "class" then - local class = el:get_attribute "class" - if not class then return false end - local c = {} - for part in class:gmatch "([^%s]+)" do - c[part] = true - end - return c[value] == true - end - -- TODO: Add more cases - -- just return true for not supported selectors - return true - end - - local function test_object(query, el) - -- test one object in CSS selector - local matched = {} - for key, value in pairs(query) do - matched[#matched+1] = test_part(key, value, el) - end - if #matched == 0 then return false end - for k, v in ipairs(matched) do - if v ~= true then return false end - end - return true - end - - local function match_query(query, el) - local query = query or {} - local object = table.remove(query) -- get current object from the query stack - if not object then return true end -- if the query stack is empty, then we can be sure that it matched previous items - if not el:is_element() then return false end -- if there is object to test, but current node isn't element, test failed - local result = test_object(object, el) - if result then - return match_query(query, el:get_parent()) - end - return false - end - for _,element in ipairs(querylist) do - local query = {} - for k,v in ipairs(element.query) do query[k] = v end - if #query > 0 then -- don't try to match empty query - local result = match_query(query, self) - if result then matches[#matches+1] = element end - end - end - return matches - end - - function Parser.get_selector_path(self, selectorlist) - local nodelist = {} - self:traverse_elements(function(el) - local matches = el:match_querylist(selectorlist) - print("Matching", el:get_element_name(), #matches) - if #matches > 0 then nodelist[#nodelist+1] = el - end - end) - return nodelist - end - - --- Parse CSS selector to match table - function Parser.prepare_selector(self, selector) - local querylist = {} - local function parse_selector(item) - local query = {} - -- for i = #item, 1, -1 do - -- local part = item[i] - for _, part in ipairs(item) do - local t = {} - for _, atom in ipairs(part) do - local key = atom[1] - local value = atom[2] - t[key] = value - end - query[#query + 1] = t - end - return query - end - -- for item in selector:gmatch("([^%s]+)") do - -- elements[#elements+1] = parse_selector(item) - -- end - local parts = query.parse_query(selector) or {} - -- several selectors may be separated using ",", we must process them separately - for _, part in ipairs(parts) do - querylist[#querylist+1] = {query = parse_selector(part)} - end - return querylist - end - - function Parser.get_children(self, el) - local el = el or self - local children = el._children or {} - return children - end - - function Parser.get_parent(self, el) - local el = el or self - return el._parent - end - - function Parser.traverse_elements(self, fn, current) - local current = current or self:root_node() - local status = true - if self:is_element(current) or self:get_element_type(current) == "ROOT"then - local status = fn(current) - -- don't traverse child nodes when the user function return false - if status ~= false then - for _, child in ipairs(self:get_children(current)) do - self:traverse_elements(fn, child) - end - end - end - end - - function Parser.traverse_node_list(self, nodelist, fn) - local nodelist = nodelist or {} - for _, node in ipairs(nodelist) do - for _, element in ipairs(node._children) do - fn(element) - end - end - end - - function Parser.replace_node(self, new) - local old = self - local parent = self:get_parent(old) - local id,msg = self:find_element_pos( old) - if id then - parent._children[id] = new - return true - end - return false, msg - end - - function Parser.add_child_node(self, child) - local parent = self - child._parent = parent - table.insert(parent._children, child) - end - - - function Parser.copy_node(self, element) - local element = element or self - local t = {} - for k, v in pairs(element) do - if type(v) == "table" and k~="_parent" then - t[k] = self:copy_node(v) - else - t[k] = v - end - end - save_methods(t) - return t - end - - function Parser.create_element(self, name, attributes, parent) - local parent = parent or self - local new = {} - new._type = "ELEMENT" - new._name = name - new._attr = attributes or {} - new._children = {} - new._parent = parent - save_methods(new) - return new - end - - function Parser.remove_node(self, element) - local element = element or self - local parent = self:get_parent(element) - local pos = self:find_element_pos(element) - -- if pos then table.remove(parent._children, pos) end - if pos then - -- table.remove(parent._children, pos) - parent._children[pos] = setmetatable({_type = "removed"}, Parser) - end - end - - function Parser.find_element_pos(self, el) - local el = el or self - local parent = self:get_parent(el) - if not self:is_element(parent) and self:get_element_type(parent) ~= "ROOT" then return nil, "The parent isn't element" end - for i, x in ipairs(parent._children) do - if x == el then return i end - end - return false, "Cannot find element" - end - - function Parser.get_siblibgs(self, el) - local el = el or self - local parent = el:get_parent() - if parent:is_element() then - return parent:get_children() - end - end - - function Parser.get_sibling_node(self, change) - local el = self - local pos = el:find_element_pos() - local siblings = el:get_siblibgs() - if pos and siblings then - return siblings[pos + change] - end - end - - function Parser.get_next_node(self, el) - local el = el or self - return el:get_sibling_node(1) - end - - function Parser.get_prev_node(self, el) - local el = el or self - return el:get_sibling_node(-1) - end - - - -- include the methods to all xml nodes - save_methods(parser._handler.root) - -- parser: - return parser -end - - -local M = {} -M.parse = parse -M.serialize= serialize_dom -return M diff --git a/Master/texmf-dist/scripts/make4ht/make4ht-filterlib.lua b/Master/texmf-dist/scripts/make4ht/make4ht-filterlib.lua new file mode 100755 index 00000000000..33cb3530a85 --- /dev/null +++ b/Master/texmf-dist/scripts/make4ht/make4ht-filterlib.lua @@ -0,0 +1,42 @@ +local M = {} + +-- the filter module must implement the load_filter function +function M.load_filters(filters, load_filter) + local sequence = {} + if type(filters) == "string" then + table.insert(sequence,load_filter(filters)) + elseif type(filters) == "table" then + for _,n in pairs(filters) do + if type(n) == "string" then + table.insert(sequence,load_filter(n)) + elseif type(n) == "function" then + table.insert(sequence, n) + end + end + elseif type(filters) == "function" then + table.insert(sequence, filters) + else + return false, "Argument to filter must be either\ntable with filter names, or single filter name" + end + return sequence +end + +function M.load_input_file(filename) + if not filename then return false, "filters: no filename" end + local input = nil + + if filename then + local file = io.open(filename,"r") + input = file:read("*all") + file:close() + end + return input +end + +function M.save_input_file(filename, input) + local file = io.open(filename,"w") + file:write(input) + file:close() +end + +return M diff --git a/Master/texmf-dist/scripts/make4ht/mkparams.lua b/Master/texmf-dist/scripts/make4ht/mkparams.lua index 61c522a2406..f5df4849966 100755 --- a/Master/texmf-dist/scripts/make4ht/mkparams.lua +++ b/Master/texmf-dist/scripts/make4ht/mkparams.lua @@ -17,6 +17,7 @@ ${progname} [options] filename ["tex4ht.sty op." "tex4ht op." "t4ht op" "latex o -c,--config (default xhtml) Custom config file -d,--output-dir (default nil) Output directory -e,--build-file (default nil) If build file is different than `filename`.mk4 +-f,--format (default html5) Output file format -h,-- help Display this message -l,--lua Use lualatex for document compilation -m,--mode (default default) Switch which can be used in the makefile @@ -26,6 +27,15 @@ ${progname} [options] filename ["tex4ht.sty op." "tex4ht op." "t4ht op" "latex o -x,--xetex Use xelatex for document compilation -v,--version Display version number ]] + +-- test if the current command line argument should be passed to tex4ht, t4ht or latex +local function is_escapedargument(arg) + -- we need to ignore make4ht options which can be used without filename, ie --version and --help + local ignored_options = {["-h"]=true, ["--help"]=true, ["-v"] = true, ["--version"]=true} + if ignored_options[arg] then return false end + -- in other cases, match if the argument starts with "-" character + return arg:match("^%-") +end local function get_args(parameters, optiontext) local parameters = parameters or {} parameters.progname = parameters.progname or "make4ht" @@ -33,10 +43,37 @@ local function get_args(parameters, optiontext) local optiontext = optiontext or m.optiontext parameters.postfile = parameters.postfile or "" optiontext = optiontext .. parameters.postparams .."<filename> (string) Input file name\n" .. parameters.postfile + -- we can pass arguments for tex4ht and t4ht after filename, but it will confuse lapp, thinking that these + -- options are for make4ht. this may result in execution error or wrong option parsing + -- as fix, add a space before options at the end (we need to stop to add spaces as soon as we find + -- nonempty string which doesn't start with - it will be filename or tex4ht.sty options + if #arg > 1 then -- do this only if more than one argument is used + for i=#arg,1,-1 do + local current = arg[i] + if is_escapedargument(arg[i]) then + arg[i] = " ".. arg[i] + -- empty parameter + elseif current == "" then + else + break + end + end + end --print("--------------\n" .. optiontext .."--------------\n") return lapp(optiontext % parameters) end +--- get outptut file format and list of extensions from --format option string +local function get_format_extensions(format_string) + local format, rest = format_string:match("^([a-zA-Z0-9]+)(.*)") + local extensions = {} + -- it is possible to pass only the extensions + rest = rest or format_string + rest:gsub("([%+%-])([^%+^%-]+)",function(typ, name) + table.insert(extensions, {type = typ, name = name}) + end) + return format, extensions +end local function process_args(args) local function get_inserter(args,tb) @@ -80,7 +117,7 @@ local function process_args(args) local latex_cli_params = args[4] or "" if not latex_cli_params:match("%-jobname") then -- we must strip out directories from jobname when full path to document is given - input = input:match("([^%/]+)$") + input = input:match("([^%/^%\\]+)$") table.insert(latex_params,"-jobname="..input) else -- when user specifies -jobname, we must change name of the input file, @@ -140,9 +177,14 @@ local function process_args(args) local build_file = input.. ".mk4" if args["build-file"] and args["build-file"] ~= "nil" then - build_file = args["build-file"] + build_file = args["build-file"] end + local outformat, extensions + if args["format"] and arg["format"] ~= "nil" then + outformat, extensions = get_format_extensions(args["format"]) + end + local parameters = { htlatex = compiler ,input=input @@ -156,6 +198,8 @@ local function process_args(args) ,mode = mode ,dvi = dvi ,build_file = build_file + ,output_format = outformat + ,extensions = extensions --,t4ht_dir_format=t4ht_dir_format } if outdir then parameters.outdir = outdir end @@ -165,8 +209,15 @@ local function process_args(args) print("tex4ht.sty :",t4sty) print("tex4ht",tex4ht) print("build_file", build_file) + if outformat~="nil" then + print("Output format", outformat) + for _, ex in ipairs(extensions) do + print("Extension", ex.type .. ex.name) + end + end return parameters end m.get_args = get_args +m.get_format_extensions = get_format_extensions m.process_args = process_args return m diff --git a/Master/texmf-dist/scripts/make4ht/mkutils.lua b/Master/texmf-dist/scripts/make4ht/mkutils.lua index 56c413ffb43..88b456fe9da 100755 --- a/Master/texmf-dist/scripts/make4ht/mkutils.lua +++ b/Master/texmf-dist/scripts/make4ht/mkutils.lua @@ -1,6 +1,7 @@ module(...,package.seeall) local make4ht = require("make4ht-lib") +local mkparams = require("mkparams") --template engine function interp(s, tab) local tab = tab or {} @@ -223,6 +224,8 @@ env.split = split env.string = string env.table = table env.copy = copy +env.tonumber = tonumber +env.tostring = tostring env.mkdirectories = mkdirectories env.require = require env.texio = texio @@ -230,7 +233,49 @@ env.type = type env.lfs = lfs env.os = os env.io = io +env.math = math env.unicode = unicode + + +-- it is necessary to use the settings table +-- set in the Make environment by mkutils +function env.set_settings(par) + local settings = env.settings + for k,v in pairs(par) do + settings[k] = v + end +end + +-- Add a value to the current settings +function env.settings_add(par) + local settings = env.settings + for k,v in pairs(par) do + local oldval = settings[k] or "" + settings[k] = oldval .. v + end +end + +function env.get_filter_settings(name) + local settings = env.settings + -- local settings = self.params + local filters = settings.filter or {} + local filter_options = filters[name] or {} + return filter_options +end + +function env.filter_settings(name) + -- local settings = Make.params + local settings = env.settings + local filters = settings.filter or {} + local filter_options = filters[name] or {} + return function(par) + for k,v in pairs(par) do + filter_options[k] = v + end + filters[name] = filter_options + settings.filter = filters + end +end env.Font = function(s) local font_name = s["name"] if not font_name then return nil, "Cannot find font name" end @@ -239,11 +284,35 @@ end env.Make = make4ht.Make env.Make.params = env.settings -env.Make:add("test","no takže ${tex4ht_sty_par} ${htlatex} ${input} ${config}") +env.Make:add("test","test the variables: ${tex4ht_sty_par} ${htlatex} ${input} ${config}") + +-- this function reads the LaTeX log file and tries to detect fatal errors in the compilation +local function testlogfile(par) + local logfile = par.input .. ".log" + local f = io.open(logfile,"r") + if not f then + print("Make4ht: cannot open log file "..logfile) + return 1 + end + local len = f:seek("end") + -- test only the end of the log file, no need to run search functions on everything + local newlen = len - 1256 + -- but the value to seek must be greater than 0 + newlen = (newlen > 0) and newlen or 0 + f:seek("set", newlen) + local text = f:read("*a") + f:close() + if text:match("No pages of output") or text:match("TeX capacity exceeded, sorry") then return 1 end + return 0 +end + + +-- Make this function available in the build files +Make.testlogfile = testlogfile --env.Make:add("htlatex", "${htlatex} ${latex_par} '\\\makeatletter\\def\\HCode{\\futurelet\\HCode\\HChar}\\def\\HChar{\\ifx\"\\HCode\\def\\HCode\"##1\"{\\Link##1}\\expandafter\\HCode\\else\\expandafter\\Link\\fi}\\def\\Link#1.a.b.c.{\\g@addto@macro\\@documentclasshook{\\RequirePackage[#1,html]{tex4ht}\\let\\HCode\\documentstyle\\def\\documentstyle{\\let\\documentstyle\\HCode\\expandafter\\def\\csname tex4ht\\endcsname{#1,html}\\def\\HCode####1{\\documentstyle[tex4ht,}\\@ifnextchar[{\\HCode}{\\documentstyle[tex4ht]}}}\\makeatother\\HCode '${config}${tex4ht_sty_par}'.a.b.c.\\input ' ${input}") -env.Make:add("htlatex",function(par) - local command = -"${htlatex} ${latex_par} '\\makeatletter".. + +-- template for calling LaTeX with tex4ht loaded +Make.latex_command = "${htlatex} ${latex_par} '\\makeatletter".. "\\def\\HCode{\\futurelet\\HCode\\HChar}\\def\\HChar{\\ifx\"\\HCode".. "\\def\\HCode\"##1\"{\\Link##1}\\expandafter\\HCode\\else".. "\\expandafter\\Link\\fi}\\def\\Link#1.a.b.c.{\\g@addto@macro".. @@ -252,46 +321,170 @@ env.Make:add("htlatex",function(par) "\\HCode\\expandafter\\def\\csname tex4ht\\endcsname{#1,html}\\def".. "\\HCode####1{\\documentstyle[tex4ht,}\\@ifnextchar[{\\HCode}{".. "\\documentstyle[tex4ht]}}}\\makeatother\\HCode ${tex4ht_sty_par}.a.b.c.".. -"\\input ${tex_file}'" +"\\input ${tex_file}'" + +env.Make:add("htlatex",function(par) + local command = Make.latex_command if os.type == "windows" then command = command:gsub("'",'') end command = command % par print("LaTeX call: "..command) os.execute(command) - local logfile = par.input .. ".log" - local f = io.open(logfile,"r") - if not f then - print("Make4ht: cannot open log file "..logfile) - return 1 - end - local len = f:seek("end") - f:seek("set", len - 1256) - local text = f:read("*a") - f:close() - if text:match("No pages of output") or text:match("TeX capacity exceeded, sorry") then return 1 end - return 0 + return testlogfile(par) end ,{correct_exit=0}) + + +env.Make:add("latexmk", function(par) + local command = Make.latex_command + par.expanded = command % par + -- quotes in latex_command must be escaped, they cause Latexmk error + par.expanded = par.expanded:gsub('"', '\\"') + local newcommand = 'latexmk -latex="${expanded}" ${tex_file}' % par + os.execute(newcommand) + return Make.testlogfile(par) +end, {correct_exit= 0}) + + + env.Make:add("tex4ht","tex4ht ${tex4ht_par} \"${input}.${dvi}\"", nil, 1) env.Make:add("t4ht","t4ht ${t4ht_par} \"${input}.${ext}\"",{ext="dvi"},1) +-- enable extension in the config file +-- the following two functions must be here and not in make4ht-lib.lua +-- because of the access to env.settings +env.Make.enable_extension = function(self,name) + table.insert(env.settings.extensions, {type="+", name=name}) +end + +-- disable extension in the config file +env.Make.disable_extension = function(self,name) + table.insert(env.settings.extensions, {type="-", name=name}) +end + function load_config(settings, config_name) - local settings = settings or main_settings - env.settings = settings - env.mode = settings.mode - local config_name = kpse.find_file(config_name, 'texmfscripts') or config_name - local f = io.open(config_name,"r") - if not f then + local settings = settings or main_settings + env.settings = settings + env.mode = settings.mode + if config_name and not file_exists(config_name) then + config_name = kpse.find_file(config_name, 'texmfscripts') or config_name + end + local f = io.open(config_name,"r") + if not f then print("Cannot open config file", config_name) return env end print("Using build file", config_name) - local code = f:read("*all") - local fn, msg = run(code,env) - if not fn then print(msg) end - assert(fn) - return env + local code = f:read("*all") + local fn, msg = run(code,env) + if not fn then print(msg) end + assert(fn) + return env +end + +local function find_lua_file(name) + local extension_path = name:gsub("%.", "/") .. ".lua" + return kpse.find_file(extension_path, "lua") +end + +--- load the output format plugins +function load_output_format(format_name) + local format_library = "make4ht.formats."..format_name + local is_format_file = find_lua_file(format_library) + if is_format_file then + local format = assert(require(format_library)) + if format then + format.prepare_extensions = format.prepare_extensions or function(extensions) return extensions end + end + return format + end +end + +--- Execute the prepare_parameters function in list of extensions +function extensions_prepare_parameters(extensions, parameters) + for _, ext in ipairs(extensions) do + -- execute the extension only if it contains prepare_parameters function + local fn = ext.prepare_parameters + if fn then + parameters = fn(parameters) + end + end + return parameters +end + +--- Modify the build sequence using extensions +-- @param extensions list of extensions +-- @make Make object +function extensions_modify_build(extensions, make) + for _, ext in ipairs(extensions) do + local fn = ext.modify_build + if fn then + make = fn(make) + end + end + return make +end + + +--- load one extension +-- @param name extension name +-- @param format current output format +function load_extension(name,format) + -- first test if the extension exists + local extension_library = "make4ht.extensions." .. 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 + local extension = require("make4ht.extensions.".. name) + -- extensions can test if the current output format is supported + local test = extension.test + if test then + if test(format) then + return extension + end + -- if the test fail return nil + return nil + end + -- if the extension doesn't provide the test function, we will assume that + -- it supports every output format + return extension end +--- load extensions +-- @param extensions table created by mkparams.get_format_extensions function +-- @param format output type format. extensions may support only certain file +-- formats +function load_extensions(extensions, format) + local module_names = {} + local extension_table = {} + -- process the extension table. it contains type field, which can enable or + -- diable the extension + for _, v in ipairs(extensions) do + local enable = v.type == "+" and true or nil + module_names[v.name] = enable + end + for name, _ in pairs(module_names) do + local extension = load_extension(name,format) + if extension then + table.insert(extension_table, extension) + else + print("Cannot load extension: ".. name) + end + end + return extension_table +end + +--- add new extensions to a list of loaded extensions +-- @param added string with extensions to be added in the form +ext1+ext2 +function add_extensions(added, extensions) + local _, newextensions = mkparams.get_format_extensions("dummyfmt" .. added) + -- insert new extension at the beginning, in order to support disabling using + -- the -f option + for _, x in ipairs(extensions or {}) do table.insert(newextensions, x) end + return newextensions +end +-- I don't know if this is clean, but settings functions won't be available +-- for filters and extensions otherwise +for k,v in pairs(env) do _G[k] = v end |