/*
 * Information tool.
 * Print information about the input pdf.
 */

#include "fitz.h"
#include "mupdf.h"

pdf_xref *xref;
int pagecount;

void closexref(void);

void die(fz_error error)
{
	fz_catch(error, "aborting");
	closexref();
	exit(1);
}

void openxref(char *filename, char *password, int dieonbadpass, int loadpages);

enum
{
	DIMENSIONS = 0x01,
	FONTS = 0x02,
	IMAGES = 0x04,
	SHADINGS = 0x08,
	PATTERNS = 0x10,
	XOBJS = 0x20,
	ALL = DIMENSIONS | FONTS | IMAGES | SHADINGS | PATTERNS | XOBJS
};

struct info
{
	int page;
	fz_obj *pageref;
	fz_obj *pageobj;
	union {
		struct {
			fz_obj *obj;
		} info;
		struct {
			fz_obj *obj;
		} crypt;
		struct {
			fz_obj *obj;
			fz_rect *bbox;
		} dim;
		struct {
			fz_obj *obj;
			fz_obj *subtype;
			fz_obj *name;
		} font;
		struct {
			fz_obj *obj;
			fz_obj *width;
			fz_obj *height;
			fz_obj *bpc;
			fz_obj *filter;
			fz_obj *cs;
			fz_obj *altcs;
		} image;
		struct {
			fz_obj *obj;
			fz_obj *type;
		} shading;
		struct {
			fz_obj *obj;
			fz_obj *type;
			fz_obj *paint;
			fz_obj *tiling;
			fz_obj *shading;
		} pattern;
		struct {
			fz_obj *obj;
			fz_obj *groupsubtype;
			fz_obj *reference;
		} form;
	} u;
};

static struct info *dim = NULL;
static int dims = 0;
static struct info *font = NULL;
static int fonts = 0;
static struct info *image = NULL;
static int images = 0;
static struct info *shading = NULL;
static int shadings = 0;
static struct info *pattern = NULL;
static int patterns = 0;
static struct info *form = NULL;
static int forms = 0;
static struct info *psobj = NULL;
static int psobjs = 0;

void closexref(void)
{
	int i;
	if (xref)
	{
		pdf_free_xref(xref);
		xref = NULL;
	}

	if (dim)
	{
		for (i = 0; i < dims; i++)
			fz_free(dim[i].u.dim.bbox);
		fz_free(dim);
		dim = NULL;
		dims = 0;
	}

	if (font)
	{
		fz_free(font);
		font = NULL;
		fonts = 0;
	}

	if (image)
	{
		fz_free(image);
		image = NULL;
		images = 0;
	}

	if (shading)
	{
		fz_free(shading);
		shading = NULL;
		shadings = 0;
	}

	if (pattern)
	{
		fz_free(pattern);
		pattern = NULL;
		patterns = 0;
	}

	if (form)
	{
		fz_free(form);
		form = NULL;
		forms = 0;
	}

	if (psobj)
	{
		fz_free(psobj);
		psobj = NULL;
		psobjs = 0;
	}

	if (xref && xref->store)
	{
		pdf_free_store(xref->store);
		xref->store = NULL;
	}
}

static void
infousage(void)
{
	fprintf(stderr,
		"usage: pdfinfo [options] [file.pdf ... ]\n"
		"\t-d -\tpassword for decryption\n"
		"\t-f\tlist fonts\n"
		"\t-i\tlist images\n"
		"\t-m\tlist dimensions\n"
		"\t-p\tlist patterns\n"
		"\t-s\tlist shadings\n"
		"\t-x\tlist form and postscript xobjects\n");
	exit(1);
}

static void
showglobalinfo(void)
{
	fz_obj *obj;

	printf("\nPDF-%d.%d\n", xref->version / 10, xref->version % 10);

	obj = fz_dict_gets(xref->trailer, "Info");
	if (obj)
	{
		printf("Info object (%d %d R):\n", fz_to_num(obj), fz_to_gen(obj));
		fz_debug_obj(fz_resolve_indirect(obj));
	}

	obj = fz_dict_gets(xref->trailer, "Encrypt");
	if (obj)
	{
		printf("\nEncryption object (%d %d R):\n", fz_to_num(obj), fz_to_gen(obj));
		fz_debug_obj(fz_resolve_indirect(obj));
	}

	printf("\nPages: %d\n\n", pagecount);
}

