TransWikia.com

Multilines, multipages phantom analog macro

TeX - LaTeX Asked by alfazaz on October 2, 2021

I need a macro to be able, in pdflatex (EDIT : or lualatex if necessary), to remove some text/formulas/images which can go on many paragraphs and pages, without changing the layout (there must be an empty space with the exact same size at its place).

This would work like phantom but, as an environment and accepting many paragraphs, page breaks etc… I thought it already exists but I can’t find any solution (it could be a good idea to have more general phantomplus and phantomplusenv macros – idea for a phantomplus package ? 😉 ).

For now, I simply use a color{white} to hide text|formulas but it’s not sufficient (text is still in the pdf, is selectable etc…).

I saw How to replace a large block of text by an empty block of the same size? but there is no available solution in it for my situation.

If it is a good and realistic idea, how to do this ? Or other idea (with lualatex ?) ?

3 Answers

In LuaLaTeX, this can be implemented similar to lua-ul and luacolor: Use an attribute to mark all text that should be removed, then hook into the shipout routine to delete/replace with empty space:

Create a Lua file hideme.lua with (explanations inline)

local set_func = luatexbase.new_luafunction'hideme.set_attribute'
local reset_func = luatexbase.new_luafunction'hideme.reset_attribute'
local process_func = luatexbase.new_luafunction'hideme.process_attribute'
local functions = lua.get_functions_table()

-- Define the attribute we use as marker
local attr = luatexbase.new_attribute'hide_marker'

-- This function will later activate the hiding. It could be implemented in TeX, but then we would have to make the attribute number available there
functions[set_func] = function()
  tex.attribute[attr] = 1
end
functions[reset_func] = function()
  tex.attribute[attr] = -0x7FFFFFFF
end

-- Just some shorter names to improve readability and performance
local glue_id = node.id'glue'
local vlist_id = node.id'vlist'
local hlist_id = node.id'hlist'
local whatsit_id = node.id'whatsit'
local rule_id = node.id'rule'
local direct = node.direct
local setglue = direct.setglue
local getid = direct.getid
local todirect = direct.todirect
local getlist = direct.getlist
local setlist = direct.setlist
local getleader = direct.getleader
local traverse = direct.traverse
local free = direct.free
local flush_list = direct.flush_list
local has_attribute = direct.has_attribute
local rangedimensions = direct.rangedimensions
local getprev = direct.getprev
local slide = direct.slide
local node_new = direct.new
local setlink = direct.setlink
local flatten_discretionaries = direct.flatten_discretionaries

-- We later want to remove nodes while we are traversing over them, so add a helper which ensures that deletion gets delayed until we no longer need to look at the node
local delayed_free do
  local delayed
  function delayed_free(n)
    if delayed then free(delayed) end
    delayed = n
  end
end

local do_vhide

-- Iterate over a horizontal list and hide marked nodes:
local function do_hhide(parent, list)
  local work_done, begin_hide
  list = flatten_discretionaries(list) -- Nobody likes disc nodes anyway
  slide(list) -- Ensure that we can use getprev
  for n, id, sub in traverse(list) do
    local hide_this
    -- We have to recursivly visit vlist and hlist nodes
    if id == vlist_id then
      do_vhide(n)
    elseif id == hlist_id then
      setlist(n, (do_hhide(n, getlist(n))))
    -- Everything else gets deleted if it is marked
    elseif has_attribute(n, attr) then
      hide_this = true
      if not begin_hide then
        -- Actually we don't really delete yet, we only mark for deletion
        begin_hide = n
      end
    -- Again, recursively iterate leaders
    elseif id == glue_id and sub >= 100 then -- leaders
      local leader = getleader(n)
      local leader_id = leader and getid(leader)
      if leader_id == hlist_id then
        setlist(leader, do_hhide(leader, getlist(leader)))
      elseif leader_id == vlist_id then
        do_vhide(leader)
      end -- else rule --> ignore
    end
    if not hide_this and begin_hide then
      -- Now we have to actually remove the nodes from begin_hide to this point. Let's first measure what we got:
      local nglue = node_new(glue_id)
      setglue(nglue, rangedimensions(parent, begin_hide, n))
      -- Remove n from the list starting at begin_hide
      setlink(getprev(n), nil)
      -- And integrate nglue in the list without the deleted nodes
      if list == begin_hide then
        list = setlink(nglue, n)
      else
        setlink(getprev(begin_hide), nglue, n)
      end
      -- Now we can delete the list of hidden nodes
      flush_list(begin_hide)
      begin_hide = nil
      work_done = true
    end
  end
  if begin_hide then
    -- We end with some hidden nodes. No need for glue here, just delete them
    if list == begin_hide then
      list = nil
    else
      setlink(getprev(begin_hide), nil)
    end
    flush_list(begin_hide)
    work_done = true
  end
  return list, work_done
