michal-h21 / make4ht

Build system for tex4ht
137 stars 15 forks source link

Problem embedding several SVG-converted TiKZ pictures at once #124

Closed togrul-topal closed 1 year ago

togrul-topal commented 1 year ago

This thread refers to this StackExchange thread that I have found earlier. If works well with a single tikz. However, when I have several TiKZ pictures in my file, some problems appear.

\documentclass[10pt]{article}
\usepackage[T1]{fontenc}
\usepackage[english]{babel}
\usepackage{enumitem}
\usepackage{float}
\usepackage{tikz}
\usepackage{pgfplots}

\begin{document}
\begin{enumerate}[label=(\alph*)]
\item first graph
  \begin{figure}[H] \centering
    \begin{tikzpicture}[scale=0.90]
      \begin{axis}[axis lines=center, width=10cm, height=4cm, xmin=0, xmax=3.3, ymin=0, ymax=2.5, xtick={3}, ytick=\empty, xticklabel={60 min}, xlabel={$t$, time}, ylabel={$x(t)$, horizontal distance}, x label style = {at=(ticklabel cs:0.5),anchor=north}, y label style={at=(ticklabel cs:0.5),rotate=90,anchor=south}]
        \addplot[domain=0:3, samples=150, mark=none, color=red, ultra thick] {sin(deg(x-1.5)) +1};
      \end{axis}
    \end{tikzpicture}
  \end{figure}

\item second graph

  \begin{figure}[H] \centering
    \begin{tikzpicture}[scale=0.90]
      \begin{axis}[width=10cm, height=4cm, axis lines=center, xmin=0, xmax=5.5, ymin=0, ymax=1.5, xtick={5.277}, ytick=\empty, xticklabel={60 min}, xlabel={$t$, time}, x label style = {at={(ticklabel cs:0.5)},anchor=north}, ylabel={$y(t)$, altitude}, y label style={at=(ticklabel cs:0.5),rotate=90,anchor=south}]
        \addplot[domain=0:1.292, samples=50, mark=none, color=red, ultra thick] {sin(deg(pi/2*x))*x}; 
        \addplot[domain=1.292:4, samples=2, mark=none, color=red, ultra thick] {1.158}; 
        \addplot[domain=4:5.277, samples=100, mark=none, color=red, ultra thick] {1.158 - sin(deg((pi/2)*(x - 4)))*(x - 4)}; 
      \end{axis}
    \end{tikzpicture}
  \end{figure}

\item third graph

  \begin{figure}[H] \centering
    \begin{tikzpicture}[scale=0.90]
      \begin{axis}[axis lines=center, width=10cm, height=6cm, xmin=0, xmax=22, ymin=-4.2, ymax=4.2, ytick=\empty, y label style={anchor=east}, xtick={21.495}, xticklabel={60 min}, xlabel={$t$, time}, ylabel={$v$, vertical speed}, x label style = {at={(ticklabel cs:0.5)},anchor=north}, y label style={at=(ticklabel cs:0.5),rotate=90,anchor=south}]
        \addplot[domain=0.929:7.212, samples=150, mark=none, color=red, ultra thick] {2*sin(deg(x-2.5))+2};
        \addplot[domain=7.212:15.21, samples=150, mark=none, color=red, ultra thick] {0};
        \addplot[domain=15.21:21.495, samples=150, mark=none, color=red, ultra thick] {2*sin(deg((x-(2.5*pi)-12.07)))-2};
      \end{axis}
    \end{tikzpicture}
  \end{figure}
\end{enumerate}
\end{document}

I have the following build.lua file:

local domfilter = require "make4ht-domfilter"
local domobject = require "luaxml-domobject"

local process = domfilter {
  function(dom)
    for _, img in ipairs(dom:query_selector "img") do
      local file = img:get_attribute "src" or ""
      -- because we sometimes use imgdir option, script should not search in imgdir directory
      -- the next line therefore transformes imgdir/tikz.svg to tikz.svg
      local filename = file:match("^.+/(.+)$")
      -- process all SVG images 
      if filename:match("svg$") then
        -- open the SVG file and load it into a string
        local f = io.open(filename, "r")
        local content = f:read("*all")
        f:close()
        -- parse it to a new DOM object
        local newdom = domobject.parse(content)
        -- now find the <svg> element
        local root = newdom:root_node()
        -- <svg> should be a child of the root node
        for _, child in ipairs(root:get_children()) do
          -- replace <img> with <svg>
          if child:is_element() and child:get_element_name() == "svg" then
            img:replace_node(child)
          end
        end
      end
    end
    return dom
  end
}

-- call the LuaXML dom processing on all HTML files
Make:match("html$", process)

I run everything with

make4ht --build-file build.lua markup.tex 'mathjax'
michal-h21 commented 1 year ago

Try this version of the build file:

local domfilter = require "make4ht-domfilter"
local domobject = require "luaxml-domobject"

local function fix_ids(dom, prefix)
  -- we need to prevent duplicate ID attributes in included SVG, so we add a prefix to them
  dom:traverse_elements(function(element)
    local id = element:get_attribute("id")
    if id then
      element:set_attribute("id",  prefix .. id)
    end

    local xlink = element:get_attribute("xlink:href")
    if xlink and xlink:sub(1,1) == "#"  then
      element:set_attribute("xlink:href", "#" .. prefix .. xlink:sub(2))
    end

    local clip = element:get_attribute("clip-path")
    if clip then
      element:set_attribute("clip-path", clip:gsub("%#", "#" .. prefix))
    end

  end)

end

local process = domfilter {
  function(dom)
    for count, img in ipairs(dom:query_selector "img") do
      local file = img:get_attribute "src" or ""
      -- because we sometimes use imgdir option, script should not search in imgdir directory
      -- the next line therefore transformes imgdir/tikz.svg to tikz.svg
      local filename = file:match("^.+/(.+)$")
      if not filename then filename = file end
      -- process all SVG images 
      if filename:match("svg$") then
        -- open the SVG file and load it into a string
        local f = io.open(filename, "r")
        local content = f:read("*all")
        f:close()
        -- parse it to a new DOM object
        local newdom = domobject.parse(content)
        fix_ids(newdom, "x" .. count .. "-")
        -- now find the <svg> element
        local root = newdom:root_node()
        -- <svg> should be a child of the root node
        for _, child in ipairs(root:get_children()) do
          -- replace <img> with <svg>
          if child:is_element() and child:get_element_name() == "svg" then
            img:replace_node(child)
          end
        end
      end
    end
    return dom
  end
}

-- call the LuaXML dom processing on all HTML files
Make:match("html$", process)
togrul-topal commented 1 year ago

Spot on, IDs were the problem!

Not the hero we deserve but the hero we need.