static void
gatherdimensions(int page, fz_obj *pageref, fz_obj *pageobj)
{
	fz_rect bbox;
	fz_obj *obj;
	int j;

	obj = fz_dict_gets(pageobj, "MediaBox");
	if (!fz_is_array(obj))
		return;

	bbox = pdf_to_rect(obj);

	for (j = 0; j < dims; j++)
		if (!memcmp(dim[j].u.dim.bbox, &bbox, sizeof (fz_rect)))
			break;

	if (j < dims)
		return;

	dims++;

	dim = fz_realloc(dim, dims, sizeof(struct info));
	dim[dims - 1].page = page;
	dim[dims - 1].pageref = pageref;
	dim[dims - 1].pageobj = pageobj;
	dim[dims - 1].u.dim.bbox = fz_malloc(sizeof(fz_rect));
	memcpy(dim[dims - 1].u.dim.bbox, &bbox, sizeof (fz_rect));

	return;
}

static void
gatherfonts(int page, fz_obj *pageref, fz_obj *pageobj, fz_obj *dict)
{
	int i;

	for (i = 0; i < fz_dict_len(dict); i++)
	{
		fz_obj *fontdict = NULL;
		fz_obj *subtype = NULL;
		fz_obj *basefont = NULL;
		fz_obj *name = NULL;
		int k;

		fontdict = fz_dict_get_val(dict, i);
		if (!fz_is_dict(fontdict))
		{
			fz_warn("not a font dict (%d %d R)", fz_to_num(fontdict), fz_to_gen(fontdict));
			continue;
		}

		subtype = fz_dict_gets(fontdict, "Subtype");
		basefont = fz_dict_gets(fontdict, "BaseFont");
		if (!basefont || fz_is_null(basefont))
			name = fz_dict_gets(fontdict, "Name");

		for (k = 0; k < fonts; k++)
			if (!fz_objcmp(font[k].u.font.obj, fontdict))
				break;

		if (k < fonts)
			continue;

		fonts++;

		font = fz_realloc(font, fonts, sizeof(struct info));
		font[fonts - 1].page = page;
		font[fonts - 1].pageref = pageref;
		font[fonts - 1].pageobj = pageobj;
		font[fonts - 1].u.font.obj = fontdict;
		font[fonts - 1].u.font.subtype = subtype;
		font[fonts - 1].u.font.name = basefont ? basefont : name;
	}
}

static void
gatherimages(int page, fz_obj *pageref, fz_obj *pageobj, fz_obj *dict)
{
	int i;

	for (i = 0; i < fz_dict_len(dict); i++)
	{
		fz_obj *imagedict;
		fz_obj *type;
		fz_obj *width;
		fz_obj *height;
		fz_obj *bpc = NULL;
		fz_obj *filter = NULL;
		fz_obj *cs = NULL;
		fz_obj *altcs;
		int k;

		imagedict = fz_dict_get_val(dict, i);
		if (!fz_is_dict(imagedict))
		{
			fz_warn("not an image dict (%d %d R)", fz_to_num(imagedict), fz_to_gen(imagedict));
			continue;
		}

		type = fz_dict_gets(imagedict, "Subtype");
		if (strcmp(fz_to_name(type), "Image"))
			continue;

		filter = fz_dict_gets(imagedict, "Filter");

		altcs = NULL;
		cs = fz_dict_gets(imagedict, "ColorSpace");
		if (fz_is_array(cs))
		{
			fz_obj *cses = cs;

			cs = fz_array_get(cses, 0);
			if (fz_is_name(cs) && (!strcmp(fz_to_name(cs), "DeviceN") || !strcmp(fz_to_name(cs), "Separation")))
			{
				altcs = fz_array_get(cses, 2);
				if (fz_is_array(altcs))
					altcs = fz_array_get(altcs, 0);
			}
		}

		width = fz_dict_gets(imagedict, "Width");
		height = fz_dict_gets(imagedict, "Height");
		bpc = fz_dict_gets(imagedict, "BitsPerComponent");

		for (k = 0; k < images; k++)
			if (!fz_objcmp(image[k].u.image.obj, imagedict))
				break;

		if (k < images)
			continue;

		images++;

		image = fz_realloc(image, images, sizeof(struct info));
		image[images - 1].page = page;
		image[images - 1].pageref = pageref;
		image[images - 1].pageobj = pageobj;
		image[images - 1].u.image.obj = imagedict;
		image[images - 1].u.image.width = width;
		image[images - 1].u.image.height = height;
		image[images - 1].u.image.bpc = bpc;
		image[images - 1].u.image.filter = filter;
		image[images - 1].u.image.cs = cs;
		image[images - 1].u.image.altcs = altcs;
	}
}

