You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Lua生成PDF时页面丢失问题排查求助

Lua生成多页PDF时页面2丢失的问题修复

问题背景

开发Lua脚本生成包含表格数据的多页PDF文件,脚本日志显示所有页面已正确创建,但打开PDF时发现页面2丢失,内容直接从页面1跳转至页面3。已完成以下排查:

  • 确认脚本日志中页面2的生成和包含记录正常
  • 检查PDF对象结构(页面、引用、xref)表面无异常
  • 验证交叉引用结构(xref)和页面树的/Kids条目

问题根源

问题出在交叉引用表(xref)的偏移量计算错误:原代码硬编码初始偏移量,且未正确记录每个PDF对象的实际写入位置,导致PDF阅读器无法准确定位页面2的对象内容,从而跳过该页面。

具体问题点:

  1. 初始偏移量设置错误:%PDF-1.4\n实际长度为8字节,原代码设为9,导致后续所有偏移量偏差1
  2. 偏移量更新逻辑错误:先写入预估偏移量再更新位置,导致记录的是下一个对象的起始位置,而非当前对象的实际位置
  3. 对象编号与偏移量对应混乱:未按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)

修复要点

  1. 新增offsets数组,在写入每个PDF对象前记录其起始偏移量,确保xref表偏移完全准确
  2. 按对象实际写入顺序记录偏移,保证对象编号与偏移量一一对应
  3. 修正xref表的最大对象编号计算,避免遗漏或多余条目
  4. 移除硬编码的初始偏移量,改为动态记录每个对象的实际位置

内容的提问来源于stack exchange,提问作者Josh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:05:57