Root/eeshow/gfx/pdftoc.c

1/*
2 * gfx/pdftoc.c - PDF writer with TOC generation
3 *
4 * Written 2016 by Werner Almesberger
5 * Copyright 2016 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13/*
14 * Strongly influenced by https://neo900.org/git?p=misc;a=tree;f=schtoc
15 *
16 * PDF Reference:
17 * http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
18 */
19
20
21#include <stdbool.h>
22#include <stdlib.h>
23#include <stdio.h>
24#include <string.h>
25#include <assert.h>
26
27#include "misc/util.h"
28#include "misc/diag.h"
29#include "gfx/pdftoc.h"
30
31
32struct title {
33    char *s;
34    struct title *next;
35};
36
37struct object {
38    int gen;
39    unsigned pos;
40    bool is_page;
41};
42
43struct pdftoc {
44    FILE *file;
45
46    enum state {
47        idle, /* between objects */
48        object, /* inside an object */
49        catalog,/* inside the catalog object */
50        xref, /* stopped at xref */
51        trailer,/* going through the trailer */
52    } state;
53
54    struct title *titles;
55    struct title **next_title;
56    unsigned n_titles;
57
58    char *buf;
59    unsigned left; /* bytes left in buffer */
60    unsigned offset; /* offset into buffer */
61    unsigned pos; /* position in file */
62
63    struct object *objs; /* object array */
64    struct object *curr_obj;
65    int top; /* highest object number; -1 if no objects */
66
67    int root; /* catalog dict */
68    int info; /* information dict, 0 if absent */
69};
70
71
72struct pdftoc *pdftoc_begin(const char *file)
73{
74    struct pdftoc *ctx;
75
76    ctx = alloc_type(struct pdftoc);
77    if (file) {
78        ctx->file = fopen(file, "w");
79        if (!ctx->file)
80            diag_pfatal(file);
81    } else {
82        ctx->file = stdout;
83    }
84
85    ctx->state = idle;
86
87    ctx->titles = NULL;
88    ctx->next_title = &ctx->titles;
89    ctx->n_titles = 0;
90
91    ctx->buf = NULL;
92    ctx->left = 0;
93    ctx->offset = 0;
94    ctx->pos = 0;
95
96    ctx->objs = NULL;
97    ctx->top = -1;
98
99    ctx->root = 0;
100    ctx->info = 0;
101
102    return ctx;
103}
104
105
106static void add_object(struct pdftoc *ctx, int id, int gen, unsigned pos)
107{
108    struct object *obj;
109
110    if (id > ctx->top) {
111        ctx->objs = realloc_type_n(ctx->objs, struct object, id + 1);
112        memset(ctx->objs + ctx->top + 1 , 0,
113            (id - ctx->top) * sizeof(struct object));
114        ctx->top = id;
115    }
116
117    obj = ctx->objs + id;
118    ctx->curr_obj = obj;
119    obj->gen = gen;
120    obj->pos = pos;
121    obj->is_page = 0;
122}
123
124
125static bool parse_object(struct pdftoc *ctx, const char *s)
126{
127    int id, gen;
128    int n = 0;
129
130    if (sscanf(s, "%d %d obj%n", &id, &gen, &n) != 2 || !n)
131        return 0;
132    add_object(ctx, id, gen, ctx->pos);
133    return 1;
134}
135
136
137static void line(struct pdftoc *ctx, const char *s)
138{
139
140    switch (ctx->state) {
141    case idle:
142        if (parse_object(ctx, s)) {
143            ctx->state = object;
144            break;
145        }
146        if (strbegins(s, "xref")) {
147            ctx->state = xref;
148            break;
149        }
150        break;
151    case object:
152        if (strbegins(s, "endobj")) {
153            ctx->state = idle;
154            break;
155        }
156        if (strbegins(s, "<< /Type /Page") &&
157            !strbegins(s, "<< /Type /Pages")) {
158            ctx->curr_obj->is_page = 1;
159            break;
160        }
161        if (strbegins(s, "<< /Type /Catalog")) {
162            ctx->state = catalog;
163            break;
164        }
165        break;
166    case catalog:
167        if (strbegins(s, ">>")) {
168            ctx->state = object;
169            ctx->pos += fprintf(ctx->file,
170                " /Outlines %u 0 R\n",
171                ctx->top + 1);
172            break;
173        }
174        break;
175    case xref:
176        abort();
177    case trailer:
178        if (sscanf(s, " /Root %d 0 R", &ctx->root) == 1)
179            break;
180        if (sscanf(s, " /Info %d 0 R", &ctx->info) == 1)
181            break;
182        break;
183    default:
184        abort();
185    }
186}
187
188
189static void parse_buffer(struct pdftoc *ctx, bool do_write)
190{
191    unsigned size, wrote;
192    char *nl;
193
194    while (ctx->state != xref) {
195        nl = memchr(ctx->buf + ctx->offset, '\n', ctx->left);
196        if (!nl)
197            break;
198        *nl = 0;
199        size = nl - (ctx->buf + ctx->offset);
200        line(ctx, ctx->buf + ctx->offset);
201        *nl = '\n';
202        if (ctx->state == xref)
203            break;
204        if (do_write) {
205            wrote = fwrite(ctx->buf + ctx->offset, 1, size + 1,
206                ctx->file);
207            if (wrote != size + 1)
208                diag_pfatal("fwrite");
209            ctx->pos += size + 1;
210        }
211        ctx->offset += size + 1;
212        ctx->left -= size + 1;
213    }
214}
215
216
217bool pdftoc_write(struct pdftoc *ctx, const void *data, unsigned length)
218{
219    char *buf;
220
221    buf = alloc_size(ctx->left + length + 1);
222    memcpy(buf, ctx->buf + ctx->offset, ctx->left);
223    memcpy(buf + ctx->left, data, length);
224    ctx->offset = 0;
225    ctx->left += length;
226    free(ctx->buf);
227    ctx->buf = buf;
228
229    parse_buffer(ctx, 1);
230
231    return 1;
232}
233
234
235void pdftoc_title(struct pdftoc *ctx, const char *title)
236{
237    struct title *t;
238
239    t = alloc_type(struct title);
240    t->s = stralloc(title);
241    *ctx->next_title = t;
242    t->next = NULL;
243    ctx->next_title = &t->next;
244    ctx->n_titles++;
245}
246
247
248static void write_trailer(struct pdftoc *ctx)
249{
250    unsigned n = ctx->top + 1;
251    const struct object *obj = ctx->objs;
252    const struct title *t;
253    unsigned outline, tail;
254    int i;
255
256    /* Outline root */
257
258    outline = n;
259    add_object(ctx, n, 0, ctx->pos);
260    tail = fprintf(ctx->file,
261        "%u 0 obj\n<<\n"
262        " /Count %u\n"
263        " /First %u 0 R\n"
264        " /Last %u 0 R\n"
265        ">>\nendobj\n",
266        n, ctx->n_titles, n + 1, n + ctx->n_titles);
267
268    /* Outline items */
269
270    n++;
271    i = 0;
272    for (t = ctx->titles; t; t = t->next) {
273        assert(i <= ctx->top);
274        while (!ctx->objs[i].is_page) {
275            i++;
276            assert(i <= ctx->top);
277        }
278        add_object(ctx, n, 0, ctx->pos + tail);
279        tail += fprintf(ctx->file,
280            "%u 0 obj\n<<\n"
281            " /Title (%s)\n"
282            " /Parent %u 0 R\n",
283            n, t->s, outline);
284        if (t != ctx->titles)
285            tail += fprintf(ctx->file,
286                " /Prev %u 0 R\n", n - 1);
287        if (t->next)
288            tail += fprintf(ctx->file,
289                " /Next %u 0 R\n", n + 1);
290        tail += fprintf(ctx->file,
291            " /Dest [%d %u R /Fit]\n"
292            ">>\nendobj\n",
293            i, ctx->objs[i].gen);
294        n++;
295        i++;
296    }
297
298    /* xref table */
299
300    fprintf(ctx->file, "xref\n0 %u\n", n);
301    for (obj = ctx->objs; obj != ctx->objs + ctx->top + 1; obj++)
302        fprintf(ctx->file,
303            "%010u %05u %c \n",
304            obj->pos, obj->pos ? 0 : 65535, obj->pos ? 'n' : 'f');
305
306    fprintf(ctx->file,
307        "trailer\n"
308        "<< /Size %u\n"
309        " /Root %u 0 R\n",
310        n, ctx->root);
311    if (ctx->info)
312        fprintf(ctx->file, " /Info %u 0 R\n", ctx->info);
313    fprintf(ctx->file, ">>\nstartxref\n%u\n%%%%EOF\n", ctx->pos + tail);
314}
315
316
317void pdftoc_end(struct pdftoc *ctx)
318{
319    struct title *next;
320
321    assert(ctx->state == xref);
322    ctx->state = trailer;
323    parse_buffer(ctx, 0);
324    if (ctx->left) {
325        fatal("%u bytes left in buffer at end\n", ctx->left);
326        exit(1);
327    }
328
329    write_trailer(ctx);
330
331    if (fclose(ctx->file) < 0)
332        diag_pfatal("fclose");
333
334    while (ctx->titles) {
335        next = ctx->titles->next;
336        free(ctx->titles->s);
337        free(ctx->titles);
338        ctx->titles = next;
339    }
340    free(ctx->buf);
341    free(ctx);
342}
343

Archive Download this file

Branches:
master



interactive