# [Tex/LaTex] Time travel in LaTeX

floatspositioningtwo-column

Is it possible to define a previouspage environment, so that whatever is inside it is output on the previous page?

Motivation: In a two-column document, the only way to get a full-width float to be placed on a given page is to put the float on the previous page. At best this is awkward, because it separates the source code of the float from where it logically should be in the tex code. Also, I have an application where my tex file is machine generated, so that doing this is impossible — the program generating the tex code isn't capable of looking at the pdf output and figuring out what landed on what page.

Possible implementation: I was thinking that this might be possible to implement by the following method. Use everyshi to write some kind of hook code on every page. On page 17, the code might say something like \input{page17}. When the previouspage environment is invoked on page 18, it generates a label, and after the document is compiled for the first time, the aux file will say that this label lies on page 18. When latex is run the second time, we read the aux file, we determine that the previouspage environment landed on p. 18, and therefore we write the stuff inside the previouspage environment to page17.tex. On the third compilation, this code is read back in from page17.tex.

A complication in this implementation is that we are hoping the pagination converges to what we want. To get it close to the final pagination on the initial pass, we need to make sure that the floats actually do show up in the document, although probably one page too late. There is no guarantee that the whole process will converge to the desired result after three iterations, or indeed after any number of iterations.

My tex fu is probably insufficient to do anything as fancy as implementing all of the above in pure latex, so if I were to do this myself, I would probably write an external script in some other programming language that would do some of the work. Or maybe something like this already exists…?

Related:

IEEEtran: Placing a two-column figure on desired page

How to set the position of an equation in LaTeX?

EDIT -- After posting the code below, I worked on it some more and got rid of some of the ugliness. Rather than continuing to update this answer, I made a project on github: https://github.com/bcrowell/timetravel

Below is the code for a proof-of-concept implementation of this idea.

The good news:

1. It accomplishes what I wanted to do for this application. In the sample below, we have a two-column document. A floating full-page-width table occurs in the source code at page 2, and is typeset at the top of page 2.

2. It should normally converge to a definite result after compiling the document three times. Compiling a fourth or subsequent times should not cause floats to move to different pages.

1. It's implemented as a separate ruby script that preprocesses the tex source code.

2. It won't work for the very first page of the document.

3. The float is inserted at the beginning of the first paragraph that lands on the desired page. To accomplish this, I had to use everyhook to place a hook at the beginning of every paragraph. This would cause an error if the first paragraph on the target page wasn't in outer paragraph mode. To work around this, any material that isn't normal paragraphs has to have \prevpagedisable above it and \prevpageenable below it.

Bad-news item #3 is quite ugly, and that's the most important reason that I would consider this no more than a proof of concept. A usenet post by Donald Aseneau suggests that there is no reliable way for latex code to detect whether it's in outer paragraph mode. The original idea I had when I wrote the question was to get the necessary hook using everyshi or eso-pic, but that doesn't work, because the material typeset by those packages is not in outer paragraph mode.

Sample LaTeX file:

\documentclass[twocolumn]{article}

\usepackage{prevpage}
\usepackage{lipsum}

\begin{document}

\lipsum[1-13]

% begin-prev-page
\begin{table*}
\begin{tabular}{p{30mm}p{30mm}p{30mm}p{30mm}}
John & Paul & George & Ringo
\end{tabular}
\end{table*}
% end-prev-page

\end{document}


Style file:

\RequirePackage{everyhook}

% This is a proof-of-concept package that allows us to implement "time travel" in LaTeX
% by causing a float to be invoked on the page before the page on which its source code
% occurs. This can be used in a two-column document to make a full-page-width float
% show up on the same page as the one on which it was invoked.
% http://tex.stackexchange.com/questions/314257/time-travel-in-latex

\newcounter{prevpageparctr}% a counter that labels each paragraph in the document sequentially

