1
0
mirror of synced 2026-01-12 00:02:50 +00:00
brouhaha.tumble/semantics.c

824 lines
20 KiB
C

/*
* tumble: build a PDF file from image files
*
* Semantic routines for spec file parser
* Copyright 2001, 2002, 2003, 2017 Eric Smith <spacewar@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. Note that permission is
* not granted to redistribute this program under the terms of any
* other version of the General Public License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
*
* 2009-03-13 [JDB] Add support for blank pages, overlay images, color
* mapping, color-key masking, and push/pop of input
* contexts.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "semantics.h"
#ifdef CTL_LANG
#include "parser.tab.h"
#endif
#include "tumble.h"
typedef struct
{
bool has_page_size;
page_size_t page_size;
bool has_rotation;
int rotation;
bool has_crop;
crop_t crop;
bool has_transparency;
rgb_range_t transparency;
} input_modifiers_t;
typedef struct input_context_t
{
struct input_context_t *parent;
struct input_context_t *next;
int image_count; /* how many pages reference this context,
including those from subcontexts */
char *input_file;
bool is_blank;
input_modifiers_t modifiers;
} input_context_t;
typedef struct input_image_t
{
struct input_image_t *next;
input_context_t *input_context;
range_t range;
} input_image_t;
typedef struct output_context_t
{
struct output_context_t *parent;
struct output_context_t *next;
int page_count; /* how many pages reference this context,
including those from subcontexts */
char *output_file;
pdf_file_attributes_t file_attributes;
bookmark_t *first_bookmark;
bookmark_t *last_bookmark;
bool has_page_label;
page_label_t page_label;
bool has_colormap;
colormap_t colormap;
} output_context_t;
typedef struct output_page_t
{
struct output_page_t *next;
output_context_t *output_context;
range_t range;
bookmark_t *bookmark_list;
bool has_overlay;
overlay_t overlay;
} output_page_t;
#ifdef SEMANTIC_DEBUG
#define SDBG(x) printf x
#else
#define SDBG(x)
#endif
extern FILE *yyin; // forward declaration, will be defined by bison
int line; /* line number in spec file */
int bookmark_level;
input_context_t *first_input_context;
input_context_t *last_input_context;
input_image_t *first_input_image;
input_image_t *last_input_image;
output_context_t *first_output_context;
output_context_t *last_output_context;
output_page_t *first_output_page;
output_page_t *last_output_page;
void input_push_context (void)
{
input_context_t *new_input_context;
new_input_context = malloc (sizeof (input_context_t));
if (! new_input_context)
{
fprintf (stderr, "failed to malloc an input context\n");
return;
}
if (last_input_context)
{
memcpy (new_input_context, last_input_context, sizeof (input_context_t));
new_input_context->image_count = 0;
}
else
{
memset (new_input_context, 0, sizeof (input_context_t));
first_input_context = new_input_context;
}
new_input_context->parent = last_input_context;
last_input_context = new_input_context;
};
void input_pop_context (void)
{
if (! last_input_context)
{
fprintf (stderr, "failed to pop an input context\n");
return;
}
last_input_context = last_input_context->parent;
};
static void input_clone (void)
{
input_context_t *new_input_context;
if (! last_input_context->image_count)
return;
new_input_context = malloc (sizeof (input_context_t));
if (! new_input_context)
{
fprintf (stderr, "failed to malloc an input context\n");
return;
}
memcpy (new_input_context, last_input_context, sizeof (input_context_t));
new_input_context->image_count = 0;
last_input_context->next = new_input_context;
last_input_context = new_input_context;
}
void input_set_file (char *name)
{
input_clone ();
last_input_context->input_file = name;
last_input_context->is_blank = (name == NULL);
};
void input_set_rotation (int rotation)
{
last_input_context->modifiers.has_rotation = 1;
last_input_context->modifiers.rotation = rotation;
SDBG(("rotation %d\n", rotation));
}
void input_set_page_size (page_size_t size)
{
last_input_context->modifiers.has_page_size = 1;
last_input_context->modifiers.page_size = size;
SDBG(("page size %f, %f\n", size.width, size.height));
}
void input_set_transparency (rgb_range_t rgb_range)
{
last_input_context->modifiers.has_transparency = 1;
last_input_context->modifiers.transparency = rgb_range;
}
static void increment_input_image_count (int count)
{
input_context_t *context;
for (context = last_input_context; context; context = context->parent)
context->image_count += count;
}
void input_images (range_t range)
{
input_image_t *new_image;
int count = ((range.last - range.first) + 1);
#ifdef SEMANTIC_DEBUG
if (range.first == range.last)
SDBG(("image %d\n", range.first));
else
SDBG(("images %d..%d\n", range.first, range.last));
#endif /* SEMANTIC_DEBUG */
new_image = calloc (1, sizeof (input_image_t));
if (! new_image)
{
fprintf (stderr, "failed to malloc an input image struct\n");
return;
}
if (first_input_image)
{
last_input_image->next = new_image;
last_input_image = new_image;
}
else
{
first_input_image = last_input_image = new_image;
}
new_image->range = range;
new_image->input_context = last_input_context;
increment_input_image_count (count);
}
void output_push_context (void)
{
output_context_t *new_output_context;
new_output_context = malloc (sizeof (output_context_t));
if (! new_output_context)
{
fprintf (stderr, "failed to malloc an output context\n");
return;
}
if (last_output_context)
{
memcpy (new_output_context, last_output_context, sizeof (output_context_t));
new_output_context->page_count = 0;
new_output_context->first_bookmark = NULL;
new_output_context->last_bookmark = NULL;
}
else
{
memset (new_output_context, 0, sizeof (output_context_t));
first_output_context = new_output_context;
}
new_output_context->parent = last_output_context;
last_output_context = new_output_context;
};
void output_pop_context (void)
{
if (! last_output_context)
{
fprintf (stderr, "failed to pop an output context\n");
return;
}
last_output_context = last_output_context->parent;
};
static void output_clone (void)
{
output_context_t *new_output_context;
if (! last_output_context->page_count)
return;
new_output_context = malloc (sizeof (output_context_t));
if (! new_output_context)
{
fprintf (stderr, "failed to malloc an output context\n");
return;
}
memcpy (new_output_context, last_output_context, sizeof (output_context_t));
new_output_context->page_count = 0;
last_output_context->next = new_output_context;
last_output_context = new_output_context;
}
void output_set_file (char *name)
{
output_clone ();
last_output_context->output_file = name;
last_output_context->file_attributes.author = NULL;
last_output_context->file_attributes.creator = NULL;
last_output_context->file_attributes.title = NULL;
last_output_context->file_attributes.subject = NULL;
last_output_context->file_attributes.keywords = NULL;
};
void output_set_author (char *author)
{
last_output_context->file_attributes.author = author;
}
void output_set_creator (char *creator)
{
last_output_context->file_attributes.creator = creator;
}
void output_set_title (char *title)
{
last_output_context->file_attributes.title = title;
}
void output_set_subject (char *subject)
{
last_output_context->file_attributes.subject = subject;
}
void output_set_keywords (char *keywords)
{
last_output_context->file_attributes.keywords = keywords;
}
void output_set_bookmark (char *name)
{
bookmark_t *new_bookmark;
/* As the language is defined (parser.y), a bookmark can only appear
at the beginning of a context! */
if (last_output_context->page_count)
{
fprintf (stderr, "internal error, bookmark not at beginning of context\n");
exit (2);
}
new_bookmark = calloc (1, sizeof (bookmark_t));
if (! new_bookmark)
{
fprintf (stderr, "failed to calloc a bookmark\n");
return;
}
new_bookmark->level = bookmark_level;
new_bookmark->name = name;
if (last_output_context->first_bookmark)
last_output_context->last_bookmark->next = new_bookmark;
else
last_output_context->first_bookmark = new_bookmark;
last_output_context->last_bookmark = new_bookmark;
}
void output_set_page_label (page_label_t label)
{
output_clone ();
last_output_context->has_page_label = 1;
last_output_context->page_label = label;
}
static void increment_output_page_count (int count)
{
output_context_t *context;
for (context = last_output_context; context; context = context->parent)
context->page_count += count;
}
void output_pages (range_t range)
{
output_page_t *new_page;
output_context_t *context;
int count = ((range.last - range.first) + 1);
#ifdef SEMANTIC_DEBUG
if (range.first == range.last)
SDBG(("page %d\n", range.first));
else
SDBG(("pages %d..%d\n", range.first, range.last));
#endif /* SEMANTIC_DEBUG */
new_page = calloc (1, sizeof (output_page_t));
if (! new_page)
{
fprintf (stderr, "failed to malloc an output page struct\n");
return;
}
if (first_output_page)
{
last_output_page->next = new_page;
last_output_page = new_page;
}
else
{
first_output_page = last_output_page = new_page;
}
new_page->range = range;
new_page->output_context = last_output_context;
/* transfer bookmarks from context(s) to page */
for (context = last_output_context; context; context = context->parent)
if (context->first_bookmark)
{
context->last_bookmark->next = new_page->bookmark_list;
new_page->bookmark_list = context->first_bookmark;
context->first_bookmark = NULL;
context->last_bookmark = NULL;
}
increment_output_page_count (count);
}
void output_overlay (overlay_t overlay)
{
output_pages (last_output_page->range);
last_output_page->has_overlay = true;
last_output_page->overlay = overlay;
}
void output_set_colormap (rgb_t black_color, rgb_t white_color)
{
output_clone ();
last_output_context->has_colormap = 1;
last_output_context->colormap.black_map = black_color;
last_output_context->colormap.white_map = white_color;
}
void output_imagemask (rgb_t foreground_color)
{
output_pages (last_output_page->range);
last_output_page->has_overlay = true;
last_output_page->overlay.imagemask = true;
last_output_page->overlay.foreground = foreground_color;
}
void yyerror (const char *s)
{
fprintf (stderr, "%d: %s\n", line, s);
}
static char *get_input_filename (input_context_t *context)
{
for (; context; context = context->parent)
if ((context->input_file) || (context->is_blank))
return context->input_file;
fprintf (stderr, "no input file name found\n");
exit (2);
}
static bool get_input_rotation (input_context_t *context,
int *rotation)
{
for (; context; context = context->parent)
{
if (context->modifiers.has_rotation)
{
* rotation = context->modifiers.rotation;
return true;
}
}
return false; /* default */
}
static rgb_range_t *get_input_transparency (input_context_t *context)
{
for (; context; context = context->parent)
{
if (context->modifiers.has_transparency)
{
return & (context->modifiers.transparency);
}
}
return NULL; /* default */
}
static bool get_input_page_size (input_context_t *context,
page_size_t *page_size)
{
for (; context; context = context->parent)
{
if (context->modifiers.has_page_size)
{
* page_size = context->modifiers.page_size;
return true;
}
}
return false; /* default */
}
static char *get_output_filename (output_context_t *context)
{
for (; context; context = context->parent)
if (context->output_file)
return context->output_file;
fprintf (stderr, "no output file found\n");
exit (2);
}
static pdf_file_attributes_t *get_output_file_attributes (output_context_t *context)
{
for (; context; context = context->parent)
if (context->output_file)
return & context->file_attributes;
fprintf (stderr, "no output file found\n");
exit (2);
}
static page_label_t *get_output_page_label (output_context_t *context)
{
for (; context; context = context->parent)
if (context->has_page_label)
return & context->page_label;
return NULL; /* default */
}
static colormap_t *get_output_colormap (output_context_t *context)
{
for (; context; context = context->parent)
if (context->has_colormap)
return & context->colormap;
return NULL; /* default */
}
#ifdef SEMANTIC_DEBUG
void dump_input_tree (void)
{
input_image_t *image;
int i;
char *fn;
printf ("input images:\n");
for (image = first_input_image; image; image = image->next)
for (i = image->range.first; i <= image->range.last; i++)
{
bool has_rotation, has_page_size;
int rotation;
page_size_t page_size;
rgb_range_t *transparency;
has_rotation = get_input_rotation (image->input_context,
& rotation);
has_page_size = get_input_page_size (image->input_context,
& page_size);
transparency = get_input_transparency (image->input_context);
fn = get_input_filename (image->input_context);
if (fn)
printf ("file '%s' image %d", fn, i);
else
printf ("blank image %d", i);
if (has_rotation)
printf (" rotation %d", rotation);
if (transparency)
printf (" transparency %d..%d, %d..%d, %d..%d",
transparency->red.first, transparency->red.last,
transparency->green.first, transparency->green.last,
transparency->blue.first, transparency->blue.last);
if (has_page_size)
printf (" size %f, %f", page_size.width, page_size.height);
printf ("\n");
printf ("input context: %p\n", image->input_context);
}
}
void dump_output_tree (void)
{
int i;
output_page_t *page;
bookmark_t *bookmark;
printf ("output pages:\n");
for (page = first_output_page; page; page = page->next)
{
if (page->bookmark_list)
{
for (bookmark = page->bookmark_list; bookmark; bookmark = bookmark->next)
printf ("bookmark %d \"%s\"\n", bookmark->level, bookmark->name);
}
for (i = page->range.first; i <= page->range.last; i++)
{
page_label_t *label = get_output_page_label (page->output_context);
colormap_t *colormap = get_output_colormap (page->output_context);
printf ("file \"%s\" ", get_output_filename (page->output_context));
if (label)
{
printf ("label ");
if (label->prefix)
printf ("\"%s\" ", label->prefix);
if (label->style)
printf ("'%c' ", label->style);
}
if (colormap)
printf ("colormap (%d %d %d) (%d %d %d) ",
colormap->black_map.red, colormap->black_map.green, colormap->black_map.blue,
colormap->white_map.red, colormap->white_map.green, colormap->white_map.blue);
printf ("page %d\n", i);
printf ("output context %p\n", page->output_context);
}
}
}
#endif /* SEMANTIC_DEBUG */
static inline int range_count (range_t range)
{
return (range.last - range.first) + 1;
}
#ifdef CTL_LANG
bool parse_control_file (char *fn)
{
bool result = 0;
yyin = fopen (fn, "r");
if (! yyin)
{
fprintf (stderr, "can't open spec file '%s'\n", fn);
goto fail;
}
line = 1;
input_push_context (); /* create root input context */
input_push_context (); /* create inital input context */
output_push_context (); /* create root output context */
output_push_context (); /* create initial output context */
yyparse ();
if (first_input_context->image_count != first_output_context->page_count)
{
fprintf (stderr, "input image count %d != output page count %d\n",
first_input_context->image_count,
first_output_context->page_count);
goto fail;
}
fprintf (stderr, "%d images specified\n", first_input_context->image_count);
result = 1;
#ifdef SEMANTIC_DEBUG
dump_input_tree ();
dump_output_tree ();
#endif /* SEMANTIC_DEBUG */
fail:
if (yyin)
fclose (yyin);
return result;
}
bool omit_label (page_label_t *page_label)
{
static page_label_t *last_page_label;
bool unneeded;
unneeded = ( (last_page_label != NULL) &&
page_label->prefix &&
last_page_label->prefix &&
(strcmp (page_label->prefix, last_page_label->prefix) == 0) &&
(page_label->style == last_page_label->style) &&
(page_label->base == last_page_label->base + 1) );
last_page_label = page_label;
return unneeded;
}
bool process_controls (void)
{
input_image_t *image = NULL;
output_page_t *page = NULL;
int i = 0;
int p = 0;
int page_index = 0;
input_attributes_t input_attributes;
page_label_t *page_label;
output_attributes_t output_attributes;
for (;;)
{
if ((! image) || (i >= range_count (image->range)))
{
char *input_fn;
if (image)
image = image->next;
else
image = first_input_image;
if (! image)
return true; /* done */
i = 0;
input_fn = get_input_filename (image->input_context);
if (verbose)
{
if (input_fn)
fprintf (stderr, "opening input file '%s'\n", input_fn);
else
fprintf (stderr, "generating blank image\n");
}
if (! open_input_file (input_fn))
{
fprintf (stderr, "error opening input file '%s'\n", input_fn);
return false;
}
}
if ((! page) || (p >= range_count (page->range)))
{
char *output_fn;
if (page)
page = page->next;
else
page = first_output_page;
p = 0;
output_fn = get_output_filename (page->output_context);
if (verbose)
fprintf (stderr, "opening PDF file '%s'\n", output_fn);
if (! open_pdf_output_file (output_fn,
get_output_file_attributes (page->output_context)))
{
fprintf (stderr, "error opening PDF file '%s'\n", output_fn);
return false;
}
}
memset (& input_attributes, 0, sizeof (input_attributes));
input_attributes.rotation = 0;
input_attributes.has_rotation = get_input_rotation (image->input_context,
& input_attributes.rotation);
input_attributes.has_page_size = get_input_page_size (image->input_context,
& input_attributes.page_size);
input_attributes.transparency = get_input_transparency (image->input_context);
memset (& output_attributes, 0, sizeof (output_attributes));
output_attributes.colormap = get_output_colormap (page->output_context);
if (page->has_overlay)
output_attributes.overlay = & page->overlay;
if (verbose)
{
fprintf(stderr, "processing image %d", image->range.first + i);
if (page->has_overlay)
fprintf(stderr, " overlay");
fprintf(stderr, "\n");
}
if (p || page->has_overlay)
page_label = NULL;
else
{
page_label = get_output_page_label (page->output_context);
if (page_label)
{
page_label->page_index = page_index;
page_label->base = page->range.first;
page_label->count = range_count (page->range);
if (omit_label (page_label))
page_label = NULL;
}
}
if (! process_page (image->range.first + i,
input_attributes,
p ? NULL : page->bookmark_list,
page_label,
output_attributes))
{
fprintf (stderr, "error processing image %d\n", image->range.first + i);
return false;
}
i++;
p++;
if (! page->has_overlay)
page_index++;
}
}
#endif /* CTL_LANG */