以下代码缓存包括。这消除了使用 的需要\includeonly
。问题是,为了使引用、计数器、目录正确,需要导入相应的辅助文件。不幸的是,我似乎无法让它做任何正确的事情(我尝试了很多不同的方法)。
基本上,当我手动将 aux 文件内容复制并粘贴到 tex 文件然后进行编译时,我会得到所需的结果,但是当我通过 lua 执行等效操作时,我什么也得不到。就好像插入的 tex 被完全忽略了一样。
这个项目有点复杂,但我会把它分解开来,因为有些人似乎很容易感到困惑或跳到简单的结论:
这需要 Windows。稍加修改即可在 Linux 或其他操作系统上运行。主要问题是执行简单的文件操作(例如复制)时需要经过一些步骤,因为 TeX 的设计很差。
以下所有文件都直接放在同一个
TeXCache.lua——主要的 Lua 缓存代码
if __INCLUDES__TeXCache ~= nil then
else
__INCLUDES__TeXCache = true
if __DEBUG == true then
print("Including TeXCache.lua")
end
require "md5"
require "lfs"
require "persist"
------------------------------------------ Utility functions
if not file then file = {} end
function file.Exists(name) local f = io.open(name, "r") if f ~= nil then io.close(f) return true end return false end
function FileExists(name) local f = io.open(name, "r") if f ~= nil then io.close(f) return true end return false end
function FileSize(name) local f = io.open(name, "r") if f == nil then return -1 end local current = f:seek() local size = f:seek("end") f:seek("set", current) io.close(f) return size end
function split(str, pat) local t = {} local fpat = "(.-)" .. pat local last_end = 1 local s, e, cap = str:find(fpat, 1) while s do if s ~= 1 or cap ~= "" then table.insert(t,cap) end last_end = e+1 s, e, cap = str:find(fpat, last_end) end if last_end <= #str then cap = str:sub(last_end) table.insert(t, cap) end return t end
function string.trim(s, c) if not c then c = " \t" end return s:match('^['..c..']*(.-)['..c..']*$') end
function string.split(str, sep, n)
local sep, fields = sep or ":", {}
--local pattern = string.format("([^%s]+)", sep)
local pattern = '[^'..sep..']'
if n == 0 or n == nil then pattern = '('..pattern..'+)' end
if n > 0 then pattern = '^'..(pattern..'+'):rep(n)..'('..pattern..'+)' end
str:gsub(pattern, function(c) fields[#fields + 1] = c end)
return fields
end
function DeepCopyTable(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[DeepCopyTable(orig_key)] = DeepCopyTable(orig_value)
end
setmetatable(copy, DeepCopyTable(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function CopyFile(source, dest)
local infile = io.open(source, "rb")
instr = infile:read("*a")
infile:close()
local outfile = io.open(dest, "wb")
outfile:write(instr)
outfile:close()
end
function ReadFile(source, text)
if not text then text = "rb" else text = "r" end
local infile = io.open(source, text)
if not infile then return nil end
instr = infile:read("*a")
infile:close()
return instr
end
function WriteFile(source, data)
local outfile = io.open(source, "wb")
instr = infile:write(data)
outfile:close()
end
function DeleteFile(Filename)
-- windows only. os.remove has problems!?
os.execute('del /Q "'..Filename:gsub("/","\\")..'" > nul')
end
--------------------------------------------- Setup
TeXCache = {}
TeXCache.Arguments = "";
TeXCache.ModeAfter = "\\batchmode"
TeXCache.Disabled = false
TeXCache.FileExt = "_TeXCache.tex"
TeXCache.CacheDir = "TeXCache/"
TeXCache.HistoryFilename = "TeXCacheHistory.dat";
TeXCache.Header = ""
TeXCache.ForceRegenerate = false
TeXCache.Count = 0
TeXCache.History = {}
TeXCache.Temp = {}
--------------------- Preprocessing TeX
function StartProcessingTeX()
-- Load TeXCache.History and set all values to nil, If they are cached, they will be set to 1 at the end and the nil values keys/files can be removed
TeXCache.History = persistence.load(TeXCache.Path..TeXCache.CacheDir..TeXCache.HistoryFilename);
if TeXCache.History then
if not TeXCache.History.Cached then
TeXCache.History.Cached = {}
end
else
TeXCache.History = {}
TeXCache.History.Cached = {}
end
local curPage = -1
local line
-- Read the synctex file and attempt to match input file with pages, onces the pages that the input file spans is determined,
TeXCache.Temp.ValidCache = false
TeXCache.Temp.Synctex = {}
TeXCache.Temp.Synctex.Input_ShortFiles = {}
TeXCache.Temp.Synctex.Input_Indexes = {}
-- Read Synctex If exists, else cannot cache. Stores page relations to input file relations
if FileExists(TeXCache.Path..TeXCache.JobName..".synctex") then
local f = io.open(TeXCache.Path..TeXCache.JobName..".synctex", "r")
if f ~= nil then
while true do
line = f:read()
if line == nil then break end
-- Get Input files and store in three way lookup
if (string.sub(line,1,6) == "Input:") then
local idx = tonumber(split(line, ":")[2])
line = string.sub(line,8 + #tostring(idx),-1)
line = string.gsub(string.gsub(line, "\\./", "\\"), "/", "\\")
local files = split(line, "\\")
local file = files[#files]
local file2 = split(file, "%.")
-- remove tex extension as it is default(we assume that inputs must have an extension of some type, else collisions may occur)
if file2[#file2] == "tex" then
file2[#file2] = nil
if #file2 > 1 then
file2 = table.concat(file2, ".")
else
file2 = file2[#file2]
end
file = file2
end
TeXCache.Temp.Synctex.Input_ShortFiles[file] = {}
TeXCache.Temp.Synctex.Input_ShortFiles[file].idx = idx
TeXCache.Temp.Synctex.Input_ShortFiles[file].file = file
TeXCache.Temp.Synctex.Input_ShortFiles[file].min = 100000
TeXCache.Temp.Synctex.Input_ShortFiles[file].max = -1
TeXCache.Temp.Synctex.Input_Indexes[idx] = {}
TeXCache.Temp.Synctex.Input_Indexes[idx].idx = idx
TeXCache.Temp.Synctex.Input_Indexes[idx].file = file
TeXCache.Temp.Synctex.Input_Indexes[idx].min = 100000
TeXCache.Temp.Synctex.Input_Indexes[idx].max = -1
elseif (string.sub(line,1,1) == "{") then
curPage = string.sub(line,2,-1);
elseif #line > 1 then
local b = string.sub(line,1,1)
if b == "[" or b == "(" or b == "v" or b == "h" or b == "x" or b == "k" or b == "g" or b == "$" then
local idx = tonumber(split(string.sub(line,2,-1), ",")[1])
if not TeXCache.Temp.Synctex.Input_Indexes[idx] then TeXCache.Temp.Synctex.Input_Indexes[idx] = {} end
local lp = math.min(TeXCache.Temp.Synctex.Input_Indexes[idx].min or 100000, curPage)
local hp = math.max(TeXCache.Temp.Synctex.Input_Indexes[idx].max or -1, curPage)
local file = TeXCache.Temp.Synctex.Input_Indexes[idx].file
if file then
TeXCache.Temp.Synctex.Input_Indexes[idx].min = lp
TeXCache.Temp.Synctex.Input_Indexes[idx].max = hp
TeXCache.Temp.Synctex.Input_ShortFiles[file].min = lp
TeXCache.Temp.Synctex.Input_ShortFiles[file].max = hp
end
end
end
end
TeXCache.Temp.Synctex.Exists = true
end
f:close()
end
end
function PreBeginDocument()
end
function TocWriteAux()
-- Import aux file preambles headers
for key, value in pairs(TeXCache.History.Cached) do
local s = ReadFile(TeXCache.Path..TeXCache.CacheDir.."Cached_"..key..'.aux', 1)
if s then
local loc = string.find(s, "\\@setckpt{")
s = s:sub(1,loc-1)
tex.print("\\makeatletter")
tex.print(s)
tex.print("\\makeatother")
print("\n\n\n\r\n\\makeatletter\r\n"..s.."\r\n\\makeatother\r\n\n\n\n")
end
end
end
--------------------- End Of Processing -------------------------
function DoneProcessingTeX()
persistence.store(TeXCache.Path..TeXCache.CacheDir..TeXCache.HistoryFilename, TeXCache.History);
--print("\nTABLE----------------------------"); print(inspect(TeXCache.History)); print("\nTABLE----------------------------------");
print("\n----Total Includes Cached = "..TeXCache.Count.."\n")
if TeXCache.Backups then
for key, value in pairs(TeXCache.Backups) do
-- Create backup of pdf for later use, could optimize duplicate files away
-- We must redirect the copy to a batch file asychronously because TeX people suck ass(too lame)
--CopyFile(TeXCache.Path..TeXCache.JobName..".pdf", TeXCache.Path..TeXCache.CacheDir.."Cached_"..key..".pdf")
if value == 1 then
os.execute('Start "copy" /min '..TeXCache.Path..'CopyFile.bat "'..TeXCache.Path..TeXCache.JobName..".pdf"..'" "'..TeXCache.Path..TeXCache.CacheDir.."Cached_"..key..'.pdf"')
os.execute('Start "copy" /min '..TeXCache.Path..'CopyFile.bat "'..TeXCache.Path..key..".aux"..'" "'..TeXCache.Path..TeXCache.CacheDir.."Cached_"..key..'.aux"')
end
end
end
-- Another hoop to jump through because of the ignorant TeX people. syntex(busy) won't be renamed when using os.execute above for some ignorant reason
os.execute('Start "copy" /min '..string.gsub('CopyFile "'..TeXCache.Path..TeXCache.JobName..'.synctex(busy)" "'..TeXCache.Path..TeXCache.JobName..'.synctex"', "/", "\\"))
end
-- Function callbacks must be above
luatexbase.add_to_callback("stop_run", DoneProcessingTeX, "pdfextract3")
-------------------------------------- Caching code
function TeXCache.Initialize(JobName, path)
-- To write aux files without getting strange write error one must set MIKTEX_ALLOWUNSAFEOUTPUTFILES=1 in environment for miktex.
-- Get base_dir using dos if necessary
if not path then local p = io.popen("echo %cd%") if p then path = p:read("*l"):trim("\\").."\\" p:close() end end
if path == nil or path == "" then path = lfs.currentdir() end
path = (path or ""):trim('"\' \\/').."/"
JobName = JobName:trim('"\' /') or "temp"
TeXCache.JobName = JobName
TeXCache.Path = path
print('Path = '..TeXCache.Path)
StartProcessingTeX()
end tex.print("\\directlua{TeXCache.Initialize('\\jobname', '\\BasePath')}")
function TeXCache.Include(code)
-- compare hash of included file with previously stored hash, if the same, then use cache
-- could use filesize/date comparision for quicker tests
local codefilename = TeXCache.Path..code..".tex"
local fs = -1
local hash = -1
local curIdx = -1
local lp = -1
local hp = -1
if FileExists(codefilename) then
fs = FileSize(codefilename)
if TeXCache.History.Cached[code] and TeXCache.History.Cached[code].FileSize == fs then
hash = md5.sumhexa(ReadFile(codefilename))
if TeXCache.History.Cached[code].Hash == hash then
local idx = -1
if TeXCache.Temp.Synctex.Input_ShortFiles[code] then
curIdx = TeXCache.Temp.Synctex.Input_ShortFiles[code].idx
lp = math.max(TeXCache.Temp.Synctex.Input_ShortFiles[code].min, TeXCache.History.Cached[code].LowPage)
hp = math.max(TeXCache.Temp.Synctex.Input_ShortFiles[code].max, TeXCache.History.Cached[code].HighPage)
else
lp = TeXCache.History.Cached[code].LowPage
hp = TeXCache.History.Cached[code].HighPage
end
if lp < 0 or hp < 0 or lp >= 99999 then
TeXCache.Temp.ValidCache = false
else
TeXCache.Temp.ValidCache = true
end
else
TeXCache.Temp.ValidCache = false
end
else
TeXCache.Temp.ValidCache = false
end
else
TeXCache.Temp.ValidCache = false
end
if TeXCache.Temp.ValidCache == false then
tex.print("\\include{"..code.."}")
if TeXCache.Backups == nil then
TeXCache.Backups = {}
end
TeXCache.Backups[code] = 1
-- Store Cached information
if FileExists(codefilename) then
fs = FileSize(codefilename)
hash = md5.sumhexa(ReadFile(codefilename))
end
if TeXCache.History.Cached[code] then
fs = fs
hash = hash
curIdx = curIdx
lp = math.max(lp, TeXCache.History.Cached[code].LowPage or -1);
hp = math.max(hp, TeXCache.History.Cached[code].HighPage or -1);
end
TeXCache.History.Cached[code] = {}
TeXCache.History.Cached[code].FileSize = fs
TeXCache.History.Cached[code].Hash = hash
TeXCache.History.Cached[code].LowPage = lp
TeXCache.History.Cached[code].HighPage = hp
return
end
for key, value in pairs(TeXCache.History.Cached) do
if code == key then
print("\nUsing cached `"..code..".tex!\n")
local filename = TeXCache.Path..TeXCache.CacheDir.."Cached_"..code
tex.print("\\includepdf[pages={"..lp.."-"..hp.."}]{"..filename.."}")
-- Import aux file counters
local s = ReadFile(TeXCache.Path..TeXCache.CacheDir.."Cached_"..key..'.aux')
if s then
local _, loc = string.find(s, "\\@setckpt{"..code.."}{")
s = s:sub(loc+1, -1)
for i=#s, 1, -1 do
if s:sub(i,i) == "}" then
s = string.sub(s,1,i-1)
break;
end
end
s = "\r\n\\makeatletter\r\n"..s.."\r\n\\makeatother\r\n"
tex.print(s)
print("\r\n\r\n\r\n"..s.."\r\n\r\n\r\n")
end
-- Store cached info for use on next run
TeXCache.History.Cached[code].FileSize = fs
TeXCache.History.Cached[code].Hash = hash
TeXCache.History.Cached[code].LowPage = lp
TeXCache.History.Cached[code].HighPage = hp
TeXCache.Count = TeXCache.Count + 1
break
end
end
end
end -- __INCLUDE__
CopyFile.bat-用于异步复制文件
@echo off
@REM use asynchronously to copy a file after a process as exited(set timeout to be long enough for process to end).
@REM Call with Start "copy file" <path to this batch file> <source> <dest>
TIMEOUT 3 /NOBREAK
copy /B /Y %1 %2
exit
Clear.bat-清除垃圾文件以删除缓存(开始重置所有内容)
@echo off
del test.synctex(busy)
del test.synctex
del test.pdf
del test.toc
del test.log
del test.aux
del TeXCache\TeXCacheHistory.dat
del TeXCache\Cached_testinput.pdf
del TeXCache\Cached_testinput.aux
persist.lua-用于存储和检索 lua 表(可从 git hub 获取)
--t_original = {1, 2, ["a"] = "string", b = "test", {"subtable", [4] = 2}};
--persistence.store("storage.lua", t_original);
--t_restored = persistence.load("storage.lua");
local write, writeIndent, writers, refCount;
persistence =
{
store = function (path, ...)
local file, e = io.open(path, "w");
if not file then
return error(e);
end
local n = select("#", ...);
-- Count references
local objRefCount = {}; -- Stores reference that will be exported
for i = 1, n do
refCount(objRefCount, (select(i,...)));
end;
-- Export Objects with more than one ref and assign name
-- First, create empty tables for each
local objRefNames = {};
local objRefIdx = 0;
file:write("-- Persistent Data\n");
file:write("local multiRefObjects = {\n");
for obj, count in pairs(objRefCount) do
if count > 1 then
objRefIdx = objRefIdx + 1;
objRefNames[obj] = objRefIdx;
file:write("{};"); -- table objRefIdx
end;
end;
file:write("\n} -- multiRefObjects\n");
-- Then fill them (this requires all empty multiRefObjects to exist)
for obj, idx in pairs(objRefNames) do
for k, v in pairs(obj) do
file:write("multiRefObjects["..idx.."][");
write(file, k, 0, objRefNames);
file:write("] = ");
write(file, v, 0, objRefNames);
file:write(";\n");
end;
end;
-- Create the remaining objects
for i = 1, n do
file:write("local ".."obj"..i.." = ");
write(file, (select(i,...)), 0, objRefNames);
file:write("\n");
end
-- Return them
if n > 0 then
file:write("return obj1");
for i = 2, n do
file:write(" ,obj"..i);
end;
file:write("\n");
else
file:write("return\n");
end;
if type(path) == "string" then
file:close();
end;
end;
load = function (path)
local f, e;
if type(path) == "string" then
f, e = loadfile(path);
else
f, e = path:read('*a')
end
if f then
return f();
else
return nil, e;
end;
end;
}
-- Private methods
-- write thing (dispatcher)
write = function (file, item, level, objRefNames)
writers[type(item)](file, item, level, objRefNames);
end;
-- write indent
writeIndent = function (file, level)
for i = 1, level do
file:write("\t");
end;
end;
-- recursively count references
refCount = function (objRefCount, item)
-- only count reference types (tables)
if type(item) == "table" then
-- Increase ref count
if objRefCount[item] then
objRefCount[item] = objRefCount[item] + 1;
else
objRefCount[item] = 1;
-- If first encounter, traverse
for k, v in pairs(item) do
refCount(objRefCount, k);
refCount(objRefCount, v);
end;
end;
end;
end;
-- Format items for the purpose of restoring
writers = {
["nil"] = function (file, item)
file:write("nil");
end;
["number"] = function (file, item)
file:write(tostring(item));
end;
["string"] = function (file, item)
file:write(string.format("%q", item));
end;
["boolean"] = function (file, item)
if item then
file:write("true");
else
file:write("false");
end
end;
["table"] = function (file, item, level, objRefNames)
local refIdx = objRefNames[item];
if refIdx then
-- Table with multiple references
file:write("multiRefObjects["..refIdx.."]");
else
-- Single use table
file:write("{\n");
for k, v in pairs(item) do
writeIndent(file, level+1);
file:write("[");
write(file, k, level+1, objRefNames);
file:write("] = ");
write(file, v, level+1, objRefNames);
file:write(";\n");
end
writeIndent(file, level);
file:write("}");
end;
end;
["function"] = function (file, item)
-- Does only work for "normal" functions, not those
-- with upvalues or c functions
local dInfo = debug.getinfo(item, "uS");
if dInfo.nups > 0 then
file:write("nil --[[functions with upvalue not supported]]");
elseif dInfo.what ~= "Lua" then
file:write("nil --[[non-lua function not supported]]");
else
local r, s = pcall(string.dump,item);
if r then
file:write(string.format("loadstring(%q)", s));
else
file:write("nil --[[function could not be dumped]]");
end
end
end;
["thread"] = function (file, item)
file:write("nil --[[thread]]\n");
end;
["userdata"] = function (file, item)
file:write("nil --[[userdata]]\n");
end;
}
test.tex - 要测试的演示 tex 文件
\documentclass[12pt,oneside]{book}
%\batchmode
\directlua{
require("debugger")()
}
\directlua{
% Must be ran first, sets up paths
__DEBUG = true
local path = BasePath
local p = io.popen('echo '..string.char(37)..'cd'..string.char(37)) if p and (path == nil or path == "") then path = p:read("*l") p:close() end
if path == nil or path == "" then path = lfs.currentdir() end
if path == nil or path == "" then else BasePath = path end
path = string.gsub(path, string.char(92), "/");
BasePath = string.gsub(BasePath, string.char(92), "/")
tex.print(-1, string.char(92).."def"..string.char(92).."BasePath"..string.char(123)..BasePath..string.char(125))
}
\directlua{
dofile('TeXCache.lua')
}
\usepackage{luatex}
\usepackage{xcolor}
\usepackage{everypage}
\usepackage{afterpage}
\usepackage{pdfpages}
\usepackage{everyhook}
\usepackage{ifthen}
\usepackage{letltxmacro}
\usepackage{atbegshi}
\usepackage{lipsum}
\directlua{tex.enableprimitives('',tex.extraprimitives())}
\newcommand{\tableofcontentsEx}{\tableofcontents\directlua{TocWriteAux()}}
\newcommand{\cachedInput}[1]{\clearpage\directlua{TeXCache.Include("\luaescapestring{#1}"); }\clearpage}
\title{Sections and Chapters}
\author{Test Author}
\date{ }
\directlua{if PreBeginDocument then PreBeginDocument() end}
\begin{document}
\maketitle
\tableofcontentsEx
\clearpage
\lipsum
%This either includes or includespdf when cached.
\cachedInput{testinput}
slfkljasfasfjllasfjas;df
234214214892784
\chapter{HELP!!!}
asdfjhajsdfkljasjdf
a
asdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
\end{document}
testinput.tex - 演示输入文本文件 - 可以是任何普通的 tex 文件
\chapter{AMAZING!}
{{{}}}{}{}{}{}{}{}
$$x =
y + z + z^2 + z^{311}
$$
\begin{tabular}{|c|c|}
\hline
% after \\: \hline or \cline{col1-col2} \cline{col3-col4} ...
1 & 1 \\
2 & 2 \\
\hline
\end{tabular}
$$x =
y
$$
$z =
x
$
A\lipsum
\{
\textcolor{red}{
This is a test cache1
B\lipsum
\textcolor{green}{C\lipsum}
}
$$x =
y + z + z^2 + z^43434
$$
\lipsum
\lipsum
\lipsum
$z =
x
$
D\lipsum
test
xxxxxxxxxxxxxxxx
这些就是我们需要的所有文件。完整项目可以在http://www.filedropper.com/test_192
如果代码指定了行号,那么我就可以引用处理辅助文件的块了。无论如何,TeXCache.lua 中实际上只有两个部分处理辅助文件。
一种是尝试使用 TocWriteAux() 在前言中加载它,另一种是在 TeXCache.Include 函数中加载它。这发生在它说“-- 导入辅助文件计数器”的地方。
我尝试将 aux 文件拆分为 setckpt 之前的内容和之后的内容,因为简单地使用“\input”不起作用。
可以删除辅助代码并清空 TocWriteAux(),这样 lua 代码中就不会有任何辅助行为。我之所以包含它,只是为了在出现问题时可以对其进行修改。
再次强调,主要问题是当我在 lua 中“tex.print”辅助代码时,它不起作用,但当我复制和粘贴时它却起作用。为什么?我不知道!这就是我问的原因。请不要用一般性的答案来“帮助”,而是提供一个解决方案,使测试项目按预期工作(任何不能解决这个问题的答案都不是答案。它也应该在一般情况下有效(至少 95% 的测试用例)。)我已经不遗余力地提供了一个“MWE”,我希望任何试图提供帮助的人都能这样做,而不是试图获得“金星”或其他什么。
那么,预期的行为是什么?
目录始终是正确的。我意识到目录可能需要额外的编译才能正确更新,但这是目录设计的一般问题,而不是缓存机制的问题。
章节计数器始终是正确的。它们在第一次运行后是正确的,但是当缓存机制启动时,它们就错了,因为辅助文件未被使用。其他计数器等也应正确导入。辅助文件的全部目的是使这些东西保持同步,因此无论缓存设计如何,它都应该按应有的方式运行。
例如,经过几次编译测试后,“帮助”章节应该是 2,但实际是 1。可以直接插入辅助文件(复制并粘贴),然后就可以正常工作。因此,为什么 lua 代码(本质上做同样的事情,至少在我尝试拆分之前我尝试过)不起作用,这让我无法理解。
lua 代码不应该是导入辅助信息的必要条件吗?有人告诉我这个,但我试过的方法不起作用。例如,在以下行之后
tex.print("\includepdf[pages={"..lp.."-"..hp.."}]{"..filename.."}")
可以添加
tex.print("\\input{"..filename..".aux}")
并且它应该导入 aux 文件,但它什么也没做。就像直接将其添加到 tex 中一样。
无论如何,希望有人能花时间来实现这一点。能够缓存包含内容是一个很好的功能,对于运行缓慢的项目来说,可以大大节省速度。虽然与每页缓存相比,它有点太过精细,但总比没有好,避免了我们目前使用的“仅包含”问题。
我期望答案很简单,因为它只是做了 tex 已经做的事情……例如,包含辅助文件。但要么是我搞砸了,要么就是它没有正常工作(一个错误或由于 TeXCache 代码的一些奇怪问题)。
请注意,另一个错误是 synctex 文件未正确关闭。发生这种情况的原因似乎是另一个错误。它之所以发生只是因为我将 pdf 文件的复制异步委托给了操作系统。这是因为没有后 pdf 回调。如果有人试图通过 lua(stop_run 回调或其他方式)在“末尾”复制 pdf,则 pdf 不会完全完成并且无效。这些问题和解决方法是由于 TeX、LuaLatex 和 syntex 的拼凑性质造成的。如果添加了适当的回调层次结构以及其他一些内容,则可以创建一些非常好的功能。(例如,几乎透明的全页逐页缓存机制等)
无论如何,感谢您的帮助......