static void
gatherforms(int page, fz_obj *pageref, fz_obj *pageobj, fz_obj *dict)
{
	int i;

	for (i = 0; i < fz_dict_len(dict); i++)
	{
		fz_obj *xobjdict;
		fz_obj *type;
		fz_obj *subtype;
		fz_obj *group;
		fz_obj *groupsubtype;
		fz_obj *reference;
		int k;

		xobjdict = fz_dict_get_val(dict, i);
		if (!fz_is_dict(xobjdict))
		{
			fz_warn("not a xobject dict (%d %d R)", fz_to_num(xobjdict), fz_to_gen(xobjdict));
			continue;
		}

		type = fz_dict_gets(xobjdict, "Subtype");
		if (strcmp(fz_to_name(type), "Form"))
			continue;

		subtype = fz_dict_gets(xobjdict, "Subtype2");
		if (!strcmp(fz_to_name(subtype), "PS"))
			continue;

		group = fz_dict_gets(xobjdict, "Group");
		groupsubtype = fz_dict_gets(group, "S");
		reference = fz_dict_gets(xobjdict, "Ref");

		for (k = 0; k < forms; k++)
			if (!fz_objcmp(form[k].u.form.obj, xobjdict))
				break;

		if (k < forms)
			continue;

		forms++;

		form = fz_realloc(form, forms, sizeof(struct info));
		form[forms - 1].page = page;
		form[forms - 1].pageref = pageref;
		form[forms - 1].pageobj = pageobj;
		form[forms - 1].u.form.obj = xobjdict;
		form[forms - 1].u.form.groupsubtype = groupsubtype;
		form[forms - 1].u.form.reference = reference;
	}
}

static void
gatherpsobjs(int page, fz_obj *pageref, fz_obj *pageobj, fz_obj *dict)
{
	int i;

	for (i = 0; i < fz_dict_len(dict); i++)
	{
		fz_obj *xobjdict;
		fz_obj *type;
		fz_obj *subtype;
		int k;

		xobjdict = fz_dict_get_val(dict, i);
		if (!fz_is_dict(xobjdict))
		{
			fz_warn("not a xobject dict (%d %d R)", fz_to_num(xobjdict), fz_to_gen(xobjdict));
			continue;
		}

		type = fz_dict_gets(xobjdict, "Subtype");
		subtype = fz_dict_gets(xobjdict, "Subtype2");
		if (strcmp(fz_to_name(type), "PS") &&
			(strcmp(fz_to_name(type), "Form") || strcmp(fz_to_name(subtype), "PS")))
			continue;

		for (k = 0; k < psobjs; k++)
			if (!fz_objcmp(psobj[k].u.form.obj, xobjdict))
				break;

		if (k < psobjs)
			continue;

		psobjs++;

		psobj = fz_realloc(psobj, psobjs, sizeof(struct info));
		psobj[psobjs - 1].page = page;
		psobj[psobjs - 1].pageref = pageref;
		psobj[psobjs - 1].pageobj = pageobj;
		psobj[psobjs - 1].u.form.obj = xobjdict;
	}
}

static void
gathershadings(int page, fz_obj *pageref, fz_obj *pageobj, fz_obj *dict)
{
	int i;

	for (i = 0; i < fz_dict_len(dict); i++)
	{
		fz_obj *shade;
		fz_obj *type;
		int k;

		shade = fz_dict_get_val(dict, i);
		if (!fz_is_dict(shade))
		{
			fz_warn("not a shading dict (%d %d R)", fz_to_num(shade), fz_to_gen(shade));
			continue;
		}

		type = fz_dict_gets(shade, "ShadingType");
		if (!fz_is_int(type) || fz_to_int(type) < 1 || fz_to_int(type) > 7)
		{
			fz_warn("not a shading type (%d %d R)", fz_to_num(shade), fz_to_gen(shade));
			type = NULL;
		}

		for (k = 0; k < shadings; k++)
			if (!fz_objcmp(shading[k].u.shading.obj, shade))
				break;

		if (k < shadings)
			continue;

		shadings++;

		shading = fz_realloc(shading, shadings, sizeof(struct info));
		shading[shadings - 1].page = page;
		shading[shadings - 1].pageref = pageref;
		shading[shadings - 1].pageobj = pageobj;
		shading[shadings - 1].u.shading.obj = shade;
		shading[shadings - 1].u.shading.type = type;
	}
}

