4f7ee97ec9
git-svn-id: svn://kolibrios.org@4680 a494cfbc-eb01-0410-851d-a64ba20cac60
502 lines
11 KiB
C
502 lines
11 KiB
C
#include "fitz.h"
|
|
#include "muxps.h"
|
|
|
|
#include <zlib.h>
|
|
|
|
xps_part *
|
|
xps_new_part(xps_context *ctx, char *name, int size)
|
|
{
|
|
xps_part *part;
|
|
|
|
part = fz_malloc(sizeof(xps_part));
|
|
part->name = fz_strdup(name);
|
|
part->size = size;
|
|
part->data = fz_malloc(size + 1);
|
|
part->data[size] = 0; /* null-terminate for xml parser */
|
|
|
|
return part;
|
|
}
|
|
|
|
void
|
|
xps_free_part(xps_context *ctx, xps_part *part)
|
|
{
|
|
fz_free(part->name);
|
|
fz_free(part->data);
|
|
fz_free(part);
|
|
}
|
|
|
|
static inline int getshort(fz_stream *file)
|
|
{
|
|
int a = fz_read_byte(file);
|
|
int b = fz_read_byte(file);
|
|
return a | b << 8;
|
|
}
|
|
|
|
static inline int getlong(fz_stream *file)
|
|
{
|
|
int a = fz_read_byte(file);
|
|
int b = fz_read_byte(file);
|
|
int c = fz_read_byte(file);
|
|
int d = fz_read_byte(file);
|
|
return a | b << 8 | c << 16 | d << 24;
|
|
}
|
|
|
|
static void *
|
|
xps_zip_alloc_items(xps_context *ctx, int items, int size)
|
|
{
|
|
return fz_calloc(items, size);
|
|
}
|
|
|
|
static void
|
|
xps_zip_free(xps_context *ctx, void *ptr)
|
|
{
|
|
fz_free(ptr);
|
|
}
|
|
|
|
static int
|
|
xps_compare_entries(const void *a0, const void *b0)
|
|
{
|
|
xps_entry *a = (xps_entry*) a0;
|
|
xps_entry *b = (xps_entry*) b0;
|
|
return xps_strcasecmp(a->name, b->name);
|
|
}
|
|
|
|
static xps_entry *
|
|
xps_find_zip_entry(xps_context *ctx, char *name)
|
|
{
|
|
int l = 0;
|
|
int r = ctx->zip_count - 1;
|
|
while (l <= r)
|
|
{
|
|
int m = (l + r) >> 1;
|
|
int c = xps_strcasecmp(name, ctx->zip_table[m].name);
|
|
if (c < 0)
|
|
r = m - 1;
|
|
else if (c > 0)
|
|
l = m + 1;
|
|
else
|
|
return &ctx->zip_table[m];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
xps_read_zip_entry(xps_context *ctx, xps_entry *ent, unsigned char *outbuf)
|
|
{
|
|
z_stream stream;
|
|
unsigned char *inbuf;
|
|
int sig;
|
|
int version, general, method;
|
|
int namelength, extralength;
|
|
int code;
|
|
|
|
fz_seek(ctx->file, ent->offset, 0);
|
|
|
|
sig = getlong(ctx->file);
|
|
if (sig != ZIP_LOCAL_FILE_SIG)
|
|
return fz_throw("wrong zip local file signature (0x%x)", sig);
|
|
|
|
version = getshort(ctx->file);
|
|
general = getshort(ctx->file);
|
|
method = getshort(ctx->file);
|
|
(void) getshort(ctx->file); /* file time */
|
|
(void) getshort(ctx->file); /* file date */
|
|
(void) getlong(ctx->file); /* crc-32 */
|
|
(void) getlong(ctx->file); /* csize */
|
|
(void) getlong(ctx->file); /* usize */
|
|
namelength = getshort(ctx->file);
|
|
extralength = getshort(ctx->file);
|
|
|
|
fz_seek(ctx->file, namelength + extralength, 1);
|
|
|
|
if (method == 0)
|
|
{
|
|
fz_read(ctx->file, outbuf, ent->usize);
|
|
}
|
|
else if (method == 8)
|
|
{
|
|
inbuf = fz_malloc(ent->csize);
|
|
|
|
fz_read(ctx->file, inbuf, ent->csize);
|
|
|
|
memset(&stream, 0, sizeof(z_stream));
|
|
stream.zalloc = (alloc_func) xps_zip_alloc_items;
|
|
stream.zfree = (free_func) xps_zip_free;
|
|
stream.opaque = ctx;
|
|
stream.next_in = inbuf;
|
|
stream.avail_in = ent->csize;
|
|
stream.next_out = outbuf;
|
|
stream.avail_out = ent->usize;
|
|
|
|
code = inflateInit2(&stream, -15);
|
|
if (code != Z_OK)
|
|
return fz_throw("zlib inflateInit2 error: %s", stream.msg);
|
|
code = inflate(&stream, Z_FINISH);
|
|
if (code != Z_STREAM_END)
|
|
{
|
|
inflateEnd(&stream);
|
|
return fz_throw("zlib inflate error: %s", stream.msg);
|
|
}
|
|
code = inflateEnd(&stream);
|
|
if (code != Z_OK)
|
|
return fz_throw("zlib inflateEnd error: %s", stream.msg);
|
|
|
|
fz_free(inbuf);
|
|
}
|
|
else
|
|
{
|
|
return fz_throw("unknown compression method (%d)", method);
|
|
}
|
|
|
|
return fz_okay;
|
|
}
|
|
|
|
/*
|
|
* Read the central directory in a zip file.
|
|
*/
|
|
|
|
static int
|
|
xps_read_zip_dir(xps_context *ctx, int start_offset)
|
|
{
|
|
int sig;
|
|
int offset, count;
|
|
int namesize, metasize, commentsize;
|
|
int i;
|
|
|
|
fz_seek(ctx->file, start_offset, 0);
|
|
|
|
sig = getlong(ctx->file);
|
|
if (sig != ZIP_END_OF_CENTRAL_DIRECTORY_SIG)
|
|
return fz_throw("wrong zip end of central directory signature (0x%x)", sig);
|
|
|
|
(void) getshort(ctx->file); /* this disk */
|
|
(void) getshort(ctx->file); /* start disk */
|
|
(void) getshort(ctx->file); /* entries in this disk */
|
|
count = getshort(ctx->file); /* entries in central directory disk */
|
|
(void) getlong(ctx->file); /* size of central directory */
|
|
offset = getlong(ctx->file); /* offset to central directory */
|
|
|
|
ctx->zip_count = count;
|
|
ctx->zip_table = fz_calloc(count, sizeof(xps_entry));
|
|
memset(ctx->zip_table, 0, sizeof(xps_entry) * count);
|
|
|
|
fz_seek(ctx->file, offset, 0);
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
sig = getlong(ctx->file);
|
|
if (sig != ZIP_CENTRAL_DIRECTORY_SIG)
|
|
return fz_throw("wrong zip central directory signature (0x%x)", sig);
|
|
|
|
(void) getshort(ctx->file); /* version made by */
|
|
(void) getshort(ctx->file); /* version to extract */
|
|
(void) getshort(ctx->file); /* general */
|
|
(void) getshort(ctx->file); /* method */
|
|
(void) getshort(ctx->file); /* last mod file time */
|
|
(void) getshort(ctx->file); /* last mod file date */
|
|
(void) getlong(ctx->file); /* crc-32 */
|
|
ctx->zip_table[i].csize = getlong(ctx->file);
|
|
ctx->zip_table[i].usize = getlong(ctx->file);
|
|
namesize = getshort(ctx->file);
|
|
metasize = getshort(ctx->file);
|
|
commentsize = getshort(ctx->file);
|
|
(void) getshort(ctx->file); /* disk number start */
|
|
(void) getshort(ctx->file); /* int file atts */
|
|
(void) getlong(ctx->file); /* ext file atts */
|
|
ctx->zip_table[i].offset = getlong(ctx->file);
|
|
|
|
ctx->zip_table[i].name = fz_malloc(namesize + 1);
|
|
fz_read(ctx->file, (unsigned char*)ctx->zip_table[i].name, namesize);
|
|
ctx->zip_table[i].name[namesize] = 0;
|
|
|
|
fz_seek(ctx->file, metasize, 1);
|
|
fz_seek(ctx->file, commentsize, 1);
|
|
}
|
|
|
|
qsort(ctx->zip_table, count, sizeof(xps_entry), xps_compare_entries);
|
|
|
|
return fz_okay;
|
|
}
|
|
|
|
static int
|
|
xps_find_and_read_zip_dir(xps_context *ctx)
|
|
{
|
|
unsigned char buf[512];
|
|
int file_size, back, maxback;
|
|
int i, n;
|
|
|
|
fz_seek(ctx->file, 0, SEEK_END);
|
|
file_size = fz_tell(ctx->file);
|
|
|
|
maxback = MIN(file_size, 0xFFFF + sizeof buf);
|
|
back = MIN(maxback, sizeof buf);
|
|
|
|
while (back < maxback)
|
|
{
|
|
fz_seek(ctx->file, file_size - back, 0);
|
|
|
|
n = fz_read(ctx->file, buf, sizeof buf);
|
|
if (n < 0)
|
|
return fz_throw("cannot read end of central directory");
|
|
|
|
for (i = n - 4; i > 0; i--)
|
|
if (!memcmp(buf + i, "PK\5\6", 4))
|
|
return xps_read_zip_dir(ctx, file_size - back + i);
|
|
|
|
back += sizeof buf - 4;
|
|
}
|
|
|
|
return fz_throw("cannot find end of central directory");
|
|
}
|
|
|
|
/*
|
|
* Read and interleave split parts from a ZIP file.
|
|
*/
|
|
static xps_part *
|
|
xps_read_zip_part(xps_context *ctx, char *partname)
|
|
{
|
|
char buf[2048];
|
|
xps_entry *ent;
|
|
xps_part *part;
|
|
int count, size, offset, i;
|
|
char *name;
|
|
|
|
name = partname;
|
|
if (name[0] == '/')
|
|
name ++;
|
|
|
|
/* All in one piece */
|
|
ent = xps_find_zip_entry(ctx, name);
|
|
if (ent)
|
|
{
|
|
part = xps_new_part(ctx, partname, ent->usize);
|
|
xps_read_zip_entry(ctx, ent, part->data);
|
|
return part;
|
|
}
|
|
|
|
/* Count the number of pieces and their total size */
|
|
count = 0;
|
|
size = 0;
|
|
while (1)
|
|
{
|
|
sprintf(buf, "%s/[%d].piece", name, count);
|
|
ent = xps_find_zip_entry(ctx, buf);
|
|
if (!ent)
|
|
{
|
|
sprintf(buf, "%s/[%d].last.piece", name, count);
|
|
ent = xps_find_zip_entry(ctx, buf);
|
|
}
|
|
if (!ent)
|
|
break;
|
|
count ++;
|
|
size += ent->usize;
|
|
}
|
|
|
|
/* Inflate the pieces */
|
|
if (count)
|
|
{
|
|
part = xps_new_part(ctx, partname, size);
|
|
offset = 0;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (i < count - 1)
|
|
sprintf(buf, "%s/[%d].piece", name, i);
|
|
else
|
|
sprintf(buf, "%s/[%d].last.piece", name, i);
|
|
ent = xps_find_zip_entry(ctx, buf);
|
|
xps_read_zip_entry(ctx, ent, part->data + offset);
|
|
offset += ent->usize;
|
|
}
|
|
return part;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Read and interleave split parts from files in the directory.
|
|
*/
|
|
static xps_part *
|
|
xps_read_dir_part(xps_context *ctx, char *name)
|
|
{
|
|
char buf[2048];
|
|
xps_part *part;
|
|
FILE *file;
|
|
int count, size, offset, i, n;
|
|
|
|
fz_strlcpy(buf, ctx->directory, sizeof buf);
|
|
fz_strlcat(buf, name, sizeof buf);
|
|
|
|
/* All in one piece */
|
|
file = fopen(buf, "rb");
|
|
if (file)
|
|
{
|
|
fseek(file, 0, SEEK_END);
|
|
size = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
part = xps_new_part(ctx, name, size);
|
|
fread(part->data, 1, size, file);
|
|
fclose(file);
|
|
return part;
|
|
}
|
|
|
|
/* Count the number of pieces and their total size */
|
|
count = 0;
|
|
size = 0;
|
|
while (1)
|
|
{
|
|
sprintf(buf, "%s%s/[%d].piece", ctx->directory, name, count);
|
|
file = fopen(buf, "rb");
|
|
if (!file)
|
|
{
|
|
sprintf(buf, "%s%s/[%d].last.piece", ctx->directory, name, count);
|
|
file = fopen(buf, "rb");
|
|
}
|
|
if (!file)
|
|
break;
|
|
count ++;
|
|
fseek(file, 0, SEEK_END);
|
|
size += ftell(file);
|
|
fclose(file);
|
|
}
|
|
|
|
/* Inflate the pieces */
|
|
if (count)
|
|
{
|
|
part = xps_new_part(ctx, name, size);
|
|
offset = 0;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (i < count - 1)
|
|
sprintf(buf, "%s%s/[%d].piece", ctx->directory, name, i);
|
|
else
|
|
sprintf(buf, "%s%s/[%d].last.piece", ctx->directory, name, i);
|
|
file = fopen(buf, "rb");
|
|
n = fread(part->data + offset, 1, size - offset, file);
|
|
offset += n;
|
|
fclose(file);
|
|
}
|
|
return part;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
xps_part *
|
|
xps_read_part(xps_context *ctx, char *partname)
|
|
{
|
|
if (ctx->directory)
|
|
return xps_read_dir_part(ctx, partname);
|
|
return xps_read_zip_part(ctx, partname);
|
|
}
|
|
|
|
static int
|
|
xps_open_directory(xps_context **ctxp, char *directory)
|
|
{
|
|
xps_context *ctx;
|
|
int code;
|
|
|
|
ctx = fz_malloc(sizeof(xps_context));
|
|
memset(ctx, 0, sizeof(xps_context));
|
|
|
|
ctx->directory = fz_strdup(directory);
|
|
|
|
code = xps_read_page_list(ctx);
|
|
if (code)
|
|
{
|
|
xps_free_context(ctx);
|
|
return fz_rethrow(code, "cannot read page list");
|
|
}
|
|
|
|
*ctxp = ctx;
|
|
return fz_okay;
|
|
}
|
|
|
|
int
|
|
xps_open_stream(xps_context **ctxp, fz_stream *file)
|
|
{
|
|
xps_context *ctx;
|
|
int code;
|
|
|
|
ctx = fz_malloc(sizeof(xps_context));
|
|
memset(ctx, 0, sizeof(xps_context));
|
|
|
|
ctx->file = fz_keep_stream(file);
|
|
|
|
code = xps_find_and_read_zip_dir(ctx);
|
|
if (code < 0)
|
|
{
|
|
xps_free_context(ctx);
|
|
return fz_rethrow(code, "cannot read zip central directory");
|
|
}
|
|
|
|
code = xps_read_page_list(ctx);
|
|
if (code)
|
|
{
|
|
xps_free_context(ctx);
|
|
return fz_rethrow(code, "cannot read page list");
|
|
}
|
|
|
|
*ctxp = ctx;
|
|
return fz_okay;
|
|
}
|
|
|
|
int
|
|
xps_open_file(xps_context **ctxp, char *filename)
|
|
{
|
|
char buf[2048];
|
|
fz_stream *file;
|
|
char *p;
|
|
int code;
|
|
|
|
if (strstr(filename, "/_rels/.rels") || strstr(filename, "\\_rels\\.rels"))
|
|
{
|
|
fz_strlcpy(buf, filename, sizeof buf);
|
|
p = strstr(buf, "/_rels/.rels");
|
|
if (!p)
|
|
p = strstr(buf, "\\_rels\\.rels");
|
|
*p = 0;
|
|
return xps_open_directory(ctxp, buf);
|
|
}
|
|
|
|
file = fz_open_file(filename);
|
|
if (!file)
|
|
return fz_throw("cannot open file '%s': %s", filename, strerror(errno));
|
|
|
|
code = xps_open_stream(ctxp, file);
|
|
fz_close(file);
|
|
if (code)
|
|
return fz_rethrow(code, "cannot load document '%s'", filename);
|
|
return fz_okay;
|
|
}
|
|
|
|
void
|
|
xps_free_context(xps_context *ctx)
|
|
{
|
|
xps_font_cache *font, *next;
|
|
int i;
|
|
|
|
if (ctx->file)
|
|
fz_close(ctx->file);
|
|
|
|
for (i = 0; i < ctx->zip_count; i++)
|
|
fz_free(ctx->zip_table[i].name);
|
|
fz_free(ctx->zip_table);
|
|
|
|
font = ctx->font_table;
|
|
while (font)
|
|
{
|
|
next = font->next;
|
|
fz_drop_font(font->font);
|
|
fz_free(font->name);
|
|
fz_free(font);
|
|
font = next;
|
|
}
|
|
|
|
xps_free_page_list(ctx);
|
|
|
|
fz_free(ctx->start_part);
|
|
fz_free(ctx->directory);
|
|
fz_free(ctx);
|
|
}
|