Files
ElyOffice/crates/ely-pdf-engine/shim/ely_output.c
T
ZacharyZhang-NY b7fca0146d Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
2026-10-08 21:20:08 -04:00

190 lines
5.6 KiB
C

/* Page images, text, optimized copies, repaired copies (PDF-13, PDF-14). */
#include <string.h>
#include "ely_shim.h"
/* Page `number` as a PNG at `dpi`, white, as printed. */
int ely_export_image(fz_context *ctx, fz_document *doc, int number, float dpi, const char *path)
{
fz_pixmap *pixmap = NULL;
fz_var(pixmap);
fz_try(ctx)
{
if (dpi < 18 || dpi > 1200)
fz_throw(ctx, FZ_ERROR_ARGUMENT, "a resolution outside 18 to 1200 dpi");
ely_watch_begin(ctx);
pixmap = fz_new_pixmap_from_page_number(ctx, doc, number, fz_scale(dpi / 72, dpi / 72), fz_device_rgb(ctx), 0);
if (ely_watch_end(ctx))
fz_throw(ctx, FZ_ERROR_FORMAT, "page %d did not read whole: %s", number + 1, ely_open_warnings());
fz_save_pixmap_as_png(ctx, pixmap, path);
}
fz_always(ctx)
{
ely_watch_end(ctx);
fz_drop_pixmap(ctx, pixmap);
}
fz_catch(ctx)
return ely_fail(ctx);
return 0;
}
/* Every page's text, UTF-8, pages parted by form feeds. */
int ely_export_text(fz_context *ctx, fz_document *doc, const char *path)
{
fz_output *out = NULL;
fz_stext_page *text = NULL;
fz_buffer *buf = NULL;
fz_var(out);
fz_var(text);
fz_var(buf);
fz_try(ctx)
{
int i;
ely_watch_begin(ctx);
out = fz_new_output_with_path(ctx, path, 0);
for (i = 0; i < fz_count_pages(ctx, doc); i++)
{
if (i > 0)
fz_write_byte(ctx, out, '\f');
text = fz_new_stext_page_from_page_number(ctx, doc, i, NULL);
buf = fz_new_buffer_from_stext_page(ctx, text);
fz_write_buffer(ctx, out, buf);
fz_drop_buffer(ctx, buf);
buf = NULL;
fz_drop_stext_page(ctx, text);
text = NULL;
}
/* Partial text is no export. */
if (ely_watch_end(ctx))
fz_throw(ctx, FZ_ERROR_FORMAT, "the text did not read whole: %s", ely_open_warnings());
fz_close_output(ctx, out);
}
fz_always(ctx)
{
ely_watch_end(ctx);
fz_drop_buffer(ctx, buf);
fz_drop_stext_page(ctx, text);
fz_drop_output(ctx, out);
}
fz_catch(ctx)
return ely_fail(ctx);
return 0;
}
typedef struct
{
int from, to;
} downsample;
static void shrink_images(fz_context *ctx, fz_document *copy, void *arg)
{
downsample *d = arg;
pdf_image_rewriter_options opts = { 0 };
if (!d->to)
return;
opts.color_lossless_image_subsample_method = opts.color_lossy_image_subsample_method = FZ_SUBSAMPLE_BICUBIC;
opts.gray_lossless_image_subsample_method = opts.gray_lossy_image_subsample_method = FZ_SUBSAMPLE_BICUBIC;
opts.bitonal_image_subsample_method = FZ_SUBSAMPLE_BICUBIC;
opts.color_lossless_image_subsample_threshold = opts.color_lossy_image_subsample_threshold = d->from;
opts.gray_lossless_image_subsample_threshold = opts.gray_lossy_image_subsample_threshold = d->from;
opts.bitonal_image_subsample_threshold = d->from;
opts.color_lossless_image_subsample_to = opts.color_lossy_image_subsample_to = d->to;
opts.gray_lossless_image_subsample_to = opts.gray_lossy_image_subsample_to = d->to;
opts.bitonal_image_subsample_to = d->to;
/* Never, the default, drops the smaller picture. */
opts.color_lossless_image_recompress_method = opts.gray_lossless_image_recompress_method = FZ_RECOMPRESS_LOSSLESS;
opts.color_lossy_image_recompress_method = opts.gray_lossy_image_recompress_method = FZ_RECOMPRESS_SAME;
opts.bitonal_image_recompress_method = FZ_RECOMPRESS_SAME;
pdf_rewrite_images(ctx, ely_pdf_of(ctx, copy), &opts);
}
/* Writes `out` compressed, images above `from` dpi taken to `to`. */
int ely_optimize(fz_context *ctx, fz_document *doc, const char *raw, const char *out, const char *password, int compress,
int from, int to)
{
fz_try(ctx)
{
pdf_write_options opts = pdf_default_write_options;
downsample d = { from, to };
if (to && (to < 36 || from <= to))
fz_throw(ctx, FZ_ERROR_ARGUMENT, "a downsampling from %d to %d dpi", from, to);
opts.do_compress = opts.do_compress_images = opts.do_compress_fonts = compress;
ely_save_copy(ctx, ely_pdf_of(ctx, doc), raw, out, password, &opts, shrink_images, &d);
}
fz_catch(ctx)
return ely_fail(ctx);
return 0;
}
/* 1 when MuPDF rebuilt the file's structure to read it. */
int ely_repaired(fz_context *ctx, fz_document *doc)
{
int repaired = 0;
fz_try(ctx)
repaired = pdf_was_repaired(ctx, ely_pdf_of(ctx, doc));
fz_catch(ctx)
return ely_fail(ctx);
return repaired;
}
/* The repaired document written whole to `path`. */
int ely_write_repaired(fz_context *ctx, fz_document *doc, const char *path)
{
fz_try(ctx)
{
pdf_write_options opts = pdf_default_write_options;
opts.do_garbage = 1;
pdf_save_document(ctx, ely_pdf_of(ctx, doc), path, &opts);
}
fz_catch(ctx)
return ely_fail(ctx);
return 0;
}
/* Leave to print was checked; the printer gets no lock. */
int ely_print_copy(fz_context *ctx, fz_document *doc, const char *raw, const char *out, const char *password)
{
fz_try(ctx)
{
pdf_write_options opts = pdf_default_write_options;
opts.do_encrypt = PDF_ENCRYPT_NONE;
ely_save_copy(ctx, ely_pdf_of(ctx, doc), raw, out, password, &opts, NULL, NULL);
}
fz_catch(ctx)
return ely_fail(ctx);
return 0;
}
/* Decodes every picture; 1 if MuPDF warned on any. */
int ely_check_pictures(fz_context *ctx, fz_document *doc)
{
pdf_document *pdf = ely_pdf_of(ctx, doc);
int i;
ely_watch_begin(ctx);
for (i = 1; i < pdf_xref_len(ctx, pdf); i++)
{
pdf_obj *obj = NULL;
fz_buffer *bytes = NULL;
fz_var(obj);
fz_var(bytes);
fz_try(ctx)
{
obj = pdf_new_indirect(ctx, pdf, i, 0);
if (pdf_is_stream(ctx, obj) && pdf_name_eq(ctx, pdf_dict_get(ctx, obj, PDF_NAME(Subtype)), PDF_NAME(Image)))
bytes = pdf_load_stream_number(ctx, pdf, i);
}
fz_always(ctx)
{
fz_drop_buffer(ctx, bytes);
pdf_drop_obj(ctx, obj);
}
fz_catch(ctx)
{
fz_warn(ctx, "picture %d: %s", i, fz_caught_message(ctx));
fz_ignore_error(ctx);
}
}
return ely_watch_end(ctx);
}