static void
gatherpatterns(int page, fz_obj *pageref, fz_obj *pageobj, fz_obj *dict)
{
	int i;

	for (i = 0; i < fz_dict_len(dict); i++)
	{
		fz_obj *patterndict;
		fz_obj *type;
		fz_obj *paint = NULL;
		fz_obj *tiling = NULL;
		fz_obj *shading = NULL;
		int k;

		patterndict = fz_dict_get_val(dict, i);
		if (!fz_is_dict(patterndict))
		{
			fz_warn("not a pattern dict (%d %d R)", fz_to_num(patterndict), fz_to_gen(patterndict));
			continue;
		}

		type = fz_dict_gets(patterndict, "PatternType");
		if (!fz_is_int(type) || fz_to_int(type) < 1 || fz_to_int(type) > 2)
		{
			fz_warn("not a pattern type (%d %d R)", fz_to_num(patterndict), fz_to_gen(patterndict));
			type = NULL;
		}

		if (fz_to_int(type) == 1)
		{
			paint = fz_dict_gets(patterndict, "PaintType");
			if (!fz_is_int(paint) || fz_to_int(paint) < 1 || fz_to_int(paint) > 2)
			{
				fz_warn("not a pattern paint type (%d %d R)", fz_to_num(patterndict), fz_to_gen(patterndict));
				paint = NULL;
			}

			tiling = fz_dict_gets(patterndict, "TilingType");
			if (!fz_is_int(tiling) || fz_to_int(tiling) < 1 || fz_to_int(tiling) > 3)
			{
				fz_warn("not a pattern tiling type (%d %d R)", fz_to_num(patterndict), fz_to_gen(patterndict));
				tiling = NULL;
			}
		}
		else
		{
			shading = fz_dict_gets(patterndict, "Shading");
		}

		for (k = 0; k < patterns; k++)
			if (!fz_objcmp(pattern[k].u.pattern.obj, patterndict))
				break;

		if (k < patterns)
			continue;

		patterns++;

		pattern = fz_realloc(pattern, patterns, sizeof(struct info));
		pattern[patterns - 1].page = page;
		pattern[patterns - 1].pageref = pageref;
		pattern[patterns - 1].pageobj = pageobj;
		pattern[patterns - 1].u.pattern.obj = patterndict;
		pattern[patterns - 1].u.pattern.type = type;
		pattern[patterns - 1].u.pattern.paint = paint;
		pattern[patterns - 1].u.pattern.tiling = tiling;
		pattern[patterns - 1].u.pattern.shading = shading;
	}
}

static void
gatherresourceinfo(int page, fz_obj *rsrc)
{
	fz_obj *pageobj;
	fz_obj *pageref;
	fz_obj *font;
	fz_obj *xobj;
	fz_obj *shade;
	fz_obj *pattern;
	fz_obj *subrsrc;
	int i;

	pageobj = xref->page_objs[page-1];
	pageref = xref->page_refs[page-1];

	if (!pageobj)
		die(fz_throw("cannot retrieve info from page %d", page));

	font = fz_dict_gets(rsrc, "Font");
	if (font)
	{
		gatherfonts(page, pageref, pageobj, font);

		for (i = 0; i < fz_dict_len(font); i++)
		{
			fz_obj *obj = fz_dict_get_val(font, i);

			subrsrc = fz_dict_gets(obj, "Resources");
			if (subrsrc && fz_objcmp(rsrc, subrsrc))
				gatherresourceinfo(page, subrsrc);
		}
	}

	xobj = fz_dict_gets(rsrc, "XObject");
	if (xobj)
	{
		gatherimages(page, pageref, pageobj, xobj);
		gatherforms(page, pageref, pageobj, xobj);
		gatherpsobjs(page, pageref, pageobj, xobj);

		for (i = 0; i < fz_dict_len(xobj); i++)
		{
			fz_obj *obj = fz_dict_get_val(xobj, i);
			subrsrc = fz_dict_gets(obj, "Resources");
			if (subrsrc && fz_objcmp(rsrc, subrsrc))
				gatherresourceinfo(page, subrsrc);
		}
	}

	shade = fz_dict_gets(rsrc, "Shading");
	if (shade)
		gathershadings(page, pageref, pageobj, shade);

	pattern = fz_dict_gets(rsrc, "Pattern");
	if (pattern)
	{
		gatherpatterns(page, pageref, pageobj, pattern);

		for (i = 0; i < fz_dict_len(pattern); i++)
		{
			fz_obj *obj = fz_dict_get_val(pattern, i);
			subrsrc = fz_dict_gets(obj, "Resources");
			if (subrsrc && fz_objcmp(rsrc, subrsrc))
				gatherresourceinfo(page, subrsrc);
		}
	}
}