\newcommand{\inputifitexists}[1]{\IfFileExists{#1.tex}{\input{#1}}{}}
\newcommand{\kirk}{\inputifitexists{prev-page/par\theprevpageparctr}}
\newcommand{\spock}{\ifdim\emergencystretch>0pt{}\kirk\fi}
% Use \ifdim\emergencystretch>0pt to attempt to detect whether we're in outer paragraph
% mode. This won't always work, and in fact doesn't actually seem to work.
% http://comp.text.tex.narkive.com/ttqVg20H/test-for-outer-par-mode

\PushPreHook{par}{\stepcounter{prevpageparctr}\label{prevpagepar\theprevpageparctr}}
\newcommand{\prevpageenable}{\PushPreHook{par}{\spock}}
\newcommand{\prevpagedisable}{\PopPreHook{par}}
\prevpageenable


Ruby code:

#!/usr/bin/ruby

# usage: prev-page.rb foo.tex bar.tex
# Reads foo.tex, writes the preprocessed version to bar.tex.

require 'fileutils'
require 'digest'
require 'json'

$freeze_at_pass = 3 # Recompiling more than this many times should not change what pages floats land on. # This is normally 3, must be at least 2. def main() debug = false in_file = ARGV[0] out_file = ARGV[1] if in_file.nil? then fatal_error("no input file specified") end if out_file.nil? then fatal_error("no output file specified") end if !(File.exist?(in_file)) then fatal_error("input file #{in_file} does not exist") end aux_file = File.basename(out_file, ".tex") + ".aux"$temp_dir = "prev-page" # subdirectory of current working directory
$pass_file = "#{$temp_dir}/pass" # keep track of which pass we're on

pass = 1
if File.exist?(aux_file) then
if !(File.directory?($temp_dir)) then fatal_error("#{aux_file} exists, but directory #{$temp_dir} doesn't") end
pass = slurp_or_die($pass_file).to_i pass = pass+1 end if pass==1 then # make a clean temporary directory FileUtils.rm_rf$temp_dir
Dir.mkdir($temp_dir) end File.open($pass_file,'w') { |f|  f.print pass}
if debug then $stderr.print "pass=#{pass}\n" end page_numbers = {} if pass>=2 then if pass<=$freeze_at_pass then
get_page_numbers_from_aux_file(aux_file)
save_page_numbers
else
# Try to make sure it converges rather than oscillating indefinitely.
$aux_invoked,$aux_par = remember_page_numbers()
end
end
File.open(out_file,'w') { |f_out|
inside = false # are we currently inside or outside of a % begin-prev-page ... % end-prev-page block?
line_num = 0
code = '' # if inside a block, start accumulating a copy of the code here
line_num = line_num+1
if line=~/\s*%\s*begin-prev-page/ then
if inside then fatal_error("begin-prev-page twice in a row at line #{line_num}") end
inside = true
code = "\\prevpagedisable" # Don't place a hook inside the floating content itself.
end
if inside then code = code+line end
if line=~/\s*%\s*end-prev-page/ then
if !inside then fatal_error("end-prev-page occurs when not inside a prev-page block at line #{line_num}") end
inside = false
key = Digest::MD5.hexdigest(code)
#$stderr.print "hash=#{key}, code=#{code}=\n" if pass==1 then code_file = "#{$temp_dir}/#{key}.tex"
File.open(code_file,'w') { |code_f| code_f.print code+"\n\\prevpageenable" }
end
if pass>=2 then
if !$aux_invoked.key?(key) then fatal_error("aux file #{aux_file} doesn't contain key #{key}") end page =$aux_invoked[key]
if page>1 then page=page-1 end
if pass==2 then
par = $aux_par[page] File.open("#{$temp_dir}/par#{par}.tex",'a') { |f_page| f_page.print "\\input{prev-page/#{key}}"}
end
end
f_out.print "\\label{prevpageinvoked#{key}}" # This will be immediately followed by the % end-prev-page.
end
if pass<2 || !inside then f_out.print line end
# If pass is 2 or greater, don't duplicate the content of the block.
}
if inside then fatal_error("begin-prev-page ended at end of file") end
}
end

def save_page_numbers
File.open("#{$temp_dir}/freeze_aux_invoked.json",'w') { |f| f.print JSON.generate($aux_invoked)
}
File.open("#{$temp_dir}/freeze_aux_par.json",'w') { |f| f.print JSON.generate($aux_par)
}
end

def remember_page_numbers
return [
get_json_data_from_file_or_die("#{$temp_dir}/freeze_aux_invoked.json"), get_json_data_from_file_or_die("#{$temp_dir}/freeze_aux_par.json")
]
end

# Initializes $aux_invoked and$aux_par.
# Lines in aux file look like this:
#   \newlabel{prevpageinvoked226d375a2efab58c0ff60b659a2b5e70}{{}{2}}
#   \newlabel{prevpagepar14}{{}{2}}
def get_page_numbers_from_aux_file(aux_file)
$aux_invoked = {} # key=hash, value=page$aux_par = {}     # key=page, value=paragraph number
if line=~/\\newlabel{([^}]+)}{{([^}]*)}{([^}]+)}}/ then
label,number,page = $1,$2,$3.to_i if label=~/\Aprevpage(invoked|par)([^}]*)/ then type,key=$1,$2 if type=="invoked" then$aux_invoked[key]=page end
if type=="par" then
if $aux_par.key?(page) then if key<$aux_par[page] then $aux_par[page]=key end else$aux_par[page] = key
end
end
end
end
}
end

def fatal_error(message)
$stderr.print "generate_problems.rb: #{$verb} fatal error: #{message}\n"
exit(-1)
end

def warning(message)
$stderr.print "generate_problems.rb: #{$verb} warning: #{message}\n"
end

def get_json_data_from_file_or_die(file)
r = slurp_file_with_detailed_error_reporting(file)
if !(r[1].nil?) then fatal_error(r[1]) end
return parse_json_or_die(r[0])
end

def parse_json_or_die(json)
begin
return JSON.parse(json) # use minifier to get rid of comments
rescue JSON::ParserError
fatal_error("syntax error in JSON string '#{json}'")
end
end

# returns contents or nil on error; for more detailed error reporting, see slurp_file_with_detailed_error_reporting()
def slurp_file(file)
x = slurp_file_with_detailed_error_reporting(file)
return x[0]
end

def slurp_or_die(file)
x = slurp_file_with_detailed_error_reporting(file)
x = x[0]
return x
end

# returns [contents,nil] normally [nil,error message] otherwise
def slurp_file_with_detailed_error_reporting(file)
begin
File.open(file,'r') { |f|
t = f.gets(nil) # nil means read whole file
if t.nil? then t='' end # gets returns nil at EOF, which means it returns nil if file is empty
return [t,nil]
}
rescue
return [nil,"Error opening file #{file} for input: #{\$!}."]
end
end

main()


Makefile:

default:
make clean
prev-page.rb a.tex a2.tex
pdflatex a2
prev-page.rb a.tex a2.tex
pdflatex a2
prev-page.rb a.tex a2.tex
pdflatex a2
prev-page.rb a.tex a2.tex
pdflatex a2

clean:
rm -f *.aux
rm -Rf prev-page