end
-- In vboxes, the situation is a bit different. It is harder to measure nodes here because rangedimensions doesn't work, but very few node types actually have to be hidden
function do_vhide(parent)
  local list = getlist(parent)
  for n, id, sub in traverse(list) do
    -- Again recurse into the usual suspects (No discretionaries here)
    if id == vlist_id then
      do_vhide(n)
    elseif id == hlist_id then
      setlist(n, (do_hhide(n, getlist(n))))
    elseif has_attribute(n, attr) then
      -- Here we actually remove directly
      if id == glue_id and sub >= 100 then -- leaders
        -- Just convert them into "regular" glue
        flush_list(getleader(n))
        direct.setleader(n, nil)
        direct.setsubtype(n, 0)
      elseif id == rule_id and sub ~= 3 then
        -- rules (also includes images etc.) Convert into invisible rules
        direct.setsubtype(n, 3) -- empty rule
      elseif id == whatsit_id then
        -- whatsit - We don't know what they do exactly, so better delete it completly
        list = direct.remove(list, n)
        delayed_free(n)
      end
    elseif id == glue_id and sub >= 100 then -- leaders
      local leader = getleader(n)
      local leader_id = leader and getid(leader)
      if leader_id == hlist_id then
        setlist(leader, do_hhide(leader, getlist(leader)))
      elseif leader_id == vlist_id then
        do_vhide(leader)
      end -- else rule --> ignore
    end
  end
  setlist(parent, list)
end

-- Now just dome driver to call the function above for a given box
functions[process_func] = function()
  local box = todirect(tex.box[token.scan_int()])
  local box_id = box and getid(box)
  if box_id == hlist_id then
    setlist(box, do_hhide(box, getlist(box)))
  else
    do_vhide(box)
  end
  delayed_free()
end

-- And give TeX accessible names to our functions
token.set_lua('HideMeStart', set_func, 'global', 'protected')
token.set_lua('HideMeReset', reset_func, 'global', 'protected')
token.set_lua('HideMeProcessBox', process_func, 'global', 'protected')

You can use this from TeX as (based on Ulrike's example)

documentclass{article}
usepackage{transparent}
usepackage{lipsum}
usepackage{graphicx}
usepackage{atbegshi}
directlua{require'hideme'}
makeatletter
% Don't hide content inserted in the output routine
outputexpandafterexpandafterexpandafter{expandafterexpandafterexpandafterHideMeResetexpandafter@firstofonetheoutput}
makeatother
AtBeginShipout{HideMeProcessBoxAtBeginShipoutBox}
begin{document}
abc

begingroup
some text [a=b=2] more text
rule{1cm}{1cm}

includegraphics[width=5cm]{example-image-duck}
endgroup

blub

abc
begingroupHideMeStart
some text [a=b=2] more text
rule{1cm}{1cm}

includegraphics[width=5cm]{example-image-duck}
endgroup

blub

end{document}

enter image description here

Correct answer by Marcel Krüger on October 2, 2021

A multi-line, single-page workaround is to use pgfsys@begininvisible and pgfsys@endinvisible provided by pgf package, the backend of tikz.

Contents in between these two commands are actually typeset but with a large amount of shift (x = 20000bp, y = 20000bp). The overlay utility in beamer class uses this pair of commands as well.

documentclass{article}
usepackage{lipsum}
usepackage{pgf}

begin{document}
makeatletter
lipsum[1]
pgfsys@begininvisible
lipsum[2] % the output of this line is moved out of the page
pgfsys@endinvisible
lipsum[3]
makeatother
end{document}

Answered by muzimuzhi Z on October 2, 2021

You could use the transparent package. But it will make the content only transparent, it is still there, e.g. for copy&paste:

documentclass{article}
usepackage{transparent}
usepackage{lipsum}
usepackage{graphicx}
begin{document}
abc

begingroup
some text [a=b=2] more text
rule{1cm}{1cm}

includegraphics[width=5cm]{example-image-duck}
endgroup

blub

abc
begingrouptransparent{0}
some text [a=b=2] more text
rule{1cm}{1cm}

includegraphics[width=5cm]{example-image-duck}
endgroup

blub

end{document}

enter image description here

Answered by Ulrike Fischer on October 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP