ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
337 lines
9.5 KiB
C
337 lines
9.5 KiB
C
/* Images and form XObjects: list, move, scale, swap, add (PDF-04). */
|
|
|
|
#include <math.h>
|
|
|
|
#include "ely_shim.h"
|
|
|
|
/* One drawn object: its kind and where it lands. */
|
|
typedef struct
|
|
{
|
|
int form;
|
|
fz_matrix ctm;
|
|
fz_rect rect;
|
|
} drawn;
|
|
|
|
/* Counts top-level draws, following q, Q and cm. */
|
|
typedef struct
|
|
{
|
|
pdf_processor super;
|
|
fz_matrix ctm;
|
|
fz_matrix *stack;
|
|
int depth, room;
|
|
fz_matrix page;
|
|
drawn *found;
|
|
int n, cap;
|
|
} inventory;
|
|
|
|
static void inv_q(fz_context *ctx, pdf_processor *proc)
|
|
{
|
|
inventory *inv = (inventory *)proc;
|
|
if (inv->depth == inv->room)
|
|
{
|
|
inv->room = inv->room * 2 + 8;
|
|
inv->stack = fz_realloc_array(ctx, inv->stack, inv->room, fz_matrix);
|
|
}
|
|
inv->stack[inv->depth++] = inv->ctm;
|
|
}
|
|
|
|
static void inv_Q(fz_context *ctx, pdf_processor *proc)
|
|
{
|
|
inventory *inv = (inventory *)proc;
|
|
if (inv->depth > 0)
|
|
inv->ctm = inv->stack[--inv->depth];
|
|
}
|
|
|
|
static void inv_cm(fz_context *ctx, pdf_processor *proc, float a, float b, float c, float d, float e, float f)
|
|
{
|
|
inventory *inv = (inventory *)proc;
|
|
inv->ctm = fz_concat(fz_make_matrix(a, b, c, d, e, f), inv->ctm);
|
|
}
|
|
|
|
static void found(fz_context *ctx, inventory *inv, int form, fz_rect box)
|
|
{
|
|
if (inv->n == inv->cap)
|
|
{
|
|
inv->cap = inv->cap * 2 + 8;
|
|
inv->found = fz_realloc_array(ctx, inv->found, inv->cap, drawn);
|
|
}
|
|
inv->found[inv->n].form = form;
|
|
inv->found[inv->n].ctm = inv->ctm;
|
|
inv->found[inv->n++].rect = fz_transform_rect(box, fz_concat(inv->ctm, inv->page));
|
|
}
|
|
|
|
static void inv_image(fz_context *ctx, pdf_processor *proc, const char *name, fz_image *image)
|
|
{
|
|
found(ctx, (inventory *)proc, 0, fz_unit_rect);
|
|
}
|
|
|
|
static void inv_form(fz_context *ctx, pdf_processor *proc, const char *name, pdf_obj *form)
|
|
{
|
|
fz_rect box = fz_transform_rect(pdf_xobject_bbox(ctx, form), pdf_xobject_matrix(ctx, form));
|
|
found(ctx, (inventory *)proc, 1, box);
|
|
}
|
|
|
|
/* The page's top-level draws, in content order. */
|
|
static int take_inventory(fz_context *ctx, pdf_document *pdf, pdf_page *page, drawn **out)
|
|
{
|
|
inventory *inv = pdf_new_processor(ctx, sizeof *inv);
|
|
int n = 0;
|
|
fz_try(ctx)
|
|
{
|
|
inv->super.op_q = inv_q;
|
|
inv->super.op_Q = inv_Q;
|
|
inv->super.op_cm = inv_cm;
|
|
inv->super.op_Do_image = inv_image;
|
|
inv->super.op_Do_form = inv_form;
|
|
inv->ctm = fz_identity;
|
|
pdf_page_transform(ctx, page, NULL, &inv->page);
|
|
pdf_process_contents(ctx, &inv->super, pdf, pdf_page_resources(ctx, page), pdf_page_contents(ctx, page), NULL, NULL);
|
|
pdf_close_processor(ctx, &inv->super);
|
|
*out = inv->found;
|
|
n = inv->n;
|
|
inv->found = NULL;
|
|
}
|
|
fz_always(ctx)
|
|
{
|
|
fz_free(ctx, inv->stack);
|
|
fz_free(ctx, inv->found);
|
|
pdf_drop_processor(ctx, &inv->super);
|
|
}
|
|
fz_catch(ctx)
|
|
fz_rethrow(ctx);
|
|
return n;
|
|
}
|
|
|
|
/* JSON of the page's images and forms. */
|
|
int ely_objects(fz_context *ctx, fz_document *doc, int number, unsigned char **data, size_t *len)
|
|
{
|
|
pdf_page *page = NULL;
|
|
drawn *list = NULL;
|
|
fz_buffer *buf = NULL;
|
|
fz_var(page);
|
|
fz_var(list);
|
|
fz_var(buf);
|
|
fz_try(ctx)
|
|
{
|
|
pdf_document *pdf = ely_pdf_of(ctx, doc);
|
|
int i, n;
|
|
page = pdf_load_page(ctx, pdf, number);
|
|
n = take_inventory(ctx, pdf, page, &list);
|
|
buf = fz_new_buffer(ctx, 256);
|
|
fz_append_byte(ctx, buf, '[');
|
|
for (i = 0; i < n; i++)
|
|
fz_append_printf(ctx, buf, "%s{\"kind\":\"%s\",\"rect\":[%g,%g,%g,%g]}", i ? "," : "",
|
|
list[i].form ? "form" : "image", list[i].rect.x0, list[i].rect.y0, list[i].rect.x1, list[i].rect.y1);
|
|
fz_append_byte(ctx, buf, ']');
|
|
*len = fz_buffer_extract(ctx, buf, data);
|
|
}
|
|
fz_always(ctx)
|
|
{
|
|
fz_drop_buffer(ctx, buf);
|
|
fz_free(ctx, list);
|
|
fz_drop_page(ctx, (fz_page *)page);
|
|
}
|
|
fz_catch(ctx)
|
|
return ely_fail(ctx);
|
|
return 0;
|
|
}
|
|
|
|
/* What the rewrite does at draw `target`. */
|
|
typedef struct
|
|
{
|
|
int target, seen, drop;
|
|
fz_matrix x;
|
|
const char *rename;
|
|
pdf_processor *out;
|
|
void (*image)(fz_context *, pdf_processor *, const char *, fz_image *);
|
|
void (*form)(fz_context *, pdf_processor *, const char *, pdf_obj *);
|
|
} hook;
|
|
|
|
/* Wraps the chosen draw in `x`, renames or drops it. */
|
|
static int at_target(fz_context *ctx, hook *h, const char **name)
|
|
{
|
|
if (h->seen++ != h->target)
|
|
return 0;
|
|
if (!h->drop)
|
|
{
|
|
h->out->op_q(ctx, h->out);
|
|
h->out->op_cm(ctx, h->out, h->x.a, h->x.b, h->x.c, h->x.d, h->x.e, h->x.f);
|
|
if (h->rename)
|
|
*name = h->rename;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void hook_image(fz_context *ctx, pdf_processor *proc, const char *name, fz_image *image)
|
|
{
|
|
hook *h = fz_user_context(ctx);
|
|
int hit = at_target(ctx, h, &name);
|
|
if (!(hit && h->drop))
|
|
h->image(ctx, proc, name, image);
|
|
if (hit && !h->drop)
|
|
h->out->op_Q(ctx, h->out);
|
|
}
|
|
|
|
static void hook_form(fz_context *ctx, pdf_processor *proc, const char *name, pdf_obj *form)
|
|
{
|
|
hook *h = fz_user_context(ctx);
|
|
int hit = at_target(ctx, h, &name);
|
|
if (!(hit && h->drop))
|
|
h->form(ctx, proc, name, form);
|
|
if (hit && !h->drop)
|
|
h->out->op_Q(ctx, h->out);
|
|
}
|
|
|
|
/* Rewrites the page's content with `h` at its target draw. */
|
|
static void rewrite(fz_context *ctx, pdf_document *pdf, pdf_page *page, hook *h)
|
|
{
|
|
fz_buffer *buf = fz_new_buffer(ctx, 1024);
|
|
pdf_processor *out = NULL;
|
|
void *before = fz_user_context(ctx);
|
|
fz_var(out);
|
|
fz_try(ctx)
|
|
{
|
|
out = pdf_new_buffer_processor(ctx, buf, 0, 0);
|
|
h->out = out;
|
|
h->image = out->op_Do_image;
|
|
h->form = out->op_Do_form;
|
|
out->op_Do_image = hook_image;
|
|
out->op_Do_form = hook_form;
|
|
fz_set_user_context(ctx, h);
|
|
pdf_process_contents(ctx, out, pdf, pdf_page_resources(ctx, page), pdf_page_contents(ctx, page), NULL, NULL);
|
|
pdf_close_processor(ctx, out);
|
|
if (h->seen <= h->target)
|
|
fz_throw(ctx, FZ_ERROR_ARGUMENT, "no drawn object %d", h->target);
|
|
pdf_dict_put_drop(ctx, page->obj, PDF_NAME(Contents), pdf_add_stream(ctx, pdf, buf, NULL, 0));
|
|
}
|
|
fz_always(ctx)
|
|
{
|
|
fz_set_user_context(ctx, before);
|
|
pdf_drop_processor(ctx, out);
|
|
fz_drop_buffer(ctx, buf);
|
|
}
|
|
fz_catch(ctx)
|
|
fz_rethrow(ctx);
|
|
}
|
|
|
|
/* The draw's matrix in page space. */
|
|
static fz_matrix placed_at(fz_context *ctx, pdf_document *pdf, pdf_page *page, int index, fz_rect *rect)
|
|
{
|
|
drawn *list = NULL;
|
|
fz_matrix at = fz_identity, page_ctm;
|
|
fz_var(list);
|
|
fz_try(ctx)
|
|
{
|
|
int n = take_inventory(ctx, pdf, page, &list);
|
|
if (index < 0 || index >= n)
|
|
fz_throw(ctx, FZ_ERROR_ARGUMENT, "no drawn object %d", index);
|
|
pdf_page_transform(ctx, page, NULL, &page_ctm);
|
|
at = fz_concat(list[index].ctm, page_ctm);
|
|
*rect = list[index].rect;
|
|
}
|
|
fz_always(ctx)
|
|
fz_free(ctx, list);
|
|
fz_catch(ctx)
|
|
fz_rethrow(ctx);
|
|
return at;
|
|
}
|
|
|
|
/* Draw `index` moved by page-space `t`, swapped, or dropped. */
|
|
int ely_place(fz_context *ctx, fz_document *doc, int number, int index, const float t[6], int drop, const char *path)
|
|
{
|
|
pdf_document *pdf = NULL;
|
|
pdf_page *page = NULL;
|
|
fz_image *image = NULL;
|
|
pdf_obj *ref = NULL;
|
|
int begun = 0;
|
|
fz_var(pdf);
|
|
fz_var(page);
|
|
fz_var(image);
|
|
fz_var(ref);
|
|
fz_var(begun);
|
|
fz_try(ctx)
|
|
{
|
|
hook h = { index, 0, drop, fz_identity, NULL, NULL, NULL, NULL };
|
|
fz_matrix change = fz_make_matrix(t[0], t[1], t[2], t[3], t[4], t[5]), at;
|
|
fz_rect rect;
|
|
char name[32];
|
|
pdf = ely_pdf_of(ctx, doc);
|
|
page = pdf_load_page(ctx, pdf, number);
|
|
pdf_begin_operation(ctx, pdf, drop ? "Delete an object" : path ? "Replace an image" : "Move an object");
|
|
begun = 1;
|
|
at = placed_at(ctx, pdf, page, index, &rect);
|
|
if (path)
|
|
{
|
|
/* The new picture fits inside the old one's box. */
|
|
float box = (rect.x1 - rect.x0) / (rect.y1 - rect.y0), own;
|
|
float cx = (rect.x0 + rect.x1) / 2, cy = (rect.y0 + rect.y1) / 2;
|
|
image = fz_new_image_from_file(ctx, path);
|
|
own = (float)image->w / image->h;
|
|
change = fz_concat(fz_concat(fz_translate(-cx, -cy), fz_scale(fminf(1, own / box), fminf(1, box / own))), fz_translate(cx, cy));
|
|
ref = pdf_add_image(ctx, pdf, image);
|
|
h.rename = ely_add_resource(ctx, pdf, page->obj, PDF_NAME(XObject), "ElyIm", ref, name, sizeof name);
|
|
}
|
|
if (fz_matrix_expansion(change) < 0.001f)
|
|
fz_throw(ctx, FZ_ERROR_ARGUMENT, "a change that leaves nothing");
|
|
h.x = fz_concat(fz_concat(at, change), fz_invert_matrix(at));
|
|
rewrite(ctx, pdf, page, &h);
|
|
pdf_end_operation(ctx, pdf);
|
|
}
|
|
fz_always(ctx)
|
|
{
|
|
pdf_drop_obj(ctx, ref);
|
|
fz_drop_image(ctx, image);
|
|
fz_drop_page(ctx, (fz_page *)page);
|
|
}
|
|
fz_catch(ctx)
|
|
return ely_edit_failed(ctx, pdf, begun);
|
|
return 0;
|
|
}
|
|
|
|
/* An image file drawn into page-space `rect`, in one step. */
|
|
int ely_add_image(fz_context *ctx, fz_document *doc, int number, const char *path, const float rect[4])
|
|
{
|
|
pdf_document *pdf = NULL;
|
|
pdf_page *page = NULL;
|
|
fz_image *image = NULL;
|
|
pdf_obj *ref = NULL;
|
|
fz_buffer *buf = NULL;
|
|
int begun = 0;
|
|
fz_var(pdf);
|
|
fz_var(page);
|
|
fz_var(image);
|
|
fz_var(ref);
|
|
fz_var(buf);
|
|
fz_var(begun);
|
|
fz_try(ctx)
|
|
{
|
|
fz_matrix m;
|
|
char name[32];
|
|
pdf = ely_pdf_of(ctx, doc);
|
|
page = pdf_load_page(ctx, pdf, number);
|
|
if (rect[2] - rect[0] < 1 || rect[3] - rect[1] < 1)
|
|
fz_throw(ctx, FZ_ERROR_ARGUMENT, "an image box of nothing");
|
|
image = fz_new_image_from_file(ctx, path);
|
|
pdf_begin_operation(ctx, pdf, "Add an image");
|
|
begun = 1;
|
|
ref = pdf_add_image(ctx, pdf, image);
|
|
ely_add_resource(ctx, pdf, page->obj, PDF_NAME(XObject), "ElyIm", ref, name, sizeof name);
|
|
/* The unit square, bottom left first, onto the box. */
|
|
m = fz_concat(fz_make_matrix(rect[2] - rect[0], 0, 0, rect[1] - rect[3], rect[0], rect[3]), ely_to_user(ctx, page));
|
|
buf = fz_new_buffer(ctx, 128);
|
|
fz_append_printf(ctx, buf, "q %g %g %g %g %g %g cm /%s Do Q\n", m.a, m.b, m.c, m.d, m.e, m.f, name);
|
|
ely_append_content(ctx, pdf, page->obj, buf);
|
|
pdf_end_operation(ctx, pdf);
|
|
}
|
|
fz_always(ctx)
|
|
{
|
|
fz_drop_buffer(ctx, buf);
|
|
pdf_drop_obj(ctx, ref);
|
|
fz_drop_image(ctx, image);
|
|
fz_drop_page(ctx, (fz_page *)page);
|
|
}
|
|
fz_catch(ctx)
|
|
return ely_edit_failed(ctx, pdf, begun);
|
|
return 0;
|
|
}
|