pdfluagenerate

Generate PDF with LUA


I wrote this piece of code to generate a pdf without external modules like (LUApdf).

I have a problem because in my table I have three elements, yet at the time of creating my pdf when I open it I have only the first lines of my table that was encoded in the PDF.

If you have a solution or an idea,

Thanks


local patch = {
    {FID = 17, IDType = "nothing", CID = "nothing", Name = "Mac Aura PXL 1", Mode = "Standard", Uaddrs = 1.301},
    {FID = 18, IDType = "something", CID = "controller", Name = "Spot Moving Head", Mode = "Advanced", Uaddrs = 2.101},
    {FID = 19, IDType = "example", CID = "another", Name = "Wash Light", Mode = "Standard", Uaddrs = 3.401}
}


local function escape_pdf_string(str)
    return str:gsub("\\", "\\\\"):gsub("%(", "\\("):gsub("%)", "\\)")
end


local file = io.open("patch_corrected.pdf", "wb")


file:write("%PDF-1.4\n")
file:write("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")
file:write("2 0 obj\n<< /Type /Pages /Count 1 /Kids [3 0 R] >>\nendobj\n")
file:write("3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n")


local content_stream = "BT\n/F1 12 Tf\n"
local y_position = 800 


for index, element in ipairs(patch) do
   
    local line = string.format("FID: %d, IDType: %s, CID: %s, Name: %s, Mode: %s, Uaddrs: %.3f",
        element.FID, element.IDType, element.CID, element.Name, element.Mode, element.Uaddrs)
    line = escape_pdf_string(line) 
    content_stream = content_stream .. string.format("50 %d Td\n(%s) Tj\n", y_position, line)
    y_position = y_position - 40 

content_stream = content_stream .. "ET\n"

print("Contenu complet de content_stream :\n" .. content_stream)

file:write(string.format("4 0 obj\n<< /Length %d >>\nstream\n%s\nendstream\nendobj\n", #content_stream, content_stream))

file:write("5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n")


file:write("xref\n")
file:write("0 6\n")
file:write("0000000000 65535 f \n")
file:write("0000000009 00000 n \n")
file:write("0000000056 00000 n \n")
file:write("0000000104 00000 n \n")
file:write(string.format("%010d 00000 n \n", 247))
file:write(string.format("%010d 00000 n \n", 317))


file:write("trailer\n")
file:write("<< /Root 1 0 R /Size 6 >>\n")
file:write("startxref\n394\n")
file:write("%%EOF\n")

file:close()

print("Le fichier PDF corrigé a été généré : patch_corrected.pdf")

Solution

  • In general your approach is good but the execution method for writing PDF as text in a programmable stream, needs to hold as few variables as is possible.
    Currently your limited to about one lines worth of text.
    enter image description here

    All those variables need to tally in the trailer. Thus I suggest you adapt slightly to use this basic structure.

    %PDF-1.4
    1 0 obj <</Type/Catalog/Pages 2 0 R>> endobj
    2 0 obj <</Type/Pages/Count 1/Kids [3 0 R]>> endobj
    3 0 obj <</Type/Page/Parent 2 0 R/MediaBox [0 0 595 842]/Resources<</Font<</F1 4 0 R>>>>/Contents 5 0 R>> endobj
    4 0 obj <</Type/Font/Subtype/Type1/BaseFont/Helvetica>> endobj
    5 0 obj <</Length 6 0 R>>
    stream
    BT /F1 20 Tf 100 700 Td (Something or nothing line 1) Tj ET
    ...line 2...
    ...line 3...
    
    etc. ... etc.
    
    endstream
    endobj
    6 0 obj ..$Length.. endobj
    xref
    0 7
    0000000000 65535 f 
    0000000009 00000 n 
    0000000054 00000 n 
    0000000106 00000 n 
    0000000219 00000 n 
    0000000282 00000 n 
    0.$length+ 00000 n 
    trailer
    <</Root 1 0 R/Size 7>>
    startxref
    .$Above+20.
    %%EOF
    

    So to add lines it is simpler, beware this is NOT correct syntax just proves the method works!
    enter image description here

    The difference is we add the stream length after the data stream and only alter the fewest variable entries.

    5 0 obj <</Length 6 0 R>>
    stream
    q 0 g
    BT /F1 12 Tf 1 0 0 1 50 700 Tm (FID: 17, IDType: nothing, CID: nothing, Name: Mac Aura PXL 1, Mode: Standard, Uaddrs: 1.301) Tj ET
    BT 1 0 0 1 50 660 Tm (FID: 18, IDType: something, CID: controller, Name: Spot Moving Head, Mode: Advanced, Uaddrs: 2.101) Tj ET
    BT 1 0 0 1 50 620 Tm (FID: 19, IDType: example, CID: another, Name: Wash Light, Mode: Standard, Uaddrs: 3.401) Tj ET
    BT 1 0 0 1 50 580 Tm (FID: 20, IDType: , CID: , Name: , Mode: , Uaddrs: 0.000) Tj ET
    Q
    
    endstream
    endobj
    6 0 obj 0469 endobj
    xref
    0 7
    0000000000 65536 f 
    0000000009 00000 n 
    0000000054 00000 n 
    0000000105 00000 n 
    0000000217 00000 n 
    0000000280 00000 n 
    0000000800 00000 n 
    
    trailer
    <</Size 7/Root 1 0 R>>
    startxref
    820
    %%EOF
    
    

    Explanation

    Adobe Acrobat Reader will accept some errors and correct others, however a stream LENGTH must be close to correct.

    When adding lines of text as variable page contents you cannot count until all done. So the stream length shown here as 469 needs to be added after the stream (or carried back to stream header).

    Any variable length needs to adjust for itself in the trailer. The decimal distance addresses can be static up to the last which impacts the startXREF (A known fixed amount later)

    The maths is then simplified to if 6 0 obj 0469 endobj value is 4 digits 0469 starting at 0800 then startxref is 20 more @ 820

    There is usually no problem using leading zer0's to ensure fixed character string lengths.

    Thus heading objects 0-5 up to stream have a static value of 313 (800-469+ approx. 18 for EOS&EOO) Thus stream = 0+ and the later index value is (313 + 18) + stream length and startxref a fixed +20 after that.