#!/usr/bin/luajit -- Construct a simple PDF, cargo-culted from Dave Long’s code. -- Try with input of '100 100 m 100 200 l 200 100 l h f'. -- By Kragen Javier Sitaker, 02025. Public domain, CC0. -- Keeps track of the byte offset in the accumulating output. local function spew(maker, data) maker.offset = maker.offset + #data table.insert(maker.chunks, data) end local function pdfmaker(objs) local maker = {offset = 0, chunks = {}, output = output, offsets = {}} spew(maker, '%PDF-1.1\n%\xa5\xa5\xa5\xa5\n') for i, data in ipairs(objs) do maker.offsets[i] = maker.offset spew(maker, ('%d 0 obj\n%s\nendobj\n'):format(i, data)) end local startxref = maker.offset -- PDF reserves object index 0 for the head of the free object ID list spew(maker, ('xref\n0 %d\n0000000000 65535 f \n'):format(#maker.offsets+1)) for i, offset in ipairs(maker.offsets) do spew(maker, ('%010d 00000 n \n'):format(offset)) end spew(maker, ([[trailer <> startxref %d %%%%EOF ]]):format(#maker.offsets+1, startxref)) return table.concat(maker.chunks) end local input = io.read('*a') -- read a page of drawing operators from input -- The PDF objects are indexed by their PDF object index in this -- table, so, for example, /Pages 2 0 R refers to object “[2]”, and -- /Contents 3 0 R refers to object “[3]”. But pdfmaker depends on -- there being no gaps in the sequence. io.write(pdfmaker { [1] = '<>', [2] = '<>', [3] = [[<> >> >> >> ]], [4] = ('<>\nstream\n%s\nendstream'):format(#input, input), })