static void
gatherpageinfo(int page)
{
	fz_obj *pageobj;
	fz_obj *pageref;
	fz_obj *rsrc;

	pageobj = xref->page_objs[page-1];
	pageref = xref->page_refs[page-1];

	if (!pageobj)
		die(fz_throw("cannot retrieve info from page %d", page));

	gatherdimensions(page, pageref, pageobj);

	rsrc = fz_dict_gets(pageobj, "Resources");
	gatherresourceinfo(page, rsrc);
}

static void
printinfo(char *filename, int show, int page)
{
	int i;
	int j;

#define PAGE_FMT "\t% 5d (% 7d %1d R): "

	if (show & DIMENSIONS && dims > 0)
	{
		printf("Mediaboxes (%d):\n", dims);
		for (i = 0; i < dims; i++)
		{
			printf(PAGE_FMT "[ %g %g %g %g ]\n",
				dim[i].page,
				fz_to_num(dim[i].pageref), fz_to_gen(dim[i].pageref),
				dim[i].u.dim.bbox->x0,
				dim[i].u.dim.bbox->y0,
				dim[i].u.dim.bbox->x1,
				dim[i].u.dim.bbox->y1);
		}
		printf("\n");
	}

	if (show & FONTS && fonts > 0)
	{
		printf("Fonts (%d):\n", fonts);
		for (i = 0; i < fonts; i++)
		{
			printf(PAGE_FMT "%s '%s' (%d %d R)\n",
				font[i].page,
				fz_to_num(font[i].pageref), fz_to_gen(font[i].pageref),
				fz_to_name(font[i].u.font.subtype),
				fz_to_name(font[i].u.font.name),
				fz_to_num(font[i].u.font.obj), fz_to_gen(font[i].u.font.obj));
		}
		printf("\n");
	}

	if (show & IMAGES && images > 0)
	{
		printf("Images (%d):\n", images);
		for (i = 0; i < images; i++)
		{
			char *cs = NULL;
			char *altcs = NULL;

			printf(PAGE_FMT "[ ",
				image[i].page,
				fz_to_num(image[i].pageref), fz_to_gen(image[i].pageref));

			if (fz_is_array(image[i].u.image.filter))
				for (j = 0; j < fz_array_len(image[i].u.image.filter); j++)
				{
					fz_obj *obj = fz_array_get(image[i].u.image.filter, j);
					char *filter = fz_strdup(fz_to_name(obj));

					if (strstr(filter, "Decode"))
						*(strstr(filter, "Decode")) = '\0';

					printf("%s%s",
							filter,
							j == fz_array_len(image[i].u.image.filter) - 1 ? "" : " ");
					fz_free(filter);
				}
			else if (image[i].u.image.filter)
			{
				fz_obj *obj = image[i].u.image.filter;
				char *filter = fz_strdup(fz_to_name(obj));

				if (strstr(filter, "Decode"))
					*(strstr(filter, "Decode")) = '\0';

				printf("%s", filter);
				fz_free(filter);
			}
			else
				printf("Raw");

			if (image[i].u.image.cs)
			{
				cs = fz_strdup(fz_to_name(image[i].u.image.cs));

				if (!strncmp(cs, "Device", 6))
				{
					int len = strlen(cs + 6);
					memmove(cs + 3, cs + 6, len + 1);
					cs[3 + len + 1] = '\0';
				}
				if (strstr(cs, "ICC"))
					fz_strlcpy(cs, "ICC", 4);
				if (strstr(cs, "Indexed"))
					fz_strlcpy(cs, "Idx", 4);
				if (strstr(cs, "Pattern"))
					fz_strlcpy(cs, "Pat", 4);
				if (strstr(cs, "Separation"))
					fz_strlcpy(cs, "Sep", 4);
			}
			if (image[i].u.image.altcs)
			{
				altcs = fz_strdup(fz_to_name(image[i].u.image.altcs));

				if (!strncmp(altcs, "Device", 6))
				{
					int len = strlen(altcs + 6);
					memmove(altcs + 3, altcs + 6, len + 1);
					altcs[3 + len + 1] = '\0';
				}
				if (strstr(altcs, "ICC"))
					fz_strlcpy(altcs, "ICC", 4);
				if (strstr(altcs, "Indexed"))
					fz_strlcpy(altcs, "Idx", 4);
				if (strstr(altcs, "Pattern"))
					fz_strlcpy(altcs, "Pat", 4);
				if (strstr(altcs, "Separation"))
					fz_strlcpy(altcs, "Sep", 4);
			}

			printf(" ] %dx%d %dbpc %s%s%s (%d %d R)\n",
				fz_to_int(image[i].u.image.width),
				fz_to_int(image[i].u.image.height),
				image[i].u.image.bpc ? fz_to_int(image[i].u.image.bpc) : 1,
				image[i].u.image.cs ? cs : "ImageMask",
				image[i].u.image.altcs ? " " : "",
				image[i].u.image.altcs ? altcs : "",
				fz_to_num(image[i].u.image.obj), fz_to_gen(image[i].u.image.obj));

			fz_free(cs);
			fz_free(altcs);
		}
		printf("\n");
	}

	if (show & SHADINGS && shadings > 0)
	{
		printf("Shading patterns (%d):\n", shadings);
		for (i = 0; i < shadings; i++)
		{
			char *shadingtype[] =
			{
				"",
				"Function",
				"Axial",
				"Radial",
				"Triangle mesh",
				"Lattice",
				"Coons patch",
				"Tensor patch",
			};

			printf(PAGE_FMT "%s (%d %d R)\n",
				shading[i].page,
				fz_to_num(shading[i].pageref), fz_to_gen(shading[i].pageref),
				shadingtype[fz_to_int(shading[i].u.shading.type)],
				fz_to_num(shading[i].u.shading.obj), fz_to_gen(shading[i].u.shading.obj));
		}
		printf("\n");
	}

	if (show & PATTERNS && patterns > 0)
	{
		printf("Patterns (%d):\n", patterns);
		for (i = 0; i < patterns; i++)
		{
			if (fz_to_int(pattern[i].u.pattern.type) == 1)
			{
				char *painttype[] =
				{
					"",
					"Colored",
					"Uncolored",
				};
				char *tilingtype[] =
				{
					"",
					"Constant",
					"No distortion",
					"Constant/fast tiling",
				};

				printf(PAGE_FMT "Tiling %s %s (%d %d R)\n",
						pattern[i].page,
						fz_to_num(pattern[i].pageref), fz_to_gen(pattern[i].pageref),
						painttype[fz_to_int(pattern[i].u.pattern.paint)],
						tilingtype[fz_to_int(pattern[i].u.pattern.tiling)],
						fz_to_num(pattern[i].u.pattern.obj), fz_to_gen(pattern[i].u.pattern.obj));
			}
			else
			{
				printf(PAGE_FMT "Shading %d %d R (%d %d R)\n",
						pattern[i].page,
						fz_to_num(pattern[i].pageref), fz_to_gen(pattern[i].pageref),
						fz_to_num(pattern[i].u.pattern.shading), fz_to_gen(pattern[i].u.pattern.shading),
						fz_to_num(pattern[i].u.pattern.obj), fz_to_gen(pattern[i].u.pattern.obj));
			}
		}
		printf("\n");
	}

	if (show & XOBJS && forms > 0)
	{
		printf("Form xobjects (%d):\n", forms);
		for (i = 0; i < forms; i++)
		{
			printf(PAGE_FMT "Form%s%s%s%s (%d %d R)\n",
				form[i].page,
				fz_to_num(form[i].pageref), fz_to_gen(form[i].pageref),
				form[i].u.form.groupsubtype ? " " : "",
				form[i].u.form.groupsubtype ? fz_to_name(form[i].u.form.groupsubtype) : "",
				form[i].u.form.groupsubtype ? " Group" : "",
				form[i].u.form.reference ? " Reference" : "",
				fz_to_num(form[i].u.form.obj), fz_to_gen(form[i].u.form.obj));
		}
		printf("\n");
	}

	if (show & XOBJS && psobjs > 0)
	{
		printf("Postscript xobjects (%d):\n", psobjs);
		for (i = 0; i < psobjs; i++)
		{
			printf(PAGE_FMT "(%d %d R)\n",
				psobj[i].page,
				fz_to_num(psobj[i].pageref), fz_to_gen(psobj[i].pageref),
				fz_to_num(psobj[i].u.form.obj), fz_to_gen(psobj[i].u.form.obj));
		}
		printf("\n");
	}
}

