Lua生成PDF时页面丢失问题排查求助
Lua生成多页PDF时页面2丢失的问题修复
问题背景
开发Lua脚本生成包含表格数据的多页PDF文件,脚本日志显示所有页面已正确创建,但打开PDF时发现页面2丢失,内容直接从页面1跳转至页面3。已完成以下排查:
- 确认脚本日志中页面2的生成和包含记录正常
- 检查PDF对象结构(页面、引用、xref)表面无异常
- 验证交叉引用结构(xref)和页面树的/Kids条目
问题根源
问题出在交叉引用表(xref)的偏移量计算错误:原代码硬编码初始偏移量,且未正确记录每个PDF对象的实际写入位置,导致PDF阅读器无法准确定位页面2的对象内容,从而跳过该页面。
具体问题点:
- 初始偏移量设置错误:
%PDF-1.4\n实际长度为8字节,原代码设为9,导致后续所有偏移量偏差1 - 偏移量更新逻辑错误:先写入预估偏移量再更新位置,导致记录的是下一个对象的起始位置,而非当前对象的实际位置
- 对象编号与偏移量对应混乱:未按PDF对象的实际写入顺序记录偏移,部分对象的xref条目错误
修复后的代码
local function creerPDF(fichierPDF, data) local fichier = assert(io.open(fichierPDF, "w")) fichier:write("%PDF-1.4\n") local pages = {} local kids = {} local positionY = 700 local lignesParPage = 30 local offsets = {} -- 记录每个对象的起始偏移量 -- 对象0的偏移固定为0 offsets[0] = 0 local function nouvellePage() local pageObj = #pages * 2 + 3 local contentObj = pageObj + 1 table.insert(kids, pageObj .. " 0 R") table.insert(pages, {page = pageObj, content = contentObj, stream = ""}) positionY = 700 end local function ajouterEnTete(page) local header = "q\n" header = header .. "BT /F1 12 Tf 20 820 Td (Programmeur : LumiArt-Studio) Tj ET\n" header = header .. "BT /F1 12 Tf 20 800 Td (Date : " .. os.date("%d/%m/%Y %H:%M") .. ") Tj ET\n" header = header .. "BT /F1 12 Tf 20 780 Td (Machine : TestMachine) Tj ET\n" header = header .. "BT /F1 12 Tf 50 720 Td (ID) Tj ET\n" header = header .. "BT /F1 12 Tf 150 720 Td (Type) Tj ET\n" header = header .. "BT /F1 12 Tf 250 720 Td (Name) Tj ET\n" header = header .. "BT /F1 12 Tf 350 720 Td (FixtureType) Tj ET\n" header = header .. "BT /F1 12 Tf 450 720 Td (U.Addrs) Tj ET\n" header = header .. "Q\n" page.stream = page.stream .. header end local function ajouterDonnee(page, ligne) local columnPositions = {50, 150, 250, 350, 450} local values = {ligne.id, ligne.idtype, ligne.name, ligne.fixturetype, ligne.uaddrs} for i, value in ipairs(values) do page.stream = page.stream .. string.format("BT /F1 10 Tf %d %d Td (%s) Tj ET\n", columnPositions[i], positionY, tostring(value)) end positionY = positionY - 20 if positionY < 100 then nouvellePage() ajouterEnTete(pages[#pages]) end end local function ajouterPiedDePage(page, index, total) local footer = string.format("BT /F1 10 Tf 500 20 Td (Page %d sur %d) Tj ET\n", index, total) page.stream = page.stream .. footer end nouvellePage() ajouterEnTete(pages[#pages]) for _, ligne in ipairs(data) do ajouterDonnee(pages[#pages], ligne) end local totalPages = #pages for i, page in ipairs(pages) do ajouterPiedDePage(page, i, totalPages) end -- 写入页面和内容对象,同步记录偏移 for _, page in ipairs(pages) do offsets[page.page] = fichier:seek() fichier:write(string.format("%d 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 5 0 R >> >> /Contents %d 0 R >>\nendobj\n", page.page, page.content)) offsets[page.content] = fichier:seek() fichier:write(string.format("%d 0 obj\n<< /Length %d >>\nstream\n%s\nendstream\nendobj\n", page.content, #page.stream, page.stream)) end -- 写入Pages对象并记录偏移 offsets[2] = fichier:seek() fichier:write("2 0 obj\n<< /Type /Pages /Kids [") for _, kid in ipairs(kids) do fichier:write(kid .. " ") end fichier:write(string.format("] /Count %d >>\nendobj\n", #kids)) -- 写入Catalog对象并记录偏移 offsets[1] = fichier:seek() fichier:write("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n") -- 写入Font对象并记录偏移 offsets[5] = fichier:seek() fichier:write("5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n") -- 生成正确的xref表 local xrefStart = fichier:seek() local maxObjNum = #kids * 2 + 5 fichier:write("xref\n0 " .. (maxObjNum + 1) .. "\n") fichier:write("0000000000 65535 f \n") for i = 1, maxObjNum do local offset = offsets[i] or 0 fichier:write(string.format("%010d 00000 n \n", offset)) end -- 写入trailer和EOF标记 fichier:write(string.format("trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%EOF\n", maxObjNum + 1, xrefStart)) fichier:close() end local data = {} for i = 1, 200 do table.insert(data, {id = i, idtype = "Fixture", name = "Fixture " .. i, fixturetype = "Type " .. i, uaddrs = tostring(i * 100)}) end creerPDF("output_debug_fixed.pdf", data)
修复要点
- 新增
offsets数组,在写入每个PDF对象前记录其起始偏移量,确保xref表偏移完全准确 - 按对象实际写入顺序记录偏移,保证对象编号与偏移量一一对应
- 修正xref表的最大对象编号计算,避免遗漏或多余条目
- 移除硬编码的初始偏移量,改为动态记录每个对象的实际位置
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

