summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/luatexdir/pdf/pdfgen.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/pdf/pdfgen.c')
-rw-r--r--Build/source/texk/web2c/luatexdir/pdf/pdfgen.c2516
1 files changed, 2516 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/pdf/pdfgen.c b/Build/source/texk/web2c/luatexdir/pdf/pdfgen.c
new file mode 100644
index 00000000000..0409dbc33dc
--- /dev/null
+++ b/Build/source/texk/web2c/luatexdir/pdf/pdfgen.c
@@ -0,0 +1,2516 @@
+/*
+
+Copyright 2009-2013 Taco Hoekwater <taco@luatex.org>
+
+This file is part of LuaTeX.
+
+LuaTeX is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+LuaTeX is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+License for more details.
+
+You should have received a copy of the GNU General Public License along
+with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "ptexlib.h"
+
+#include <kpathsea/c-dir.h>
+#include <kpathsea/c-ctype.h>
+#include "lua/luatex-api.h"
+#include "md5.h"
+
+#define check_nprintf(size_get, size_want) \
+ if ((unsigned)(size_get) >= (unsigned)(size_want)) \
+ formatted_error("pdf backend","snprintf() failed in file %s at line %d", __FILE__, __LINE__);
+
+PDF static_pdf = NULL;
+
+/*tex The commandline interface: */
+
+int output_mode_used;
+int output_mode_option;
+int output_mode_value;
+int draft_mode_option;
+int draft_mode_value;
+
+/*tex Additional keys of the |/Info| dictionary: */
+
+halfword pdf_info_toks;
+
+/*tex Additional keys of the |/Catalog| and its |/OpenAction| dictionary: */
+
+halfword pdf_catalog_toks;
+
+halfword pdf_catalog_openaction;
+
+/*tex Additional keys of the |/Names| dictionary: */
+
+halfword pdf_names_toks;
+
+/*tex Additional keys of the |/Trailer| dictionary" */
+
+halfword pdf_trailer_toks;
+
+/*tex Set to |shipping_mode| when |ship_out| starts */
+
+shipping_mode_e global_shipping_mode = NOT_SHIPPING;
+
+/*tex
+
+ Create a new buffer |strbuf_s| of size |size| and maximum allowed size
+ |limit|. Initialize it and set |p| to begin of data.
+
+*/
+
+strbuf_s *new_strbuf(size_t size, size_t limit)
+{
+ strbuf_s *b;
+ b = xtalloc(1, strbuf_s);
+ b->size = size;
+ b->limit = limit;
+ if (size > 0) {
+ b->p = b->data = xtalloc(b->size, unsigned char);
+ } else {
+ /*tex For other alloc: */
+ b->p = b->data = NULL;
+ }
+ return b;
+}
+
+/*tex Check that |n| bytes more fit into buffer; increase it if required. */
+
+static void strbuf_room(strbuf_s * b, size_t n)
+{
+ unsigned int a;
+ size_t l = (size_t) (b->p - b->data);
+ if (n > b->limit - l)
+ overflow("PDF buffer", (unsigned) b->size);
+ if (n + l > b->size) {
+ a = (unsigned int) (b->size >> 2);
+ if (n + l > b->size + a)
+ b->size = n + l;
+ else if (b->size < b->limit - a)
+ b->size = b->size + a;
+ else
+ b->size = b->limit;
+ b->data = xreallocarray(b->data, unsigned char, (unsigned) b->size);
+ b->p = b->data + l;
+ }
+}
+
+/*tex Seek to position |offset| within buffer. Position must be valid. */
+
+void strbuf_seek(strbuf_s * b, off_t offset)
+{
+ b->p = b->data + offset;
+}
+
+/*tex Get the current buffer fill level, the number of characters.*/
+
+size_t strbuf_offset(strbuf_s * b)
+{
+ return (size_t) (b->p - b->data);
+}
+
+/*tex Put one character into buffer. Make room before if needed. */
+
+void strbuf_putchar(strbuf_s * b, unsigned char c)
+{
+ if ((size_t) (b->p - b->data + 1) > b->size)
+ strbuf_room(b, 1);
+ *b->p++ = c;
+}
+
+/*tex Dump filled buffer part to the \PDF\ file. */
+
+void strbuf_flush(PDF pdf, strbuf_s * b)
+{
+ pdf_out_block(pdf, (const char *) b->data, strbuf_offset(b));
+ strbuf_seek(b, 0);
+}
+
+/*tex We free all dynamically allocated buffer structures. */
+
+void strbuf_free(strbuf_s * b)
+{
+ xfree(b->data);
+ xfree(b);
+}
+
+/*tex This |init_pdf_struct| is called early and only once. */
+
+PDF init_pdf_struct(PDF pdf)
+{
+ os_struct *os;
+ pdf = xtalloc(1, pdf_output_file);
+ memset(pdf, 0, sizeof(pdf_output_file));
+ pdf->job_name = makecstring(job_name);
+ /*tex This will be set by |fix_o_mode|: */
+ output_mode_used = OMODE_NONE;
+ /*tex Used by synctex, we need to use output_mode_used there. */
+ pdf->o_mode = output_mode_used;
+ pdf->o_state = ST_INITIAL;
+ /*tex Initialize \PDF\ and object stream writing */
+ pdf->os = os = xtalloc(1, os_struct);
+ memset(pdf->os, 0, sizeof(os_struct));
+ os->buf[PDFOUT_BUF] = new_strbuf(inf_pdfout_buf_size, sup_pdfout_buf_size);
+ os->buf[OBJSTM_BUF] = new_strbuf(inf_objstm_buf_size, sup_objstm_buf_size);
+ os->obj = xtalloc(PDF_OS_MAX_OBJS, os_obj_data);
+ os->cur_objstm = 0;
+ os->curbuf = PDFOUT_BUF;
+ pdf->buf = os->buf[os->curbuf];
+ /*tex
+ Later ttf_seek_outbuf(TABDIR_OFF + n * 4 * TTF_ULONG_SIZE); in
+ ttf_init_font we need 236 bytes, so we start with 256 bytes as in \PDFTEX.
+ */
+ pdf->fb = new_strbuf(256, 100000000);
+ pdf->stream_deflate = false;
+ pdf->stream_writing = false;
+ /*tex
+ Sometimes it is neccesary to allocate memory for PDF output that cannot be
+ deallocated then, so we use |mem| for this purpose.
+ */
+ pdf->mem_size = inf_pdf_mem_size;
+ pdf->mem = xtalloc(pdf->mem_size, int);
+ /*tex
+ The first word is not used so we can use zero as a value for testing
+ whether a pointer to |mem| is valid.
+ */
+ pdf->mem_ptr = 1;
+ pdf->pstruct = NULL;
+ pdf->posstruct = xtalloc(1, posstructure);
+ pdf->posstruct->pos.h = 0;
+ pdf->posstruct->pos.v = 0;
+ pdf->posstruct->dir = dir_TLT;
+ /*tex Allocated size of |obj_tab| array> */
+ pdf->obj_tab_size = (unsigned) inf_obj_tab_size;
+ pdf->obj_tab = xtalloc(pdf->obj_tab_size + 1, obj_entry);
+ memset(pdf->obj_tab, 0, sizeof(obj_entry));
+ pdf->minor_version = -1;
+ pdf->major_version = -1;
+ pdf->decimal_digits = 4;
+ pdf->gamma = 65536;
+ pdf->image_gamma = 65536;
+ pdf->image_hicolor = 1;
+ pdf->image_apply_gamma = 0;
+ pdf->objcompresslevel = 0;
+ pdf->compress_level = 0;
+ pdf->force_file = 0;
+ pdf->recompress = 0;
+ pdf->draftmode = 0;
+ pdf->inclusion_copy_font = 1;
+ pdf->pk_resolution = 0;
+ pdf->pk_fixed_dpi = 0;
+ pdf->pk_scale_factor = 0;
+ init_dest_names(pdf);
+ pdf->page_resources = NULL;
+ init_pdf_pagecalculations(pdf);
+ pdf->vfstruct = new_vfstruct();
+ return pdf;
+}
+
+/*tex We use |pdf_get_mem| to allocate memory in |mem|. */
+
+int pdf_get_mem(PDF pdf, int s)
+{
+ int a;
+ int ret;
+ if (s > sup_pdf_mem_size - pdf->mem_ptr)
+ overflow("pdf memory size (pdf_mem_size)", (unsigned) pdf->mem_size);
+ if (pdf->mem_ptr + s > pdf->mem_size) {
+ a = pdf->mem_size >> 2;
+ if (pdf->mem_ptr + s > pdf->mem_size + a) {
+ pdf->mem_size = pdf->mem_ptr + s;
+ } else if (pdf->mem_size < sup_pdf_mem_size - a) {
+ pdf->mem_size = pdf->mem_size + a;
+ } else {
+ pdf->mem_size = sup_pdf_mem_size;
+ }
+ pdf->mem = xreallocarray(pdf->mem, int, (unsigned) pdf->mem_size);
+ }
+ ret = pdf->mem_ptr;
+ pdf->mem_ptr = pdf->mem_ptr + s;
+ return ret;
+}
+
+/*tex
+
+ This ensures that |pdfmajorversion| and |pdfminorversion| are set only before
+ any bytes have been written to the generated \PDF\ file. Here also all
+ variables for \PDF\ output are initialized, the \PDF\ file is opened by
+ |ensure_pdf_open|, and the \PDF\ header is written.
+
+*/
+
+void fix_pdf_version(PDF pdf)
+{
+ if (pdf->major_version < 0) {
+ /*tex It is unset. */
+ if (pdf_major_version == 0) {
+ normal_warning("pdf backend","unset major version, using 1 instead");
+ pdf->major_version = 1;
+ } else if ((pdf_major_version < 0) || (pdf_major_version > 2)) {
+ formatted_warning("pdf backend","illegal major version %d, using 1 instead",pdf_major_version);
+ pdf->major_version = 1;
+ } else {
+ pdf->major_version = pdf_major_version;
+ }
+ } else if (pdf->major_version != pdf_major_version) {
+ normal_warning("pdf backend", "the major version cannot be changed after data is written to the PDF file");
+ }
+ if (pdf->minor_version < 0) {
+ /*tex It is unset. */
+ if ((pdf_minor_version < 0) || (pdf_minor_version > 9)) {
+ formatted_warning("pdf backend","illegal minor version %d, using 4 instead",pdf_minor_version);
+ pdf->minor_version = 4;
+ } else {
+ pdf->minor_version = pdf_minor_version;
+ }
+ } else if (pdf->minor_version != pdf_minor_version) {
+ normal_warning("pdf backend", "minorversion cannot be changed after data is written to the PDF file");
+ }
+}
+
+static void fix_pdf_draftmode(PDF pdf)
+{
+ if (pdf->draftmode != draft_mode_par)
+ normal_warning("pdf backend", "draftmode cannot be changed after data is written to the PDF file");
+ if (pdf->draftmode != 0) {
+ /*tex Fix these as they might have been changed in between. */
+ pdf->compress_level = 0;
+ pdf->objcompresslevel = 0;
+ }
+}
+
+#define ZIP_BUF_SIZE 32768
+
+#define check_err(f, fn) \
+ if (f != Z_OK) \
+ formatted_error("pdf backend","zlib %s() failed (error code %d)", fn, f)
+
+static void write_zip(PDF pdf)
+{
+ int flush, err = Z_OK;
+ uInt zip_len;
+ strbuf_s *buf = pdf->buf;
+ z_stream *s = pdf->c_stream;
+ boolean finish = pdf->zip_write_state == ZIP_FINISH;
+ if (pdf->stream_length == 0) {
+ if (s == NULL) {
+ s = pdf->c_stream = xtalloc(1, z_stream);
+ s->zalloc = (alloc_func) 0;
+ s->zfree = (free_func) 0;
+ s->opaque = (voidpf) 0;
+ check_err(deflateInit(s, pdf->compress_level), "deflateInit");
+ pdf->zipbuf = xtalloc(ZIP_BUF_SIZE, char);
+ } else
+ check_err(deflateReset(s), "deflateReset");
+ s->next_out = (Bytef *) pdf->zipbuf;
+ s->avail_out = ZIP_BUF_SIZE;
+ }
+ s->next_in = buf->data;
+ s->avail_in = (uInt) (buf->p - buf->data);
+ while (true) {
+ if (s->avail_out == 0 || (finish && s->avail_out < ZIP_BUF_SIZE)) {
+ zip_len = ZIP_BUF_SIZE - s->avail_out;
+ pdf->gone += (off_t) xfwrite(pdf->zipbuf, 1, zip_len, pdf->file);
+ pdf->last_byte = pdf->zipbuf[zip_len - 1];
+ s->next_out = (Bytef *) pdf->zipbuf;
+ s->avail_out = ZIP_BUF_SIZE;
+ }
+ if (finish) {
+ if (err == Z_STREAM_END) {
+ xfflush(pdf->file);
+ pdf->zip_write_state = NO_ZIP;
+ break;
+ }
+ flush = Z_FINISH;
+ } else {
+ if (s->avail_in == 0)
+ break;
+ flush = Z_NO_FLUSH;
+ }
+ err = deflate(s, flush);
+ if (err != Z_OK && err != Z_STREAM_END)
+ formatted_error("pdf backend","zlib deflate() failed (error code %d)", err);
+ }
+ pdf->stream_length = (off_t) s->total_out;
+}
+
+void zip_free(PDF pdf)
+{
+ if (pdf->zipbuf != NULL) {
+ check_err(deflateEnd(pdf->c_stream), "deflateEnd");
+ xfree(pdf->zipbuf);
+ }
+ xfree(pdf->c_stream);
+}
+
+static void write_nozip(PDF pdf)
+{
+ strbuf_s *buf = pdf->buf;
+ size_t l = strbuf_offset(buf);
+ if (l == 0)
+ return;
+ pdf->stream_length = pdf_offset(pdf) - pdf->save_offset;
+ pdf->gone += (off_t) xfwrite((char *) buf->data, sizeof(char), l, pdf->file);
+ pdf->last_byte = *(buf->p - 1);
+}
+
+/*tex
+
+ The PDF buffer is flushed by calling |pdf_flush|, which checks the variable
+ |zip_write_state| and will compress the buffer before flushing if neccesary.
+ We call |pdf_begin_stream| to begin a stream and |pdf_end_stream| to finish
+ it. The stream contents will be compressed if compression is turn on.
+
+*/
+
+void pdf_flush(PDF pdf)
+{
+ os_struct *os = pdf->os;
+ off_t saved_pdf_gone = pdf->gone;
+ switch (os->curbuf) {
+ case PDFOUT_BUF:
+ if (pdf->draftmode == 0) {
+ switch (pdf->zip_write_state) {
+ case NO_ZIP:
+ write_nozip(pdf);
+ break;
+ case ZIP_WRITING:
+ case ZIP_FINISH:
+ write_zip(pdf);
+ break;
+ default:
+ normal_error("pdf backend", "bad zip state");
+ }
+ } else
+ pdf->zip_write_state = NO_ZIP;
+ strbuf_seek(pdf->buf, 0);
+ if (saved_pdf_gone > pdf->gone)
+ normal_error("pdf backend", "file size exceeds architectural limits (pdf_gone wraps around)");
+ break;
+ case OBJSTM_BUF:
+ break;
+ default:
+ normal_error("pdf backend", "bad buffer state");
+ }
+}
+
+static void pdf_buffer_select(PDF pdf, buffer_e buf)
+{
+ os_struct *os = pdf->os;
+ if (pdf->os_enable && buf == OBJSTM_BUF) {
+ /*tex Switch to object stream: */
+ os->curbuf = OBJSTM_BUF;
+ } else {
+ /*tex Switch to \PDF\ stream: */
+ os->curbuf = PDFOUT_BUF;
+ }
+ pdf->buf = os->buf[pdf->os->curbuf];
+}
+
+/*tex
+
+ We create new |/ObjStm| object if required, and set up cross reference info.
+
+*/
+
+static void pdf_prepare_obj(PDF pdf, int k, int pdf_os_threshold)
+{
+ os_struct *os = pdf->os;
+ strbuf_s *obuf = os->buf[OBJSTM_BUF];
+ if (pdf->objcompresslevel >= pdf_os_threshold)
+ pdf_buffer_select(pdf, OBJSTM_BUF);
+ else
+ pdf_buffer_select(pdf, PDFOUT_BUF);
+ switch (os->curbuf) {
+ case PDFOUT_BUF:
+ obj_offset(pdf, k) = pdf_offset(pdf);
+ /*tex Mark it as not included in any |ObjStm|. */
+ obj_os_idx(pdf, k) = PDF_OS_MAX_OBJS;
+ break;
+ case OBJSTM_BUF:
+ if (os->cur_objstm == 0) {
+ os->cur_objstm =
+ (unsigned int) pdf_create_obj(pdf, obj_type_objstm, 0);
+ os->idx = 0;
+ /*tex Start a fresh object stream. */
+ obuf->p = obuf->data;
+ /*tex Keep some statistics. */
+ os->ostm_ctr++;
+ }
+ obj_os_idx(pdf, k) = (int) os->idx;
+ obj_os_objnum(pdf, k) = (int) os->cur_objstm;
+ os->obj[os->idx].num = k;
+ os->obj[os->idx].off = obuf->p - obuf->data;
+ break;
+ default:
+ normal_error("pdf backend", "bad object state");
+ }
+}
+
+/*tex
+
+ We set the active buffer pointer and make sure that there are at least |n|
+ bytes free in that buffer, flushing happens if needed.
+
+*/
+
+inline void pdf_room(PDF pdf, int n)
+{
+ strbuf_s *buf = pdf->buf;
+ if ((size_t) (n + buf->p - buf->data) <= buf->size)
+ return;
+ if (pdf->os->curbuf == PDFOUT_BUF) {
+ if ((size_t) n > buf->size)
+ overflow("PDF output buffer", (unsigned) buf->size);
+ if ((size_t) (n + buf->p - buf->data) < buf->limit)
+ strbuf_room(buf, (size_t) n);
+ else
+ pdf_flush(pdf);
+ } else {
+ strbuf_room(buf, (size_t) n);
+ }
+}
+
+void pdf_out_block(PDF pdf, const char *s, size_t n)
+{
+ size_t l;
+ strbuf_s *buf = pdf->buf;
+ do {
+ l = n;
+ if (l > buf->size)
+ l = buf->size;
+ pdf_room(pdf, (int) l);
+ (void) memcpy(buf->p, s, l);
+ buf->p += l;
+ s += l;
+ n -= l;
+ } while (n > 0);
+}
+
+__attribute__ ((format(printf, 2, 3)))
+void pdf_printf(PDF pdf, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ if (pdf->printf_buf == NULL) {
+ pdf->printf_buf = xtalloc(PRINTF_BUF_SIZE, char);
+ }
+ (void) vsnprintf(pdf->printf_buf, PRINTF_BUF_SIZE, fmt, args);
+ pdf_puts(pdf, pdf->printf_buf);
+ va_end(args);
+}
+
+void pdf_print(PDF pdf, str_number s)
+{
+ const char *ss;
+ size_t l;
+ if (s >= STRING_OFFSET) {
+ ss = (const char *) str_string(s);
+ l = str_length(s);
+ pdf_out_block(pdf, ss, l);
+ } else {
+ pdf_out(pdf, s);
+ }
+}
+
+void pdf_print_int(PDF pdf, longinteger n)
+{
+ char s[24];
+ int w;
+ w = snprintf(s, 23, "%" LONGINTEGER_PRI "d", (LONGINTEGER_TYPE) n);
+ check_nprintf(w, 23);
+ pdf_out_block(pdf, (const char *) s, (size_t) w);
+}
+
+void print_pdffloat(PDF pdf, pdffloat f)
+{
+ int64_t m = f.m;
+ if (m == 0) {
+ pdf_out(pdf, '0');
+ } else {
+ int e = f.e;
+ if (e == 0) {
+ /*tex division by |ten_pow[0] == 1| */
+ if (m == 1) {
+ pdf_out(pdf, '1');
+ } else {
+ char a[24];
+ snprintf(a, 23, "%" LONGINTEGER_PRI "i", (LONGINTEGER_TYPE) m);
+ pdf_puts(pdf, a);
+ }
+ } else {
+ int t = ten_pow[e] ;
+ if (t == m) {
+ pdf_out(pdf, '1');
+ } else {
+ int i, l, w;
+ char a[24];
+ if (m < 0) {
+ pdf_out(pdf, '-');
+ m *= -1;
+ }
+ l = m / t;
+ w = snprintf(a, 23, "%i", l);
+ pdf_out_block(pdf, (const char *) a, (size_t) w);
+ l = m % t;
+ if (l != 0) {
+ pdf_out(pdf, '.');
+ snprintf(a, 23, "%d", l + t);
+ for (i = e; i > 0; i--) {
+ if (a[i] != '0')
+ break;
+ a[i] = '\0';
+ }
+ pdf_puts(pdf, (a + 1));
+ }
+ }
+ }
+ }
+}
+
+void pdf_print_str(PDF pdf, const char *s)
+{
+ const char *orig = s;
+ /*tex This initializes at the last string index. */
+ int l = (int) strlen(s) - 1;
+ if (l < 0) {
+ pdf_puts(pdf, "()");
+ return;
+ }
+ /*tex
+ The next might not really be safe as the string could be ``(a)xx(b)'' but
+ so far we never had an issue.
+ */
+ if ((s[0] == '(') && (s[l] == ')')) {
+ pdf_puts(pdf, s);
+ return;
+ }
+ if ((s[0] != '<') || (s[l] != '>') || odd((l + 1))) {
+ pdf_out(pdf, '(');
+ pdf_puts(pdf, s);
+ pdf_out(pdf, ')');
+ return;
+ }
+ s++;
+ while (isxdigit((unsigned char)*s))
+ s++;
+ if (s != orig + l) {
+ pdf_out(pdf, '(');
+ pdf_puts(pdf, orig);
+ pdf_out(pdf, ')');
+ return;
+ }
+ pdf_puts(pdf, orig);
+}
+
+/*tex A stream needs to have a stream dictionary also. */
+
+void pdf_begin_stream(PDF pdf)
+{
+ pdf_puts(pdf, "\nstream\n");
+ pdf_save_offset(pdf);
+ pdf_flush(pdf);
+ if (pdf->stream_deflate) {
+ pdf->zip_write_state = ZIP_WRITING;
+ }
+ pdf->stream_writing = true;
+ pdf->stream_length = 0;
+ pdf->last_byte = 0;
+}
+
+void pdf_end_stream(PDF pdf)
+{
+ os_struct *os = pdf->os;
+ switch (os->curbuf) {
+ case PDFOUT_BUF:
+ if (pdf->zip_write_state == ZIP_WRITING)
+ pdf->zip_write_state = ZIP_FINISH;
+ /*tex This sets| pdf->last_byte|. */
+ pdf_flush(pdf);
+ break;
+ case OBJSTM_BUF:
+ normal_error("pdf backend", "bad buffer in end stream, case 1");
+ break;
+ default:
+ normal_error("pdf backend", "bad buffer in end stream, case 2");
+ }
+ pdf->stream_deflate = false;
+ pdf->stream_writing = false;
+ /*tex This doesn't really belong to the stream: */
+ pdf_out(pdf, '\n');
+ pdf_puts(pdf, "endstream");
+ /*tex Write the stream |/Length|. */
+
+ if (pdf->seek_write_length && pdf->draftmode == 0) {
+ xfseeko(pdf->file, (off_t)pdf->stream_length_offset+12, SEEK_SET, pdf->job_name);
+ fprintf(pdf->file, " ");
+ xfseeko(pdf->file, (off_t)pdf->stream_length_offset, SEEK_SET, pdf->job_name);
+ fprintf(pdf->file, "%" LONGINTEGER_PRI "i >>", (LONGINTEGER_TYPE) pdf->stream_length);
+ xfseeko(pdf->file, 0, SEEK_END, pdf->job_name);
+ }
+ pdf->seek_write_length = false;
+}
+
+/*tex
+
+ To print |scaled| value to \PDF\ output we need some subroutines to ensure
+ accurary.
+
+*/
+
+
+/*tex We max out at $2^{31}-1$. */
+
+#define max_integer 0x7FFFFFFF
+
+/*tex scaled value corresponds to 100in, exact, 473628672 */
+
+scaled one_hundred_inch = 7227 * 65536;
+
+/*tex scaled value corresponds to 1in (rounded to 4736287) */
+
+scaled one_inch = (7227 * 65536 + 50) / 100;
+
+/*tex scaled value corresponds to 1truein (rounded!) */
+
+scaled one_true_inch = (7227 * 65536 + 50) / 100;
+
+/*tex scaled value corresponds to 100bp */
+
+scaled one_hundred_bp = (7227 * 65536) / 72;
+
+/*tex scaled value corresponds to 1bp (rounded to 65782) */
+
+scaled one_bp = 65781;
+
+/*tex
+
+ One basepoint is set to exactly 65781, as in \TEX\ itself, because this value
+ is also used for |\pdfpxdimen|.
+
+*/
+
+int ten_pow[10] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 /* $10^0..10^9$ */
+};
+
+/*tex
+
+ The function |divide_scaled| divides |s| by |m| using |dd| decimal digits of
+ precision.
+
+*/
+
+scaled round_xn_over_d(scaled x, int n, unsigned int d)
+{
+ boolean positive = true;
+ unsigned t, u, v;
+ if (x < 0) {
+ positive = !positive;
+ x = -(x);
+ }
+ if (n < 0) {
+ positive = !positive;
+ n = -(n);
+ }
+ t = (unsigned) ((x % 0100000) * n);
+ u = (unsigned) (((unsigned) (x) / 0100000) * (unsigned) n + (t / 0100000));
+ v = (u % d) * 0100000 + (t % 0100000);
+ if (u / d >= 0100000)
+ arith_error = true;
+ else
+ u = 0100000 * (u / d) + (v / d);
+ v = v % d;
+ if (2 * v >= d)
+ u++;
+ if (positive)
+ return (scaled) u;
+ else
+ return (-(scaled) u);
+}
+
+void pdf_add_bp(PDF pdf, scaled s)
+{
+ pdffloat a;
+ pdfstructure *p = pdf->pstruct;
+ a.m = i64round(s * p->k1);
+ a.e = pdf->decimal_digits;
+ pdf_check_space(pdf);
+ print_pdffloat(pdf, a);
+ pdf_set_space(pdf);
+}
+
+typedef struct {
+ int obj_type;
+ pdf_object_list *list;
+} pr_entry;
+
+static int comp_page_resources(const void *pa, const void *pb, void *p)
+{
+ int a = ((const pr_entry *) pa)->obj_type;
+ int b = ((const pr_entry *) pb)->obj_type;
+ (void) p;
+ if (a > b)
+ return 1;
+ if (a < b)
+ return -1;
+ return 0;
+}
+
+void addto_page_resources(PDF pdf, pdf_obj_type t, int k)
+{
+ pdf_resource_struct *re;
+ pr_entry *pr, tmp;
+ void **pp;
+ pdf_object_list *p, *item = NULL;
+ re = pdf->page_resources;
+ if (re->resources_tree == NULL) {
+ re->resources_tree = avl_create(comp_page_resources, NULL, &avl_xallocator);
+ if (re->resources_tree == NULL)
+ formatted_error("pdf backend","addto_page_resources(): avl_create() page_resource_tree failed");
+ }
+ tmp.obj_type = t;
+ pr = (pr_entry *) avl_find(re->resources_tree, &tmp);
+ if (pr == NULL) {
+ pr = xtalloc(1, pr_entry);
+ pr->obj_type = t;
+ pr->list = NULL;
+ pp = avl_probe(re->resources_tree, pr);
+ if (pp == NULL)
+ formatted_error("pdf backend","addto_page_resources(): avl_probe() out of memory in insertion");
+ }
+ if (pr->list == NULL) {
+ item = xtalloc(1, pdf_object_list);
+ item->link = NULL;
+ item->info = k;
+ pr->list = item;
+ if (obj_type(pdf, k) == (int)t) {
+ /*tex |k| is an object number. */
+ set_obj_scheduled(pdf, k);
+ }
+ } else {
+ for (p = pr->list; p->info != k && p->link != NULL; p = p->link);
+ if (p->info != k) {
+ item = xtalloc(1, pdf_object_list);
+ item->link = NULL;
+ item->info = k;
+ p->link = item;
+ if (obj_type(pdf, k) == (int)t)
+ set_obj_scheduled(pdf, k);
+ }
+ }
+}
+
+pdf_object_list *get_page_resources_list(PDF pdf, pdf_obj_type t)
+{
+ pdf_resource_struct *re = pdf->page_resources;
+ pr_entry *pr, tmp;
+ if (re == NULL || re->resources_tree == NULL)
+ return NULL;
+ tmp.obj_type = t;
+ pr = (pr_entry *) avl_find(re->resources_tree, &tmp);
+ if (pr == NULL)
+ return NULL;
+ return pr->list;
+}
+
+static void reset_page_resources(PDF pdf)
+{
+ pdf_resource_struct *re = pdf->page_resources;
+ pr_entry *p;
+ struct avl_traverser t;
+ pdf_object_list *l1, *l2;
+ if (re == NULL || re->resources_tree == NULL)
+ return;
+ avl_t_init(&t, re->resources_tree);
+ for (p = avl_t_first(&t, re->resources_tree); p != NULL; p = avl_t_next(&t)) {
+ if (p->list != NULL) {
+ for (l1 = p->list; l1 != NULL; l1 = l2) {
+ l2 = l1->link;
+ free(l1);
+ }
+ /*tex We reset but the AVL tree remains! */
+ p->list = NULL;
+ }
+ }
+}
+
+static void destroy_pg_res_tree(void *pa, void *param)
+{
+ (void) param;
+ xfree(pa);
+}
+
+static void destroy_page_resources_tree(PDF pdf)
+{
+ pdf_resource_struct *re = pdf->page_resources;
+ reset_page_resources(pdf);
+ if (re->resources_tree != NULL)
+ avl_destroy(re->resources_tree, destroy_pg_res_tree);
+ re->resources_tree = NULL;
+}
+
+static void pdf_print_fw_int(PDF pdf, longinteger n)
+{
+ unsigned char digits[11];
+
+ int k = 10;
+ do {
+ k--;
+ digits[k] = (unsigned char) ('0' + (n % 10));
+ n /= 10;
+ } while (k != 0);
+ if (n!=0) {
+ /*tex The absolute value of $n$ is greater than 9999999999. */
+ normal_error("pdf backend", "offset exceeds 10 bytes, try enabling object compression.");
+ }
+ digits[10]='\0';
+ pdf_puts(pdf, (const char *) digits);
+}
+
+/*tex
+
+ We print out an integer |n| as a fixed number |w| of bytes,. This is used in
+ the |XRef| cross-reference stream creator.
+
+*/
+
+static void pdf_out_bytes(PDF pdf, longinteger n, size_t w)
+{
+ /*tex The number of digits in a number being output. */
+ unsigned char bytes[8];
+ int k = (int) w;
+ do {
+ k--;
+ bytes[k] = (unsigned char) (n % 256);
+ n /= 256;
+ } while (k != 0);
+ pdf_out_block(pdf, (const char *) bytes, w);
+}
+
+void pdf_print_str_ln(PDF pdf, const char *s)
+{
+ pdf_print_str(pdf, s);
+ pdf_out(pdf, '\n');
+}
+
+void pdf_print_toks(PDF pdf, halfword p)
+{
+ int len = 0;
+ char *s = tokenlist_to_cstring(p, true, &len);
+ if (len > 0) {
+ pdf_check_space(pdf);
+ pdf_puts(pdf, s);
+ pdf_set_space(pdf);
+ }
+ xfree(s);
+}
+
+void pdf_add_rect_spec(PDF pdf, halfword r)
+{
+ pdf_add_bp(pdf, pdf_ann_left(r));
+ pdf_add_bp(pdf, pdf_ann_bottom(r));
+ pdf_add_bp(pdf, pdf_ann_right(r));
+ pdf_add_bp(pdf, pdf_ann_top(r));
+}
+
+void pdf_rectangle(PDF pdf, halfword r)
+{
+ pdf_add_name(pdf, "Rect");
+ pdf_begin_array(pdf);
+ pdf_add_rect_spec(pdf, r);
+ pdf_end_array(pdf);
+}
+
+static void init_pdf_outputparameters(PDF pdf)
+{
+ int pk_mode;
+ pdf->draftmode = fix_int(draft_mode_par, 0, 1);
+ pdf->compress_level = fix_int(pdf_compress_level, 0, 9);
+ pdf->decimal_digits = fix_int(pdf_decimal_digits, 3, 5);
+ pdf->gamma = fix_int(pdf_gamma, 0, 1000000);
+ pdf->image_gamma = fix_int(pdf_image_gamma, 0, 1000000);
+ pdf->image_hicolor = fix_int(pdf_image_hicolor, 0, 1);
+ pdf->image_apply_gamma = fix_int(pdf_image_apply_gamma, 0, 1);
+ pdf->objcompresslevel = fix_int(pdf_obj_compress_level, 0, MAX_OBJ_COMPRESS_LEVEL);
+ pdf->recompress = fix_int(pdf_recompress, 0, 1);
+ pdf->inclusion_copy_font = fix_int(pdf_inclusion_copy_font, 0, 1);
+ pdf->pk_resolution = fix_int(pdf_pk_resolution, 72, 8000);
+ pdf->pk_fixed_dpi = fix_int(pdf_pk_fixed_dpi, 0, 1);
+ if ((pdf->minor_version >= 5) && (pdf->objcompresslevel > 0)) {
+ pdf->os_enable = true;
+ } else {
+ if (pdf->objcompresslevel > 0) {
+ normal_warning("pdf backend","objcompresslevel > 0 requires minorversion > 4");
+ pdf->objcompresslevel = 0;
+ }
+ pdf->os_enable = false;
+ }
+ if (pdf->pk_resolution == 0) {
+ /*tex If not set from format file or by user take it from \.{texmf.cnf}. */
+ pdf->pk_resolution = pk_dpi;
+ }
+ pdf->pk_scale_factor = divide_scaled(72, pdf->pk_resolution, pk_decimal_digits(pdf,5));
+ if (!callback_defined(read_pk_file_callback)) {
+ pk_mode = pdf_pk_mode;
+ if (pk_mode != null) {
+ char *s = tokenlist_to_cstring(pk_mode, true, NULL);
+ kpse_init_prog("LUATEX", (unsigned) pdf->pk_resolution, s, nil);
+ xfree(s);
+ } else {
+ kpse_init_prog("LUATEX", (unsigned) pdf->pk_resolution, nil, nil);
+ }
+ if (!kpse_var_value("MKTEXPK"))
+ kpse_set_program_enabled(kpse_pk_format, 1, kpse_src_cmdline);
+ }
+ set_job_id(pdf, year_par, month_par, day_par, time_par);
+ if ((pdf_unique_resname > 0) && (pdf->resname_prefix == NULL))
+ pdf->resname_prefix = get_resname_prefix(pdf);
+}
+
+/*tex
+
+ This checks that we have a name for the generated PDF file and that it's
+ open.
+
+*/
+
+void pdf_write_header(PDF pdf)
+{
+ /*tex Initialize variables for \PDF\ output. */
+ fix_pdf_version(pdf);
+ init_pdf_outputparameters(pdf);
+ fix_pdf_draftmode(pdf);
+ /*tex Write \PDF\ header */
+ pdf_printf(pdf, "%%PDF-%d.%d\n", pdf->major_version, pdf->minor_version);
+ /* Some binary crap. */
+ pdf_out(pdf, '%');
+ pdf_out(pdf, 'L' + 128);
+ pdf_out(pdf, 'U' + 128);
+ pdf_out(pdf, 'A' + 128);
+ pdf_out(pdf, 'T' + 128);
+ pdf_out(pdf, 'E' + 128);
+ pdf_out(pdf, 'X' + 128);
+ pdf_out(pdf, 'P' + 128);
+ pdf_out(pdf, 'D' + 128);
+ pdf_out(pdf, 'F' + 128);
+ pdf_out(pdf, '\n');
+}
+
+void pdf_open_file(PDF pdf) {
+ ensure_output_file_open(pdf, ".pdf");
+}
+
+void ensure_output_state(PDF pdf, output_state s)
+{
+ if (pdf->o_state < s) {
+ if (s > ST_INITIAL) {
+ ensure_output_state(pdf, s - 1);
+ }
+ switch (s - 1) {
+ case ST_INITIAL:
+ fix_o_mode();
+ break;
+ case ST_OMODE_FIX:
+ backend_out_control[backend_control_open_file](pdf);
+ break;
+ case ST_FILE_OPEN:
+ backend_out_control[backend_control_write_header](pdf);
+ break;
+ case ST_HEADER_WRITTEN:
+ break;
+ case ST_FILE_CLOSED:
+ break;
+ default:
+ normal_error("pdf backend","weird output state");
+ }
+ pdf->o_state++;
+ }
+}
+
+/*tex
+
+ Write out an accumulated object stream. The object number and byte offset
+ pairs are generated and appended to the ready buffered object stream. By this
+ the value of \.{/First} can be calculated. Then a new \.{/ObjStm} object is
+ generated, and everything is copied to the PDF output buffer, where also
+ compression is done. When calling this procedure, |pdf_os_mode| must be
+ |true|.
+
+*/
+
+static void pdf_os_write_objstream(PDF pdf)
+{
+ os_struct *os = pdf->os;
+ /*tex |n1|, |n2|: |ObjStm| buffer may be reallocated! */
+ unsigned int i, j, n1, n2;
+ strbuf_s *obuf = os->buf[OBJSTM_BUF];
+ if (os->cur_objstm == 0) {
+ /*tex No object stream started. */
+ return;
+ }
+ /*tex Remember end of collected object stream contents. */
+ n1 = (unsigned int) strbuf_offset(obuf);
+ /*tex This is needed here to calculate |/First| for the |ObjStm| dict */
+ for (i = 0, j = 0; i < os->idx; i++) {
+ /*tex Add object-number/byte-offset list to buffer. */
+ pdf_print_int(pdf, (int) os->obj[i].num);
+ pdf_out(pdf, ' ');
+ pdf_print_int(pdf, (int) os->obj[i].off);
+ if (j == 9 || i == os->idx - 1) {
+ /*tex Print out in groups of ten for better readability. */
+ pdf_out(pdf, '\n');
+ j = 0;
+ } else {
+ pdf_out(pdf, ' ');
+ j++;
+ }
+ }
+ /*tex Remember current buffer end. */
+ n2 = (unsigned int) strbuf_offset(obuf);
+ /*tex Switch to \PDF\ stream writing. */
+ pdf_begin_obj(pdf, (int) os->cur_objstm, OBJSTM_NEVER);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_name(pdf, "Type", "ObjStm");
+ /*tex The number of objects in |ObjStm|. */
+ pdf_dict_add_int(pdf, "N", (int) os->idx);
+ pdf_dict_add_int(pdf, "First", (int) (n2 - n1));
+ pdf_dict_add_streaminfo(pdf);
+ pdf_end_dict(pdf);
+ pdf_begin_stream(pdf);
+ /*tex Write object-number/byte-offset list. */
+ pdf_out_block(pdf, (const char *) (obuf->data + n1), (size_t) (n2 - n1));
+ /*tex Write collected object stream contents. */
+ pdf_out_block(pdf, (const char *) obuf->data, (size_t) n1);
+ pdf_end_stream(pdf);
+ pdf_end_obj(pdf);
+ /*tex We force object stream generation next time. */
+ os->cur_objstm = 0;
+}
+
+/*tex Here comes a bunch of flushers: */
+
+void pdf_begin_dict(PDF pdf)
+{
+ pdf_check_space(pdf);
+ pdf_puts(pdf, "<<");
+ pdf_set_space(pdf);
+}
+
+void pdf_end_dict(PDF pdf)
+{
+ pdf_check_space(pdf);
+ pdf_puts(pdf, ">>");
+ pdf_set_space(pdf);
+}
+
+void pdf_dict_add_bool(PDF pdf, const char *key, int i)
+{
+ pdf_add_name(pdf, key);
+ pdf_add_bool(pdf, i);
+}
+
+void pdf_dict_add_int(PDF pdf, const char *key, int i)
+{
+ pdf_add_name(pdf, key);
+ pdf_add_int(pdf, i);
+}
+
+void pdf_dict_add_name(PDF pdf, const char *key, const char *val)
+{
+ pdf_add_name(pdf, key);
+ pdf_add_name(pdf, val);
+}
+
+void pdf_dict_add_string(PDF pdf, const char *key, const char *val)
+{
+ if (val == NULL)
+ return;
+ pdf_add_name(pdf, key);
+ pdf_check_space(pdf);
+ pdf_print_str(pdf, val);
+ pdf_set_space(pdf);
+}
+
+void pdf_dict_add_ref(PDF pdf, const char *key, int num)
+{
+ pdf_add_name(pdf, key);
+ pdf_add_ref(pdf, num);
+}
+
+void pdf_add_null(PDF pdf)
+{
+ pdf_check_space(pdf);
+ pdf_puts(pdf, "null");
+ pdf_set_space(pdf);
+}
+
+void pdf_add_bool(PDF pdf, int i)
+{
+ pdf_check_space(pdf);
+ if (i == 0)
+ pdf_puts(pdf, "false");
+ else
+ pdf_puts(pdf, "true");
+ pdf_set_space(pdf);
+}
+
+void pdf_add_int(PDF pdf, int i)
+{
+ pdf_check_space(pdf);
+ pdf_print_int(pdf, i);
+ pdf_set_space(pdf);
+}
+
+void pdf_add_longint(PDF pdf, longinteger n)
+{
+ pdf_check_space(pdf);
+ pdf_print_int(pdf, n);
+ pdf_set_space(pdf);
+}
+
+void pdf_add_string(PDF pdf, const char *s)
+{
+ pdf_check_space(pdf);
+ pdf_print_str(pdf, s);
+ pdf_set_space(pdf);
+}
+
+void pdf_add_name(PDF pdf, const char *name)
+{
+ pdf_check_space(pdf);
+ pdf_out(pdf, '/');
+ pdf_puts(pdf, name);
+ pdf_set_space(pdf);
+}
+
+void pdf_add_ref(PDF pdf, int num)
+{
+ pdf_check_space(pdf);
+ pdf_print_int(pdf, num);
+ pdf_puts(pdf, " 0 R");
+ pdf_set_space(pdf);
+}
+
+/*tex
+
+ When we add the stream length and filter entries to a stream dictionary,
+ remember file position for seek.
+
+*/
+
+void pdf_dict_add_streaminfo(PDF pdf)
+{
+ if (pdf->compress_level > 0) {
+ pdf_dict_add_name(pdf, "Filter", "FlateDecode");
+ pdf->stream_deflate = true;
+ }
+ pdf_add_name(pdf, "Length");
+ pdf->stream_length_offset = pdf_offset(pdf) + 1;
+ /*tex Fill in length at |pdf_end_stream| call. */
+ pdf->seek_write_length = true;
+ /*tex We reserve space for 10 decimal digits plus space. */
+ pdf_puts(pdf, " x ");
+ pdf_set_space(pdf);
+}
+
+void pdf_begin_array(PDF pdf)
+{
+ pdf_check_space(pdf);
+ pdf_out(pdf, '[');
+ pdf_set_space(pdf);
+}
+
+void pdf_end_array(PDF pdf)
+{
+ pdf_check_space(pdf);
+ pdf_out(pdf, ']');
+ pdf_set_space(pdf);
+}
+
+void pdf_begin_obj(PDF pdf, int i, int pdf_os_threshold)
+{
+ os_struct *os = pdf->os;
+ ensure_output_state(pdf, ST_HEADER_WRITTEN);
+ pdf_prepare_obj(pdf, i, pdf_os_threshold);
+ switch (os->curbuf) {
+ case PDFOUT_BUF:
+ pdf_printf(pdf, "%d 0 obj\n", (int) i);
+ break;
+ case OBJSTM_BUF:
+ if (pdf->compress_level == 0) {
+ /*tex Some debugging help. */
+ pdf_printf(pdf, "%% %d 0 obj\n", (int) i);
+ }
+ break;
+ default:
+ normal_error("pdf backend","weird begin object");
+ }
+ pdf_reset_space(pdf);
+}
+
+void pdf_end_obj(PDF pdf)
+{
+ os_struct *os = pdf->os;
+ switch (os->curbuf) {
+ case PDFOUT_BUF:
+ /*tex End a \PDF\ object. */
+ pdf_puts(pdf, "\nendobj\n");
+ break;
+ case OBJSTM_BUF:
+ /*tex Tthe number of objects collected so far in ObjStm: */
+ os->idx++;
+ /*tex Only for statistics: */
+ os->o_ctr++;
+ if (os->idx == PDF_OS_MAX_OBJS) {
+ pdf_os_write_objstream(pdf);
+ } else {
+ /*tex Adobe Reader seems to need this. */
+ pdf_out(pdf, '\n');
+ }
+ break;
+ default:
+ normal_error("pdf backend","weird end object");
+ }
+}
+
+/*
+ Needed for embedding fonts.
+
+*/
+
+pdf_obj *pdf_new_stream(void)
+{
+ pdf_obj *stream = xmalloc(sizeof(pdf_obj));
+ stream->length = 0;
+ stream->data = NULL;
+ return stream;
+}
+
+void pdf_add_stream(pdf_obj * stream, unsigned char *buf, long len)
+{
+ int i;
+ assert(stream != NULL);
+ if (stream->data == NULL) {
+ stream->data = xmalloc((unsigned) len);
+ } else {
+ stream->data =
+ xrealloc(stream->data, (unsigned) len + (unsigned) stream->length);
+ }
+ for (i = 0; i < len; i++) {
+ *(stream->data + stream->length + i) = *(buf + i);
+ }
+ stream->length += (unsigned) len;
+}
+
+void pdf_release_obj(pdf_obj * stream)
+{
+ if (stream != NULL) {
+ if (stream->data != NULL) {
+ xfree(stream->data);
+ }
+ xfree(stream);
+ }
+}
+
+/*
+
+ This one converts any string given in in in an allowed PDF string which can
+ be handled by printf et.al.: \.{\\} is escaped to \.{\\\\}, parenthesis are
+ escaped and control characters are octal encoded. This assumes that the
+ string does not contain any already escaped characters!
+
+*/
+
+char *convertStringToPDFString(const char *in, int len)
+{
+ static char pstrbuf[MAX_PSTRING_LEN];
+ char *out = pstrbuf;
+ int i, j, k;
+ char buf[5];
+ j = 0;
+ for (i = 0; i < len; i++) {
+ check_buf((unsigned) j + sizeof(buf), MAX_PSTRING_LEN);
+ if (((unsigned char) in[i] < '!') || ((unsigned char) in[i] > '~')) {
+ /*tex Convert control characters into octal. */
+ k = snprintf(buf, sizeof(buf), "\\%03o", (unsigned int) (unsigned char) in[i]);
+ check_nprintf(k, sizeof(buf));
+ out[j++] = buf[0];
+ out[j++] = buf[1];
+ out[j++] = buf[2];
+ out[j++] = buf[3];
+ } else if ((in[i] == '(') || (in[i] == ')')) {
+ /*tex Escape parenthesis: */
+ out[j++] = '\\';
+ out[j++] = in[i];
+ } else if (in[i] == '\\') {
+ /*tex Escape backslash: */
+ out[j++] = '\\';
+ out[j++] = '\\';
+ } else {
+ /* Copy char : */
+ out[j++] = in[i];
+ }
+ }
+ out[j] = '\0';
+ return pstrbuf;
+}
+
+/*tex
+
+ This one converts any string given in in in an allowed PDF string which is
+ hexadecimal encoded; |sizeof(out)| should be at least $|lin|*2+1$.
+
+*/
+
+static void convertStringToHexString(const char *in, char *out, int lin)
+{
+ int i, k;
+ char buf[3];
+ int j = 0;
+ for (i = 0; i < lin; i++) {
+ k = snprintf(buf, sizeof(buf), "%02X", (unsigned int) (unsigned char) in[i]);
+ check_nprintf(k, sizeof(buf));
+ out[j++] = buf[0];
+ out[j++] = buf[1];
+ }
+ out[j] = '\0';
+}
+
+/*tex
+
+ We compute the ID string as per PDF specification 1.4 9.3 that stipulates
+ only that the two IDs must be identical when the file is created and that
+ they should be reasonably unique. Since it's difficult to get the file size
+ at this point in the execution of pdfTeX and scanning the info dict is also
+ difficult, we start with a simpler implementation using just the first two
+ items.
+
+ A user supplied trailerid had better be an array! So maybe we need to check
+ for |[]| and error otherwise.
+
+*/
+
+static void print_ID(PDF pdf)
+{
+ if ((pdf_suppress_optional_info & 512) == 0) {
+ const char *p = NULL;
+ pdf_add_name(pdf, "ID");
+ p = get_pdf_table_string("trailerid");
+ if (p && strlen(p) > 0) {
+ pdf_puts(pdf,p);
+ } else if (pdf_trailer_id != 0) {
+ /*tex The user provided one: */
+ pdf_print_toks(pdf, pdf_trailer_id);
+ } else {
+ /*tex The system provided one: */
+ time_t t;
+ size_t size;
+ char time_str[32];
+ md5_state_t state;
+ md5_byte_t digest[16];
+ char id[64];
+ char pwd[4096];
+ md5_init(&state);
+ t = pdf->start_time;
+ size = strftime(time_str, sizeof(time_str), "%Y%m%dT%H%M%SZ", gmtime(&t));
+ md5_append(&state, (const md5_byte_t *) time_str, (int) size);
+ if (getcwd(pwd, sizeof(pwd)) == NULL) {
+ formatted_error("pdf backend","getcwd() failed (%s), (path too long?)", strerror(errno));
+ }
+#ifdef WIN32
+ {
+ char *p;
+ for (p = pwd; *p; p++) {
+ if (*p == '\\')
+ *p = '/';
+ else if (IS_KANJI(p))
+ p++;
+ }
+ }
+#endif
+ md5_append(&state, (const md5_byte_t *) pwd, (int) strlen(pwd));
+ md5_append(&state, (const md5_byte_t *) "/", 1);
+ md5_append(&state, (const md5_byte_t *) pdf->file_name, (int) strlen(pdf->file_name));
+ md5_finish(&state, digest);
+ convertStringToHexString((char *) digest, id, 16);
+ pdf_begin_array(pdf);
+ pdf_check_space(pdf);
+ pdf_printf(pdf, "<%s> <%s>", id, id);
+ pdf_set_space(pdf);
+ pdf_end_array(pdf);
+ }
+ }
+}
+
+/*tex
+
+ Here we print the |/CreationDate| entry in the form
+ |(D:YYYYMMDDHHmmSSOHH'mm')|. The main difficulty is get the time zone offset.
+ |strftime()| does this in ISO C99 (e.g. newer glibc) with \%z, but we have to
+ work with other systems (e.g. Solaris 2.5).
+
+*/
+
+#define TIME_STR_SIZE 30
+
+static void makepdftime(PDF pdf)
+{
+ struct tm lt, gmt;
+ size_t size;
+ int i, off, off_hours, off_mins;
+ time_t t = pdf->start_time;
+ char *time_str = pdf->start_time_str;
+ /*tex Get the time. */
+ if (utc_option) {
+ lt = *gmtime(&t);
+ } else {
+ lt = *localtime(&t);
+ }
+ size = strftime(time_str, TIME_STR_SIZE, "D:%Y%m%d%H%M%S", &lt);
+ /*tex Expected format: |YYYYmmddHHMMSS|. */
+ if (size == 0) {
+ /*tex Unexpected, contents of |time_str| is undefined .*/
+ time_str[0] = '\0';
+ return;
+ }
+ /*tex
+ A correction for seconds. the PDF reference expects 00..59, therefore we
+ map 60 and 61 to 59.
+ */
+ if (time_str[14] == '6') {
+ time_str[14] = '5';
+ time_str[15] = '9';
+ /*tex For safety: */
+ time_str[16] = '\0';
+ }
+ /*tex Get the time zone offset. */
+ gmt = *gmtime(&t);
+ off = 60 * (lt.tm_hour - gmt.tm_hour) + lt.tm_min - gmt.tm_min;
+ if (lt.tm_year != gmt.tm_year) {
+ off += (lt.tm_year > gmt.tm_year) ? 1440 : -1440;
+ } else if (lt.tm_yday != gmt.tm_yday) {
+ off += (lt.tm_yday > gmt.tm_yday) ? 1440 : -1440;
+ }
+ if (off == 0) {
+ time_str[size++] = 'Z';
+ time_str[size] = 0;
+ } else {
+ off_hours = off / 60;
+ off_mins = abs(off - off_hours * 60);
+ i = snprintf(&time_str[size], 9, "%+03d'%02d'", off_hours, off_mins);
+ check_nprintf(i, 9);
+ }
+ pdf->start_time = t;
+}
+
+void initialize_start_time(PDF pdf)
+{
+ if (pdf->start_time == 0) {
+ pdf->start_time = get_start_time();
+ pdf->start_time_str = xtalloc(TIME_STR_SIZE, char);
+ makepdftime(pdf);
+ }
+}
+
+char *getcreationdate(PDF pdf)
+{
+ initialize_start_time(pdf);
+ return pdf->start_time_str;
+}
+
+void remove_pdffile(PDF pdf)
+{
+ if (pdf != NULL) {
+ if (!kpathsea_debug && pdf->file_name && (pdf->draftmode == 0)) {
+ xfclose(pdf->file, pdf->file_name);
+ remove(pdf->file_name);
+ }
+ }
+}
+
+/*tex We use this checker in other modules. It is not pdf specific. */
+
+void check_o_mode(PDF pdf, const char *s, int o_mode_bitpattern, boolean strict)
+{
+
+ output_mode o_mode;
+ const char *m = NULL;
+ if (lua_only) {
+ normal_error("lua only","no backend present, needed for what you asked for");
+ return ;
+ }
+ if (output_mode_used == OMODE_NONE)
+ o_mode = get_o_mode();
+ else
+ o_mode = output_mode_used;
+ /*tex This is used by synctex, we need to use output_mode_used there. */
+ pdf->o_mode = output_mode_used;
+ if (!((1 << o_mode) & o_mode_bitpattern)) {
+ switch (o_mode) {
+ case OMODE_DVI:
+ m = "DVI";
+ break;
+ case OMODE_PDF:
+ m = "PDF";
+ break;
+ default:
+ normal_error("pdf backend","weird output state");
+ }
+ if (strict)
+ formatted_error("pdf backend", "%s not allowed in %s mode (outputmode = %d)",s, m, (int) output_mode_par);
+ else
+ formatted_warning("pdf backend", "%s not allowed in %s mode (outputmode = %d)",s, m, (int) output_mode_par);
+ } else if (strict)
+ ensure_output_state(pdf, ST_HEADER_WRITTEN);
+}
+
+void ensure_output_file_open(PDF pdf, const char *ext)
+{
+ char *fn;
+ if (pdf->file_name != NULL)
+ return;
+ if (job_name == 0)
+ open_log_file();
+ fn = pack_job_name(ext);
+ if (pdf->draftmode == 0 || output_mode_used == OMODE_DVI) {
+ while (!lua_b_open_out(&pdf->file, fn))
+ fn = prompt_file_name("file name for output", ext);
+ }
+ pdf->file_name = fn;
+}
+
+/*tex till here */
+
+void set_job_id(PDF pdf, int year, int month, int day, int time)
+{
+ char *name_string, *format_string, *s;
+ size_t slen;
+ int i;
+ if (pdf->job_id_string != NULL)
+ return;
+ name_string = makecstring(job_name);
+ format_string = makecstring(format_ident);
+ slen = SMALL_BUF_SIZE + strlen(name_string) + strlen(format_string) + strlen(luatex_banner);
+ s = xtalloc(slen, char);
+ /*tex The \WEBC\ version string starts with a space. (Really?) */
+ i = snprintf(s, slen, "%.4d/%.2d/%.2d %.2d:%.2d %s %s %s", year, month, day, time / 60, time % 60, name_string, format_string, luatex_banner);
+ check_nprintf(i, slen);
+ pdf->job_id_string = xstrdup(s);
+ xfree(s);
+ xfree(name_string);
+ xfree(format_string);
+}
+
+char *get_resname_prefix(PDF pdf)
+{
+ static char name_str[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ /*tex We make a tag of 6 characters long. */
+ static char prefix[7];
+ short i;
+ size_t base = strlen(name_str);
+ unsigned long crc = crc32(0L, Z_NULL, 0);
+ crc = crc32(crc, (Bytef *) pdf->job_id_string, (uInt) strlen(pdf->job_id_string));
+ for (i = 0; i < 6; i++) {
+ prefix[i] = name_str[crc % base];
+ crc /= base;
+ }
+ prefix[6] = '\0';
+ return prefix;
+}
+
+void pdf_begin_page(PDF pdf)
+{
+ int xform_attributes;
+ int xform_type = 0;
+ scaled form_margin = pdf_xform_margin;
+ ensure_output_state(pdf, ST_HEADER_WRITTEN);
+ init_pdf_pagecalculations(pdf);
+ if (pdf->page_resources == NULL) {
+ pdf->page_resources = xtalloc(1, pdf_resource_struct);
+ pdf->page_resources->resources_tree = NULL;
+ }
+ pdf->page_resources->last_resources = pdf_create_obj(pdf, obj_type_others, 0);
+ reset_page_resources(pdf);
+
+ if (global_shipping_mode == SHIPPING_PAGE) {
+ pdf->last_page = pdf_get_obj(pdf, obj_type_page, total_pages + 1, 0);
+ /*tex Mark that this page has been created. */
+ set_obj_aux(pdf, pdf->last_page, 1);
+ pdf->last_stream = pdf_create_obj(pdf, obj_type_pagestream, 0);
+ pdf_begin_obj(pdf, pdf->last_stream, OBJSTM_NEVER);
+ pdf->last_thread = null;
+ pdf_begin_dict(pdf);
+ } else {
+ xform_type = obj_xform_type(pdf, pdf_cur_form) ;
+ pdf_begin_obj(pdf, pdf_cur_form, OBJSTM_NEVER);
+ pdf->last_stream = pdf_cur_form;
+ /*tex Write out the |Form| stream header */
+ pdf_begin_dict(pdf);
+ if (xform_type == 0) {
+ pdf_dict_add_name(pdf, "Type", "XObject");
+ pdf_dict_add_name(pdf, "Subtype", "Form");
+ pdf_dict_add_int(pdf, "FormType", 1);
+ }
+ xform_attributes = pdf_xform_attr;
+ /*tex Now stored in the object: */
+ form_margin = obj_xform_margin(pdf, pdf_cur_form);
+ if (xform_attributes != null)
+ pdf_print_toks(pdf, xform_attributes);
+ if (obj_xform_attr(pdf, pdf_cur_form) != null) {
+ pdf_print_toks(pdf, obj_xform_attr(pdf, pdf_cur_form));
+ delete_token_ref(obj_xform_attr(pdf, pdf_cur_form));
+ set_obj_xform_attr(pdf, pdf_cur_form, null);
+ }
+ if (obj_xform_attr_str(pdf, pdf_cur_form) != null) {
+ lua_pdf_literal(pdf, obj_xform_attr_str(pdf, pdf_cur_form),1);
+ luaL_unref(Luas, LUA_REGISTRYINDEX, obj_xform_attr_str(pdf, pdf_cur_form));
+ set_obj_xform_attr_str(pdf, pdf_cur_form, null);
+ }
+ if (xform_type == 0 || xform_type == 1 || xform_type == 3) {
+ pdf_add_name(pdf, "BBox");
+ pdf_begin_array(pdf);
+ pdf_add_bp(pdf, -form_margin);
+ pdf_add_bp(pdf, -form_margin);
+ pdf_add_bp(pdf, pdf->page_size.h + form_margin);
+ pdf_add_bp(pdf, pdf->page_size.v + form_margin);
+ pdf_end_array(pdf);
+ }
+ if (xform_type == 0 || xform_type == 2 || xform_type == 3) {
+ pdf_add_name(pdf, "Matrix");
+ pdf_begin_array(pdf);
+ pdf_add_int(pdf, 1);
+ pdf_add_int(pdf, 0);
+ pdf_add_int(pdf, 0);
+ pdf_add_int(pdf, 1);
+ pdf_add_int(pdf, 0);
+ pdf_add_int(pdf, 0);
+ pdf_end_array(pdf);
+ }
+ pdf_dict_add_ref(pdf, "Resources", pdf->page_resources->last_resources);
+ }
+ /*tex Start a stream of page or form contents: */
+ pdf_dict_add_streaminfo(pdf);
+ pdf_end_dict(pdf);
+ pdf_begin_stream(pdf);
+ /*tex Start with an empty stack: */
+ pos_stack_used = 0;
+ if (global_shipping_mode == SHIPPING_PAGE) {
+ colorstackpagestart();
+ }
+ if (global_shipping_mode == SHIPPING_PAGE)
+ pdf_out_colorstack_startpage(pdf);
+}
+
+void print_pdf_table_string(PDF pdf, const char *s)
+{
+ size_t len;
+ const char *ls;
+ lua_rawgeti(Luas, LUA_REGISTRYINDEX, lua_key_index(pdf_data));
+ lua_rawget(Luas, LUA_REGISTRYINDEX);
+ lua_pushstring(Luas, s);
+ lua_rawget(Luas, -2);
+ if (lua_type(Luas, -1) == LUA_TSTRING) {
+ ls = lua_tolstring(Luas, -1, &len);
+ if (len > 0) {
+ pdf_check_space(pdf);
+ pdf_out_block(pdf, ls, len);
+ pdf_set_space(pdf);
+ }
+ }
+ lua_pop(Luas, 2);
+}
+
+const char *get_pdf_table_string(const char *s)
+{
+ const_lstring ls;
+ lua_rawgeti(Luas, LUA_REGISTRYINDEX, lua_key_index(pdf_data));
+ lua_rawget(Luas, LUA_REGISTRYINDEX);
+ lua_pushstring(Luas, s);
+ lua_rawget(Luas, -2);
+ if (lua_type(Luas, -1) == LUA_TSTRING) {
+ ls.s = lua_tolstring(Luas, -1, &ls.l);
+ /*tex
+ Here |s| is supposed to be anchored (e.g.\ in the registry) so it's
+ not garbage collected.
+ */
+ lua_pop(Luas, 2);
+ return ls.s;
+ }
+ lua_pop(Luas, 2);
+ return NULL ;
+}
+
+void pdf_end_page(PDF pdf)
+{
+ char s[64], *p;
+ int j, annots = 0, beads = 0, callback_id;
+ pdf_resource_struct *res_p = pdf->page_resources;
+ pdf_resource_struct local_page_resources;
+ pdf_object_list *annot_list, *bead_list, *link_list, *ol, *ol1;
+ /*tex We save |pdf->page_size| during flushing pending forms: */
+ scaledpos save_cur_page_size;
+ shipping_mode_e save_shipping_mode;
+ int save_pdf_cur_form;
+ int xform_resources;
+ int page_resources, page_attributes;
+ int procset = PROCSET_PDF;
+ /*tex Finish the stream of page or form contents: */
+ pdf_goto_pagemode(pdf);
+ if (pos_stack_used > 0) {
+ formatted_error("pdf backend","%u unmatched 'save' after %s shipout", (unsigned int) pos_stack_used,
+ ((global_shipping_mode == SHIPPING_PAGE) ? "page" : "form"));
+ }
+ pdf_end_stream(pdf);
+ pdf_end_obj(pdf);
+ callback_id = callback_defined(finish_pdfpage_callback);
+ if (callback_id > 0)
+ run_callback(callback_id, "b->",(global_shipping_mode == SHIPPING_PAGE));
+ if (global_shipping_mode == SHIPPING_PAGE) {
+ pdf->last_pages = pdf_do_page_divert(pdf, pdf->last_page, 0);
+ /*tex Write out the |/Page| object. */
+ pdf_begin_obj(pdf, pdf->last_page, OBJSTM_ALWAYS);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_name(pdf, "Type", "Page");
+ pdf_dict_add_ref(pdf, "Contents", pdf->last_stream);
+ pdf_dict_add_ref(pdf, "Resources", res_p->last_resources);
+ pdf_add_name(pdf, "MediaBox");
+ pdf_begin_array(pdf);
+ pdf_add_int(pdf, 0);
+ pdf_add_int(pdf, 0);
+ pdf_add_bp(pdf, pdf->page_size.h);
+ pdf_add_bp(pdf, pdf->page_size.v);
+ pdf_end_array(pdf);
+ page_attributes = pdf_page_attr ;
+ if (page_attributes != null)
+ pdf_print_toks(pdf, page_attributes);
+ print_pdf_table_string(pdf, "pageattributes");
+ pdf_dict_add_ref(pdf, "Parent", pdf->last_pages);
+ if (pdf->img_page_group_val != 0) {
+ pdf_dict_add_ref(pdf, "Group", pdf->img_page_group_val);
+ }
+ annot_list = get_page_resources_list(pdf, obj_type_annot);
+ link_list = get_page_resources_list(pdf, obj_type_link);
+ if (annot_list != NULL || link_list != NULL) {
+ annots = pdf_create_obj(pdf, obj_type_annots, 0);
+ pdf_dict_add_ref(pdf, "Annots", annots);
+ }
+ bead_list = get_page_resources_list(pdf, obj_type_bead);
+ if (bead_list != NULL) {
+ beads = pdf_create_obj(pdf, obj_type_beads, 0);
+ pdf_dict_add_ref(pdf, "B", beads);
+ }
+ pdf_end_dict(pdf);
+ pdf_end_obj(pdf);
+ pdf->img_page_group_val = 0;
+ /*tex Generate an array of annotations or beads in page. */
+ if (annot_list != NULL || link_list != NULL) {
+ pdf_begin_obj(pdf, annots, OBJSTM_ALWAYS);
+ pdf_begin_array(pdf);
+ while (annot_list != NULL) {
+ pdf_add_ref(pdf, annot_list->info);
+ annot_list = annot_list->link;
+ }
+ while (link_list != NULL) {
+ pdf_add_ref(pdf, link_list->info);
+ link_list = link_list->link;
+ }
+ pdf_end_array(pdf);
+ pdf_end_obj(pdf);
+ }
+ if (bead_list != NULL) {
+ pdf_begin_obj(pdf, beads, OBJSTM_ALWAYS);
+ pdf_begin_array(pdf);
+ while (bead_list != NULL) {
+ pdf_add_ref(pdf, bead_list->info);
+ bead_list = bead_list->link;
+ }
+ pdf_end_array(pdf);
+ pdf_end_obj(pdf);
+ }
+ }
+ /*tex Write out the resource lists and pending raw objects. */
+ ol = get_page_resources_list(pdf, obj_type_obj);
+ while (ol != NULL) {
+ if (!is_obj_written(pdf, ol->info))
+ pdf_write_obj(pdf, ol->info);
+ ol = ol->link;
+ }
+ /*tex
+ When flushing pending forms we need to save and restore resource lists
+ which are also used by page shipping. Saving and restoring
+ |pdf->page_size| is needed for proper writing out pending \PDF\ marks.
+ */
+ ol = get_page_resources_list(pdf, obj_type_xform);
+ while (ol != NULL) {
+ if (!is_obj_written(pdf, ol->info)) {
+ save_pdf_cur_form = pdf_cur_form;
+ pdf_cur_form = ol->info;
+ save_cur_page_size = pdf->page_size;
+ save_shipping_mode = global_shipping_mode;
+ pdf->page_resources = &local_page_resources;
+ local_page_resources.resources_tree = NULL;
+ ship_out(pdf, obj_xform_box(pdf, pdf_cur_form), SHIPPING_FORM);
+ /*tex Restore the page size and page resources. */
+ pdf->page_size = save_cur_page_size;
+ global_shipping_mode = save_shipping_mode;
+ destroy_page_resources_tree(pdf);
+ pdf->page_resources = res_p;
+ pdf_cur_form = save_pdf_cur_form;
+ }
+ ol = ol->link;
+ }
+ /*tex Write out pending images. */
+ ol = get_page_resources_list(pdf, obj_type_ximage);
+ while (ol != NULL) {
+ if (!is_obj_written(pdf, ol->info))
+ pdf_write_image(pdf, ol->info);
+ ol = ol->link;
+ }
+ if (global_shipping_mode == SHIPPING_PAGE) {
+ /*tex Write out pending \PDF\ marks and annotations. */
+ ol = get_page_resources_list(pdf, obj_type_annot);
+ while (ol != NULL) {
+ if (ol->info > 0 && obj_type(pdf, ol->info) == obj_type_annot) {
+ /*tex Here |j| points to |pdf_annot_node|: */
+ j = obj_annot_ptr(pdf, ol->info);
+ pdf_begin_obj(pdf, ol->info, OBJSTM_ALWAYS);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_name(pdf, "Type", "Annot");
+ pdf_print_toks(pdf, pdf_annot_data(j));
+ pdf_rectangle(pdf, j);
+ pdf_end_dict(pdf);
+ pdf_end_obj(pdf);
+ }
+ ol = ol->link;
+ }
+ /*tex Write out PDF link annotations. */
+ if ((ol = get_page_resources_list(pdf, obj_type_link)) != NULL) {
+ while (ol != NULL) {
+ j = obj_annot_ptr(pdf, ol->info);
+ pdf_begin_obj(pdf, ol->info, OBJSTM_ALWAYS);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_name(pdf, "Type", "Annot");
+ if (pdf_action_type(pdf_link_action(j)) != pdf_action_user)
+ pdf_dict_add_name(pdf, "Subtype", "Link");
+ if (pdf_link_attr(j) != null)
+ pdf_print_toks(pdf, pdf_link_attr(j));
+ pdf_rectangle(pdf, j);
+ if (pdf_action_type(pdf_link_action(j)) != pdf_action_user)
+ pdf_puts(pdf, "/A ");
+ write_action(pdf, pdf_link_action(j));
+ pdf_end_dict(pdf);
+ pdf_end_obj(pdf);
+ ol = ol->link;
+ }
+ /*tex Flush |pdf_start_link_node|'s created by |append_link|. */
+ ol = get_page_resources_list(pdf, obj_type_link);
+ while (ol != NULL) {
+ j = obj_annot_ptr(pdf, ol->info);
+ /*tex
+ Nodes with |subtype = pdf_link_data_node| were created by
+ |append_link| and must be flushed here, as they are not
+ linked in any list.
+ */
+ if (subtype(j) == pdf_link_data_node)
+ flush_node(j);
+ ol = ol->link;
+ }
+ }
+ /*tex Write out \PDF\ mark destinations. */
+ write_out_pdf_mark_destinations(pdf);
+ /*tex Write out \PDF\ bead rectangle specifications. */
+ print_bead_rectangles(pdf);
+ }
+ /*tex Write out resources dictionary. */
+ pdf_begin_obj(pdf, res_p->last_resources, OBJSTM_ALWAYS);
+ pdf_begin_dict(pdf);
+ /*tex Print additional resources. */
+ if (global_shipping_mode == SHIPPING_PAGE) {
+ page_resources = pdf_page_resources;
+ if (page_resources != null) {
+ pdf_print_toks(pdf, page_resources);
+ }
+ print_pdf_table_string(pdf, "pageresources");
+ } else {
+ xform_resources = pdf_xform_resources;
+ if (xform_resources != null) {
+ pdf_print_toks(pdf, xform_resources);
+ }
+ if (obj_xform_resources(pdf, pdf_cur_form) != null) {
+ pdf_print_toks(pdf, obj_xform_resources(pdf, pdf_cur_form));
+ delete_token_ref(obj_xform_resources(pdf, pdf_cur_form));
+ set_obj_xform_resources(pdf, pdf_cur_form, null);
+ }
+ if (obj_xform_resources_str(pdf, pdf_cur_form) != null) {
+ lua_pdf_literal(pdf, obj_xform_resources_str(pdf, pdf_cur_form),1);
+ luaL_unref(Luas, LUA_REGISTRYINDEX, obj_xform_resources_str(pdf, pdf_cur_form));
+ set_obj_xform_resources_str(pdf, pdf_cur_form, null);
+ }
+ }
+ /*tex Generate font resources. */
+ if ((ol = get_page_resources_list(pdf, obj_type_font)) != NULL) {
+ pdf_add_name(pdf, "Font");
+ pdf_begin_dict(pdf);
+ while (ol != NULL) {
+ p = s;
+ p += snprintf(p, 20, "F%i", obj_info(pdf, ol->info));
+ if (pdf->resname_prefix != NULL)
+ p += snprintf(p, 20, "%s", pdf->resname_prefix);
+ pdf_dict_add_ref(pdf, s, ol->info);
+ ol = ol->link;
+ }
+ pdf_end_dict(pdf);
+ procset |= PROCSET_TEXT;
+ }
+ /*tex Generate |XObject| resources. */
+ ol = get_page_resources_list(pdf, obj_type_xform);
+ ol1 = get_page_resources_list(pdf, obj_type_ximage);
+ if (ol != NULL || ol1 != NULL) {
+ pdf_add_name(pdf, "XObject");
+ pdf_begin_dict(pdf);
+ while (ol != NULL) {
+ p = s;
+ p += snprintf(p, 20, "Fm%i", obj_info(pdf, ol->info));
+ if (pdf->resname_prefix != NULL)
+ p += snprintf(p, 20, "%s", pdf->resname_prefix);
+ pdf_dict_add_ref(pdf, s, ol->info);
+ ol = ol->link;
+ }
+ while (ol1 != null) {
+ p = s;
+ p += snprintf(p, 20, "Im%i", obj_info(pdf, ol1->info));
+ if (pdf->resname_prefix != NULL)
+ p += snprintf(p, 20, "%s", pdf->resname_prefix);
+ pdf_dict_add_ref(pdf, s, ol1->info);
+ procset |= img_procset(idict_array[obj_data_ptr(pdf, ol1->info)]);
+ ol1 = ol1->link;
+ }
+ pdf_end_dict(pdf);
+ }
+ /*tex Generate |ProcSet| in version 1.*/
+ if (pdf->major_version == 1) {
+ pdf_add_name(pdf, "ProcSet");
+ pdf_begin_array(pdf);
+ if ((procset & PROCSET_PDF) != 0)
+ pdf_add_name(pdf, "PDF");
+ if ((procset & PROCSET_TEXT) != 0)
+ pdf_add_name(pdf, "Text");
+ if ((procset & PROCSET_IMAGE_B) != 0)
+ pdf_add_name(pdf, "ImageB");
+ if ((procset & PROCSET_IMAGE_C) != 0)
+ pdf_add_name(pdf, "ImageC");
+ if ((procset & PROCSET_IMAGE_I) != 0)
+ pdf_add_name(pdf, "ImageI");
+ pdf_end_array(pdf);
+ }
+ pdf_end_dict(pdf);
+ pdf_end_obj(pdf);
+}
+
+/*tex
+
+ We're now ready to wrap up the output file. Destinations that have been
+ referenced but don't exists have |obj_dest_ptr=null|. Leaving them undefined
+ might cause troubles for PDF browsers, so we need to fix them; they point to
+ the last page.
+
+*/
+
+static void check_nonexisting_destinations(PDF pdf)
+{
+ int k;
+ for (k = pdf->head_tab[obj_type_dest]; k != 0; k = obj_link(pdf, k)) {
+ if (obj_dest_ptr(pdf, k) == null) {
+ if (obj_info(pdf, k) < 0) {
+ char *ss = makecstring(-obj_info(pdf, k));
+ formatted_warning("pdf backend", "unreferenced destination with name '%s'",ss);
+ } else {
+ formatted_warning("pdf backend", "unreferenced destination with num '%d'",obj_info(pdf,k));
+ }
+
+ pdf_begin_obj(pdf, k, OBJSTM_ALWAYS);
+ pdf_begin_array(pdf);
+ pdf_add_ref(pdf, pdf->last_page);
+ pdf_add_name(pdf, "Fit");
+ pdf_end_array(pdf);
+ pdf_end_obj(pdf);
+ }
+ }
+}
+
+static void check_nonexisting_pages(PDF pdf)
+{
+ struct avl_traverser t;
+ oentry *p;
+ struct avl_table *page_tree = pdf->obj_tree[obj_type_page];
+ avl_t_init(&t, page_tree);
+ /*tex Search from the end backward until the last real page is found. */
+ for (p = avl_t_last(&t, page_tree); p != NULL && obj_aux(pdf, p->objptr) == 0; p = avl_t_prev(&t)) {
+ formatted_warning("pdf backend", "page %d has been referenced but does not exist",obj_info(pdf, p->objptr));
+ }
+}
+
+/*tex
+
+ If the same keys in a dictionary are given several times, then it is not
+ defined which value is choosen by an application. Therefore the keys
+ |/Producer| and |/Creator| are only set if the token list |pdf_info_toks|
+ converted to a string does not contain these key strings.
+
+*/
+
+static boolean substr_of_str(const char *s, const char *t)
+{
+ if (strstr(t, s) == NULL)
+ return false;
+ return true;
+}
+
+static int pdf_print_info(PDF pdf, int luatexversion, str_number luatexrevision)
+{
+ boolean creator_given = false;
+ boolean producer_given = false;
+ boolean creationdate_given = false;
+ boolean moddate_given = false;
+ boolean trapped_given = false;
+ char *s = NULL;
+ const char *p = NULL;
+ int k, len = 0;
+ k = pdf_create_obj(pdf, obj_type_info, 0);
+ pdf_begin_obj(pdf, k, 3);
+ pdf_begin_dict(pdf);
+ if (pdf_info_toks != 0) {
+ s = tokenlist_to_cstring(pdf_info_toks, true, &len);
+ creator_given = substr_of_str("/Creator", s);
+ producer_given = substr_of_str("/Producer", s);
+ creationdate_given = substr_of_str("/CreationDate", s);
+ moddate_given = substr_of_str("/ModDate", s);
+ trapped_given = substr_of_str("/Trapped", s);
+ }
+ p = get_pdf_table_string("info");
+ if (p && strlen(p) > 0) {
+ creator_given = creator_given || substr_of_str("/Creator", p);
+ producer_given = producer_given || substr_of_str("/Producer", p);
+ creationdate_given = creationdate_given || substr_of_str("/CreationDate", p);
+ moddate_given = moddate_given || substr_of_str("/ModDate", p);
+ trapped_given = trapped_given || substr_of_str("/Trapped", p);
+ }
+ if (pdf_info_toks != null) {
+ if (len > 0) {
+ pdf_check_space(pdf);
+ pdf_puts(pdf, s);
+ pdf_set_space(pdf);
+ xfree(s);
+ }
+ delete_token_ref(pdf_info_toks);
+ pdf_info_toks = null;
+ }
+ if (p && strlen(p) > 0) {
+ pdf_check_space(pdf);
+ pdf_puts(pdf, p);
+ pdf_set_space(pdf);
+ }
+ if ((pdf_suppress_optional_info & 128) == 0 && !producer_given) {
+ pdf_add_name(pdf, "Producer");
+ pdf_puts(pdf, " (LuaTeX-");
+ pdf_puts(pdf, luatex_version_string);
+ pdf_out(pdf, ')');
+ }
+ if ((pdf_suppress_optional_info & 16) == 0 && !creator_given) {
+ pdf_dict_add_string(pdf, "Creator", "TeX");
+ }
+ if ((pdf_suppress_optional_info & 32) == 0 && !creationdate_given) {
+ initialize_start_time(pdf);
+ pdf_dict_add_string(pdf, "CreationDate", pdf->start_time_str);
+ }
+ if ((pdf_suppress_optional_info & 64) == 0 && !moddate_given) {
+ initialize_start_time(pdf);
+ pdf_dict_add_string(pdf, "ModDate", pdf->start_time_str);
+ }
+ if ((pdf_suppress_optional_info & 256) == 0 && !trapped_given) {
+ pdf_dict_add_name(pdf, "Trapped", "False");
+ }
+ if ((pdf_suppress_optional_info & 1) == 0) {
+ pdf_dict_add_string(pdf, "PTEX.FullBanner", luatex_banner);
+ }
+ pdf_end_dict(pdf);
+ pdf_end_obj(pdf);
+ return k;
+}
+
+static void build_free_object_list(PDF pdf)
+{
+ int k;
+ int l = 0;
+ /*tex A |null| object at the begin of a list of free objects. */
+ set_obj_fresh(pdf, l);
+ for (k = 1; k <= pdf->obj_ptr; k++) {
+ if (!is_obj_written(pdf, k)) {
+ set_obj_link(pdf, l, k);
+ l = k;
+ }
+ }
+ set_obj_link(pdf, l, 0);
+}
+
+/*tex
+
+ Now we can the finish of \PDF\ output file. At this moment all |/Page|
+ objects are already written completely to \PDF\ output file.
+
+*/
+
+void pdf_finish_file(PDF pdf, int fatal_error) {
+ if (fatal_error) {
+ remove_pdffile(static_pdf); /* will become remove_output_file */
+ print_err(" ==> Fatal error occurred, no output PDF file produced!");
+ } else {
+ int i, j, k;
+ int root, info;
+ int xref_stm = 0;
+ int outlines = 0;
+ int threads = 0;
+ int names_tree = 0;
+ size_t xref_offset_width;
+ int luatexversion = luatex_version;
+ str_number luatexrevision = get_luatexrevision();
+ int callback_id = callback_defined(stop_run_callback);
+ int callback_id1 = callback_defined(finish_pdffile_callback);
+ if (total_pages == 0 && !pdf->force_file) {
+ if (callback_id == 0) {
+ normal_warning("pdf backend","no pages of output.");
+ } else if (callback_id > 0) {
+ run_callback(callback_id, "->");
+ }
+ if (pdf->gone > 0) {
+ /* number of bytes gone */
+ normal_error("pdf backend","already written content discarded, no output file produced.");
+ }
+ } else {
+ if (pdf->draftmode == 0) {
+ /*tex We make sure that the output file name has been already created. */
+ pdf_flush(pdf);
+ /*tex Flush page 0 objects from JBIG2 images, if any. */
+ flush_jbig2_page0_objects(pdf);
+ if (callback_id1 > 0) {
+ run_callback(callback_id1, "->");
+ }
+ if (total_pages > 0) {
+ check_nonexisting_pages(pdf);
+ check_nonexisting_destinations(pdf);
+ }
+ /*tex Output fonts definition. */
+ for (k = 1; k <= max_font_id(); k++) {
+ if (font_used(k) && (pdf_font_num(k) < 0)) {
+ i = -pdf_font_num(k);
+ for (j = font_bc(k); j <= font_ec(k); j++)
+ if (quick_char_exists(k, j) && pdf_char_marked(k, j))
+ pdf_mark_char(i, j);
+ if ((pdf_font_attr(i) == 0) && (pdf_font_attr(k) != 0)) {
+ set_pdf_font_attr(i, pdf_font_attr(k));
+ } else if ((pdf_font_attr(k) == 0) && (pdf_font_attr(i) != 0)) {
+ set_pdf_font_attr(k, pdf_font_attr(i));
+ } else if ((pdf_font_attr(i) != 0) && (pdf_font_attr(k) != 0) && (!str_eq_str(pdf_font_attr(i), pdf_font_attr(k)))) {
+ formatted_warning("pdf backend","fontattr of font %d and %d are conflicting, %k is used",i,k,i);
+ }
+ }
+ }
+ pdf->gen_tounicode = pdf_gen_tounicode;
+ pdf->omit_cidset = pdf_omit_cidset;
+ pdf->omit_charset = pdf_omit_charset;
+ k = pdf->head_tab[obj_type_font];
+ while (k != 0) {
+ int f = obj_info(pdf, k);
+ do_pdf_font(pdf, f);
+ k = obj_link(pdf, k);
+ }
+ write_fontstuff(pdf);
+ if (total_pages > 0) {
+ pdf->last_pages = output_pages_tree(pdf);
+ /*tex Output outlines. */
+ outlines = print_outlines(pdf);
+ /*tex
+ The name tree is very similiar to Pages tree so its construction
+ should be certain from Pages tree construction. For intermediate
+ node |obj_info| will be the first name and |obj_link| will be the
+ last name in \.{\\Limits} array. Note that |pdf_dest_names_ptr|
+ will be less than |obj_ptr|, so we test if |k <
+ pdf_dest_names_ptr| then |k| is index of leaf in |dest_names|;
+ else |k| will be index in |obj_tab| of some intermediate node.
+ */
+ names_tree = output_name_tree(pdf);
+ /*tex Output article threads. */
+ if (pdf->head_tab[obj_type_thread] != 0) {
+ threads = pdf_create_obj(pdf, obj_type_others, 0);
+ pdf_begin_obj(pdf, threads, OBJSTM_ALWAYS);
+ pdf_begin_array(pdf);
+ k = pdf->head_tab[obj_type_thread];
+ while (k != 0) {
+ pdf_add_ref(pdf, k);
+ k = obj_link(pdf, k);
+ }
+ pdf_end_array(pdf);
+ pdf_end_obj(pdf);
+ k = pdf->head_tab[obj_type_thread];
+ while (k != 0) {
+ out_thread(pdf, k);
+ k = obj_link(pdf, k);
+ }
+ } else {
+ threads = 0;
+ }
+ }
+ /*tex Output the |/Catalog| object. */
+ root = pdf_create_obj(pdf, obj_type_catalog, 0);
+ pdf_begin_obj(pdf, root, OBJSTM_ALWAYS);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_name(pdf, "Type", "Catalog");
+ if (total_pages > 0) {
+ pdf_dict_add_ref(pdf, "Pages", pdf->last_pages);
+ if (threads != 0) {
+ pdf_dict_add_ref(pdf, "Threads", threads);
+ }
+ if (outlines != 0) {
+ pdf_dict_add_ref(pdf, "Outlines", outlines);
+ }
+ if (names_tree != 0) {
+ pdf_dict_add_ref(pdf, "Names", names_tree);
+ }
+ if (pdf_catalog_toks != null) {
+ pdf_print_toks(pdf, pdf_catalog_toks);
+ delete_token_ref(pdf_catalog_toks);
+ pdf_catalog_toks = null;
+ }
+ }
+ if (pdf_catalog_openaction != 0) {
+ pdf_dict_add_ref(pdf, "OpenAction", pdf_catalog_openaction);
+ }
+ print_pdf_table_string(pdf, "catalog");
+ pdf_end_dict(pdf);
+ pdf_end_obj(pdf);
+ info = pdf_print_info(pdf, luatexversion, luatexrevision);
+ if (pdf->os_enable) {
+ pdf_buffer_select(pdf, OBJSTM_BUF);
+ pdf_os_write_objstream(pdf);
+ pdf_flush(pdf);
+ pdf_buffer_select(pdf, PDFOUT_BUF);
+ /*tex Output the cross-reference stream dictionary. */
+ xref_stm = pdf_create_obj(pdf, obj_type_others, 0);
+ pdf_begin_obj(pdf, xref_stm, OBJSTM_NEVER);
+ if ((obj_offset(pdf, pdf->obj_ptr) / 256) > 16777215)
+ xref_offset_width = 5;
+ else if (obj_offset(pdf, pdf->obj_ptr) > 16777215)
+ xref_offset_width = 4;
+ else if (obj_offset(pdf, pdf->obj_ptr) > 65535)
+ xref_offset_width = 3;
+ else
+ xref_offset_width = 2;
+ /*tex Build a linked list of free objects. */
+ build_free_object_list(pdf);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_name(pdf, "Type", "XRef");
+ pdf_add_name(pdf, "Index");
+ pdf_begin_array(pdf);
+ pdf_add_int(pdf, 0);
+ pdf_add_int(pdf, pdf->obj_ptr + 1);
+ pdf_end_array(pdf);
+ pdf_dict_add_int(pdf, "Size", pdf->obj_ptr + 1);
+ pdf_add_name(pdf, "W");
+ pdf_begin_array(pdf);
+ pdf_add_int(pdf, 1);
+ pdf_add_int(pdf, (int) xref_offset_width);
+ pdf_add_int(pdf, 1);
+ pdf_end_array(pdf);
+ pdf_dict_add_ref(pdf, "Root", root);
+ pdf_dict_add_ref(pdf, "Info", info);
+ if (pdf_trailer_toks != null) {
+ pdf_print_toks(pdf, pdf_trailer_toks);
+ delete_token_ref(pdf_trailer_toks);
+ pdf_trailer_toks = null;
+ }
+ print_pdf_table_string(pdf, "trailer");
+ print_ID(pdf);
+ pdf_dict_add_streaminfo(pdf);
+ pdf_end_dict(pdf);
+ pdf_begin_stream(pdf);
+ for (k = 0; k <= pdf->obj_ptr; k++) {
+ if (!is_obj_written(pdf, k)) {
+ /*tex A free object: */
+ pdf_out(pdf, 0);
+ pdf_out_bytes(pdf, obj_link(pdf, k), xref_offset_width);
+ pdf_out(pdf, 255);
+ } else if (obj_os_idx(pdf, k) == PDF_OS_MAX_OBJS) {
+ /*tex An object not in object stream: */
+ pdf_out(pdf, 1);
+ pdf_out_bytes(pdf, obj_offset(pdf, k), xref_offset_width);
+ pdf_out(pdf, 0);
+ } else {
+ /*tex An object in object stream: */
+ pdf_out(pdf, 2);
+ pdf_out_bytes(pdf, obj_offset(pdf, k), xref_offset_width);
+ pdf_out(pdf, obj_os_idx(pdf, k));
+ }
+ }
+ pdf_end_stream(pdf);
+ pdf_end_obj(pdf);
+ pdf_flush(pdf);
+ } else {
+ /*tex Output the |obj_tab| and build a linked list of free objects. */
+ build_free_object_list(pdf);
+ pdf_save_offset(pdf);
+ pdf_puts(pdf, "xref\n");
+ pdf_puts(pdf, "0 ");
+ pdf_print_int_ln(pdf, pdf->obj_ptr + 1);
+ pdf_print_fw_int(pdf, obj_link(pdf, 0));
+ pdf_puts(pdf, " 65535 f \n");
+ for (k = 1; k <= pdf->obj_ptr; k++) {
+ if (!is_obj_written(pdf, k)) {
+ pdf_print_fw_int(pdf, obj_link(pdf, k));
+ pdf_puts(pdf, " 00000 f \n");
+ } else {
+ pdf_print_fw_int(pdf, obj_offset(pdf, k));
+ pdf_puts(pdf, " 00000 n \n");
+ }
+ }
+ }
+ /*tex Output the trailer. */
+ if (!pdf->os_enable) {
+ pdf_puts(pdf, "trailer\n");
+ pdf_reset_space(pdf);
+ pdf_begin_dict(pdf);
+ pdf_dict_add_int(pdf, "Size", pdf->obj_ptr + 1);
+ pdf_dict_add_ref(pdf, "Root", root);
+ pdf_dict_add_ref(pdf, "Info", info);
+ if (pdf_trailer_toks != null) {
+ pdf_print_toks(pdf, pdf_trailer_toks);
+ delete_token_ref(pdf_trailer_toks);
+ pdf_trailer_toks = null;
+ }
+ print_ID(pdf);
+ pdf_end_dict(pdf);
+ pdf_out(pdf, '\n');
+ }
+ pdf_puts(pdf, "startxref\n");
+ pdf_reset_space(pdf);
+ if (pdf->os_enable)
+ pdf_add_longint(pdf, (longinteger) obj_offset(pdf, xref_stm));
+ else
+ pdf_add_longint(pdf, (longinteger) pdf->save_offset);
+ pdf_puts(pdf, "\n%%EOF\n");
+ pdf_flush(pdf);
+ if (callback_id == 0) {
+ tprint_nl("Output written on ");
+ tprint(pdf->file_name);
+ tprint(" (");
+ print_int(total_pages);
+ tprint(" page");
+ if (total_pages != 1)
+ print_char('s');
+ tprint(", ");
+ print_int(pdf_offset(pdf));
+ tprint(" bytes).");
+ print_ln();
+ } else if (callback_id > 0) {
+ run_callback(callback_id, "->");
+ }
+ libpdffinish(pdf);
+ close_file(pdf->file);
+ } else {
+ if (callback_id > 0) {
+ run_callback(callback_id, "->");
+ }
+ libpdffinish(pdf);
+ normal_warning("pdf backend","draftmode enabled, not changing output pdf");
+ }
+ }
+ if (callback_id == 0) {
+ if (log_opened_global) {
+ fprintf(log_file, "\nPDF statistics: %d PDF objects out of %d (max. %d)\n",
+ (int) pdf->obj_ptr, (int) pdf->obj_tab_size,
+ (int) sup_obj_tab_size);
+ if (pdf->os->ostm_ctr > 0) {
+ fprintf(log_file, " %d compressed objects within %d object stream%s\n",
+ (int) pdf->os->o_ctr, (int) pdf->os->ostm_ctr,
+ (pdf->os->ostm_ctr > 1 ? "s" : ""));
+ }
+ fprintf(log_file, " %d named destinations out of %d (max. %d)\n",
+ (int) pdf->dest_names_ptr, (int) pdf->dest_names_size,
+ (int) sup_dest_names_size);
+ fprintf(log_file, " %d words of extra memory for PDF output out of %d (max. %d)\n",
+ (int) pdf->mem_ptr, (int) pdf->mem_size,
+ (int) sup_pdf_mem_size);
+ }
+ }
+ }
+}
+
+void scan_pdfcatalog(PDF pdf)
+{
+ halfword p;
+ scan_toks(false, true);
+ pdf_catalog_toks = concat_tokens(pdf_catalog_toks, def_ref);
+ if (scan_keyword("openaction")) {
+ if (pdf_catalog_openaction != 0) {
+ normal_error("pdf backend", "duplicate of openaction");
+ } else {
+ check_o_mode(pdf, "catalog", 1 << OMODE_PDF, true);
+ p = scan_action(pdf);
+ pdf_catalog_openaction = pdf_create_obj(pdf, obj_type_others, 0);
+ pdf_begin_obj(pdf, pdf_catalog_openaction, OBJSTM_ALWAYS);
+ write_action(pdf, p);
+ pdf_end_obj(pdf);
+ delete_action_ref(p);
+ }
+ }
+}
+
+/*tex
+
+ This function converts double to pdffloat; very small and very large numbers
+ are {\em not} converted to scientific notation. Here n must be a number or
+ real conforming to the implementation limits of \PDF\ as specified in
+ appendix C.1 of the \PDF\ standard. The maximum value of ints is |+2^32|, the
+ maximum value of reals is |+2^15| and the smallest values of reals is
+ |1/(2^16)|.
+
+*/
+
+static pdffloat conv_double_to_pdffloat(double n)
+{
+ pdffloat a;
+ a.e = 6;
+ a.m = i64round(n * ten_pow[a.e]);
+ return a;
+}
+
+void pdf_add_real(PDF pdf, double d)
+{
+ pdf_check_space(pdf);
+ print_pdffloat(pdf, conv_double_to_pdffloat(d));
+ pdf_set_space(pdf);
+}
+
+void pdf_push_list(PDF pdf, scaledpos *saved_pos, int *saved_loc) {
+ /* nothing */
+}
+
+void pdf_pop_list(PDF pdf, scaledpos *saved_pos, int *saved_loc) {
+ /* nothing */
+}
+
+extern void pdf_set_reference_point(PDF pdf, posstructure *refpoint)
+{
+ refpoint->pos.h = pdf_h_origin;
+ refpoint->pos.v = pdf->page_size.v - pdf_v_origin;
+}