static void
showinfo(char *filename, int show, char *pagelist)
{
	int page, spage, epage;
	char *spec, *dash;
	int allpages;

	if (!xref)
		infousage();

	allpages = !strcmp(pagelist, "1-");

	spec = fz_strsep(&pagelist, ",");
	while (spec)
	{
		dash = strchr(spec, '-');

		if (dash == spec)
			spage = epage = pagecount;
		else
			spage = epage = atoi(spec);

		if (dash)
		{
			if (strlen(dash) > 1)
				epage = atoi(dash + 1);
			else
				epage = pagecount;
		}

		if (spage > epage)
			page = spage, spage = epage, epage = page;

		if (spage < 1)
			spage = 1;
		if (epage > pagecount)
			epage = pagecount;
		if (spage > pagecount)
			spage = pagecount;

		if (allpages)
			printf("Retrieving info from pages %d-%d...\n", spage, epage);
		if (spage >= 1)
		{
			for (page = spage; page <= epage; page++)
			{
				gatherpageinfo(page);
				if (!allpages)
				{
					printf("Page %d:\n", page);
					printinfo(filename, show, page);
					printf("\n");
				}
			}
		}

		spec = fz_strsep(&pagelist, ",");
	}

	if (allpages)
		printinfo(filename, show, -1);
}

int main(int argc, char **argv)
{
	enum { NO_FILE_OPENED, NO_INFO_GATHERED, INFO_SHOWN } state;
	fz_error error;
	char *filename = "";
	char *password = "";
	int show = ALL;
	int c;

	while ((c = fz_getopt(argc, argv, "mfispxd:")) != -1)
	{
		switch (c)
		{
		case 'm': if (show == ALL) show = DIMENSIONS; else show |= DIMENSIONS; break;
		case 'f': if (show == ALL) show = FONTS; else show |= FONTS; break;
		case 'i': if (show == ALL) show = IMAGES; else show |= IMAGES; break;
		case 's': if (show == ALL) show = SHADINGS; else show |= SHADINGS; break;
		case 'p': if (show == ALL) show = PATTERNS; else show |= PATTERNS; break;
		case 'x': if (show == ALL) show = XOBJS; else show |= XOBJS; break;
		case 'd': password = fz_optarg; break;
		default:
			infousage();
			break;
		}
	}

	if (fz_optind == argc)
		infousage();

	state = NO_FILE_OPENED;
	while (fz_optind < argc)
	{
		if (strstr(argv[fz_optind], ".pdf") || strstr(argv[fz_optind], ".PDF"))
		{
			if (state == NO_INFO_GATHERED)
			{
				showinfo(filename, show, "1-");
				closexref();
			}

			closexref();

			filename = argv[fz_optind];
			printf("%s:\n", filename);
			error = pdf_open_xref(&xref, filename, password);
			if (error)
				die(fz_rethrow(error, "cannot open input file '%s'", filename));

			error = pdf_load_page_tree(xref);
			if (error)
				die(fz_rethrow(error, "cannot load page tree: %s", filename));
			pagecount = pdf_count_pages(xref);

			showglobalinfo();
			state = NO_INFO_GATHERED;
		}
		else
		{
			showinfo(filename, show, argv[fz_optind]);
			state = INFO_SHOWN;
		}

		fz_optind++;
	}

	if (state == NO_INFO_GATHERED)
		showinfo(filename, show, "1-");

	closexref();

	return 0;
}