Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/source/fitz/load-pnm.c @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mupdf-source/source/fitz/load-pnm.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,1062 @@ +// Copyright (C) 2004-2025 Artifex Software, Inc. +// +// This file is part of MuPDF. +// +// MuPDF is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// MuPDF 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 Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> +// +// Alternative licensing terms are available from the licensor. +// For commercial licensing, see <https://www.artifex.com/> or contact +// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, +// CA 94129, USA, for further information. + +#include "mupdf/fitz.h" + +#include "pixmap-imp.h" + +#include <string.h> +#include <limits.h> + +enum +{ + PAM_UNKNOWN = 0, + PAM_BW, + PAM_BWA, + PAM_GRAY, + PAM_GRAYA, + PAM_RGB, + PAM_RGBA, + PAM_CMYK, + PAM_CMYKA, +}; + +enum +{ + TOKEN_UNKNOWN = 0, + TOKEN_WIDTH, + TOKEN_HEIGHT, + TOKEN_DEPTH, + TOKEN_MAXVAL, + TOKEN_TUPLTYPE, + TOKEN_ENDHDR, +}; + +enum +{ + ENDIAN_UNKNOWN = 0, + ENDIAN_LITTLE, + ENDIAN_BIG, +}; + +struct info +{ + int subimages; + fz_colorspace *cs; + int width, height; + int maxval, bitdepth; + int depth, alpha; + int tupletype; + int endian; + float scale; +}; + +static inline int iswhiteeol(int a) +{ + switch (a) { + case ' ': case '\t': case '\r': case '\n': + return 1; + } + return 0; +} + +static inline int iswhite(int a) +{ + switch (a) { + case ' ': case '\t': + return 1; + } + return 0; +} + +static inline int bitdepth_from_maxval(int maxval) +{ + int depth = 0; + while (maxval) + { + maxval >>= 1; + depth++; + } + return depth; +} + +static const unsigned char * +pnm_read_signature(fz_context *ctx, const unsigned char *p, const unsigned char *e, char *signature) +{ + if (e - p < 2) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse magic number in pnm image"); + if (p[0] != 'P' || ((p[1] < '1' || p[1] > '7') && p[1] != 'F' && p[1] != 'f')) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected signature in pnm image"); + + signature[0] = *p++; + signature[1] = *p++; + return p; +} + +static const unsigned char * +pnm_read_until_eol(fz_context *ctx, const unsigned char *p, const unsigned char *e, int acceptCR) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse line in pnm image"); + + while (p < e && ((acceptCR && *p != '\r' && *p != '\n') || (!acceptCR && *p != '\n'))) + p++; + + return p; +} + +static const unsigned char * +pnm_read_eol(fz_context *ctx, const unsigned char *p, const unsigned char *e, int acceptCR) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse end of line in pnm image"); + if ((acceptCR && *p != '\r' && *p != '\n') || (!acceptCR && *p != '\n')) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected end of line in pnm image"); + + /* CR, CRLF or LF depending on acceptCR. */ + if (acceptCR && *p == '\r') + p++; + if (p < e && *p == '\n') + p++; + + return p; +} + +static const unsigned char * +pnm_read_whites(fz_context *ctx, const unsigned char *p, const unsigned char *e, int required) +{ + if (required && e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse whitespaces in pnm image"); + if (required && !iswhite(*p)) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected whitespaces in pnm image"); + + while (p < e && iswhite(*p)) + p++; + + return p; +} + +static const unsigned char * +pnm_read_white_or_eol(fz_context *ctx, const unsigned char *p, const unsigned char *e) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse whitespace/eol in pnm image"); + if (!iswhiteeol(*p)) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected whitespace/eol in pnm image"); + + return ++p; +} + +static const unsigned char * +pnm_read_whites_and_eols(fz_context *ctx, const unsigned char *p, const unsigned char *e, int required) +{ + if (required && e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse whitespaces/eols in pnm image"); + if (required && !iswhiteeol(*p)) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected whitespaces/eols in pnm image"); + + while (p < e && iswhiteeol(*p)) + p++; + + return p; +} + +static const unsigned char * +pnm_read_comment(fz_context *ctx, const unsigned char *p, const unsigned char *e, int acceptCR) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse line in pnm image"); + + if (*p != '#') + return p; + + return pnm_read_until_eol(ctx, p, e, acceptCR); +} + +static const unsigned char * +pnm_read_comments(fz_context *ctx, const unsigned char *p, const unsigned char *e, int acceptCR) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse comment in pnm image"); + + while (p < e && *p == '#') + { + p = pnm_read_comment(ctx, p, e, acceptCR); + p = pnm_read_eol(ctx, p, e, acceptCR); + } + + return p; +} + +static const unsigned char * +pnm_read_digit(fz_context *ctx, const unsigned char *p, const unsigned char *e, int *number) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse digit in pnm image"); + if (*p < '0' || *p > '1') + fz_throw(ctx, FZ_ERROR_FORMAT, "expected digit in pnm image"); + + if (number) + *number = *p - '0'; + p++; + + return p; +} + +static const unsigned char * +pnm_read_int(fz_context *ctx, const unsigned char *p, const unsigned char *e, int *number) +{ + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse integer in pnm image"); + if (*p < '0' || *p > '9') + fz_throw(ctx, FZ_ERROR_FORMAT, "expected integer in pnm image"); + + while (p < e && *p >= '0' && *p <= '9') + { + if (number) + *number = *number * 10 + *p - '0'; + p++; + } + + return p; +} + +static const unsigned char * +pnm_read_real(fz_context *ctx, const unsigned char *p, const unsigned char *e, float *number) +{ + const unsigned char *orig = p; + char *buf, *end; + size_t len; + + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse real in pnm image"); + + if (*p != '+' && *p != '-' && (*p < '0' || *p > '9')) + fz_throw(ctx, FZ_ERROR_FORMAT, "expected numeric field in pnm image"); + + while (p < e && (*p == '+' || *p == '-' || *p == '.' || (*p >= '0' && *p <= '9'))) + p++; + + len = p - orig + 1; + end = buf = fz_malloc(ctx, len); + + fz_try(ctx) + { + memcpy(buf, orig, len - 1); + buf[len - 1] = '\0'; + *number = fz_strtof(buf, &end); + p = orig + (end - buf); + } + fz_always(ctx) + fz_free(ctx, buf); + fz_catch(ctx) + fz_rethrow(ctx); + + return p; +} + +static const unsigned char * +pnm_read_tupletype(fz_context *ctx, const unsigned char *p, const unsigned char *e, int *tupletype) +{ + const struct { int len; char *str; int type; } tupletypes[] = + { + {13, "BLACKANDWHITE", PAM_BW}, + {19, "BLACKANDWHITE_ALPHA", PAM_BWA}, + {9, "GRAYSCALE", PAM_GRAY}, + {15, "GRAYSCALE_ALPHA", PAM_GRAYA}, + {3, "RGB", PAM_RGB}, + {9, "RGB_ALPHA", PAM_RGBA}, + {4, "CMYK", PAM_CMYK}, + {10, "CMYK_ALPHA", PAM_CMYKA}, + }; + const unsigned char *s; + int i, len; + + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse tuple type in pnm image"); + + s = p; + while (p < e && !iswhiteeol(*p)) + p++; + len = p - s; + + for (i = 0; i < (int)nelem(tupletypes); i++) + if (len == tupletypes[i].len && !strncmp((char *) s, tupletypes[i].str, len)) + { + *tupletype = tupletypes[i].type; + return p; + } + + fz_throw(ctx, FZ_ERROR_FORMAT, "unknown tuple type in pnm image"); +} + +static const unsigned char * +pnm_read_token(fz_context *ctx, const unsigned char *p, const unsigned char *e, int *token) +{ + const struct { int len; char *str; int type; } tokens[] = + { + {5, "WIDTH", TOKEN_WIDTH}, + {6, "HEIGHT", TOKEN_HEIGHT}, + {5, "DEPTH", TOKEN_DEPTH}, + {6, "MAXVAL", TOKEN_MAXVAL}, + {8, "TUPLTYPE", TOKEN_TUPLTYPE}, + {6, "ENDHDR", TOKEN_ENDHDR}, + }; + const unsigned char *s; + int i, len; + + if (e - p < 1) + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot parse header token in pnm image"); + + s = p; + while (p < e && !iswhiteeol(*p)) + p++; + len = p - s; + + for (i = 0; i < (int)nelem(tokens); i++) + if (len == tokens[i].len && !strncmp((char *) s, tokens[i].str, len)) + { + *token = tokens[i].type; + return p; + } + + fz_throw(ctx, FZ_ERROR_FORMAT, "unknown header token in pnm image"); +} + +static int +map_color(fz_context *ctx, int color, int inmax, int outmax) +{ + float f = (float) color / inmax; + return f * outmax; +} + +static fz_pixmap * +pnm_ascii_read_image(fz_context *ctx, struct info *pnm, const unsigned char *p, const unsigned char *e, int onlymeta, int bitmap, const unsigned char **out) +{ + fz_pixmap *img = NULL; + + pnm->width = 0; + p = pnm_read_comments(ctx, p, e, 1); + p = pnm_read_int(ctx, p, e, &pnm->width); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + if (bitmap) + { + pnm->height = 0; + p = pnm_read_int(ctx, p, e, &pnm->height); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + pnm->maxval = 1; + } + else + { + pnm->height = 0; + p = pnm_read_comments(ctx, p, e, 1); + p = pnm_read_int(ctx, p, e, &pnm->height); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + pnm->maxval = 0; + p = pnm_read_comments(ctx, p, e, 1); + p = pnm_read_int(ctx, p, e, &pnm->maxval); + p = pnm_read_white_or_eol(ctx, p, e); + } + + if (pnm->maxval <= 0 || pnm->maxval >= 65536) + fz_throw(ctx, FZ_ERROR_FORMAT, "maximum sample value of out range in pnm image: %d", pnm->maxval); + + pnm->bitdepth = bitdepth_from_maxval(pnm->maxval); + + if (pnm->height <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image height must be > 0"); + if (pnm->width <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image width must be > 0"); + if ((unsigned int)pnm->height > UINT_MAX / pnm->width / fz_colorspace_n(ctx, pnm->cs) / (pnm->bitdepth / 8 + 1)) + fz_throw(ctx, FZ_ERROR_LIMIT, "image too large"); + + if (onlymeta) + { + int x, y, k; + int w, h, n; + + w = pnm->width; + h = pnm->height; + n = fz_colorspace_n(ctx, pnm->cs); + + if (bitmap) + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + { + p = pnm_read_whites_and_eols(ctx, p, e, 0); + p = pnm_read_digit(ctx, p, e, NULL); + p = pnm_read_whites_and_eols(ctx, p, e, 0); + } + } + else + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + { + p = pnm_read_whites_and_eols(ctx, p, e, 0); + p = pnm_read_int(ctx, p, e, NULL); + p = pnm_read_whites_and_eols(ctx, p, e, 0); + } + } + } + else + { + unsigned char *dp; + int x, y, k; + int w, h, n; + + img = fz_new_pixmap(ctx, pnm->cs, pnm->width, pnm->height, NULL, 0); + dp = img->samples; + + w = img->w; + h = img->h; + n = img->n; + + if (bitmap) + { + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + int v = 0; + p = pnm_read_whites_and_eols(ctx, p, e, 0); + p = pnm_read_digit(ctx, p, e, &v); + p = pnm_read_whites_and_eols(ctx, p, e, 0); + *dp++ = v ? 0x00 : 0xff; + } + } + } + else + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + { + int v = 0; + p = pnm_read_whites_and_eols(ctx, p, e, 0); + p = pnm_read_int(ctx, p, e, &v); + p = pnm_read_whites_and_eols(ctx, p, e, 0); + v = fz_clampi(v, 0, pnm->maxval); + *dp++ = map_color(ctx, v, pnm->maxval, 255); + } + } + } + + if (out) + *out = p; + + return img; +} + +static fz_pixmap * +pnm_binary_read_image(fz_context *ctx, struct info *pnm, const unsigned char *p, const unsigned char *e, int onlymeta, int bitmap, const unsigned char **out) +{ + fz_pixmap *img = NULL; + size_t span; + int n; + + n = fz_colorspace_n(ctx, pnm->cs); + assert(n >= 1 && n <= 3); + + pnm->width = 0; + p = pnm_read_comments(ctx, p, e, 1); + p = pnm_read_int(ctx, p, e, &pnm->width); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + if (bitmap) + { + pnm->height = 0; + p = pnm_read_int(ctx, p, e, &pnm->height); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + pnm->maxval = 1; + } + else + { + pnm->height = 0; + p = pnm_read_comments(ctx, p, e, 1); + p = pnm_read_int(ctx, p, e, &pnm->height); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + pnm->maxval = 0; + p = pnm_read_comments(ctx, p, e, 1); + p = pnm_read_int(ctx, p, e, &pnm->maxval); + p = pnm_read_white_or_eol(ctx, p, e); + } + + if (pnm->maxval <= 0 || pnm->maxval >= 65536) + fz_throw(ctx, FZ_ERROR_FORMAT, "maximum sample value of out range in pnm image: %d", pnm->maxval); + + pnm->bitdepth = bitdepth_from_maxval(pnm->maxval); + + if (pnm->height <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image height must be > 0"); + if (pnm->width <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image width must be > 0"); + if (pnm->bitdepth == 1) + { + /* Overly sensitive test, but we can live with it. */ + if ((size_t)pnm->width > SIZE_MAX / (unsigned int)n) + fz_throw(ctx, FZ_ERROR_LIMIT, "image row too large"); + span = ((size_t)n * pnm->width + 7)/8; + } + else + { + size_t bytes_per_sample = (pnm->bitdepth-1)/8 + 1; + span = (size_t)n * bytes_per_sample; + if ((size_t)pnm->width > SIZE_MAX / span) + fz_throw(ctx, FZ_ERROR_LIMIT, "image row too large"); + span = (size_t)pnm->width * span; + } + if ((size_t)pnm->height > SIZE_MAX / span) + fz_throw(ctx, FZ_ERROR_LIMIT, "image too large"); + if (e - p < 0 || ((size_t)(e - p)) < span * (size_t)pnm->height) + fz_throw(ctx, FZ_ERROR_FORMAT, "insufficient data"); + + if (onlymeta) + { + p += span * (size_t)pnm->height; + } + else + { + unsigned char *dp; + int x, y, k; + int w, h; + + img = fz_new_pixmap(ctx, pnm->cs, pnm->width, pnm->height, NULL, 0); + dp = img->samples; + + w = img->w; + h = img->h; + n = img->n; + + if (pnm->maxval == 255) + { + memcpy(dp, p, (size_t)w * h * n); + p += n * w * h; + } + else if (bitmap) + { + for (y = 0; y < h; y++) + { + for (x = 0; x < w; x++) + { + *dp++ = (*p & (1 << (7 - (x & 0x7)))) ? 0x00 : 0xff; + if ((x & 0x7) == 7) + p++; + } + if (w & 0x7) + p++; + } + } + else if (pnm->maxval < 255) + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + *dp++ = map_color(ctx, *p++, pnm->maxval, 255); + } + else + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + { + *dp++ = map_color(ctx, (p[0] << 8) | p[1], pnm->maxval, 255); + p += 2; + } + } + } + + if (out) + *out = p; + + return img; +} + +static const unsigned char * +pam_binary_read_header(fz_context *ctx, struct info *pnm, const unsigned char *p, const unsigned char *e) +{ + int token = TOKEN_UNKNOWN; + const unsigned char *eol; + int seen[TOKEN_ENDHDR] = { 0 }; + + pnm->width = 0; + pnm->height = 0; + pnm->depth = 0; + pnm->maxval = 0; + pnm->tupletype = 0; + + while (p < e && token != TOKEN_ENDHDR) + { + eol = pnm_read_until_eol(ctx, p, e, 0); + + p = pnm_read_whites(ctx, p, eol, 0); + + if (p < eol && *p != '#') + { + p = pnm_read_token(ctx, p, eol, &token); + + if (seen[token - 1]) + fz_throw(ctx, FZ_ERROR_FORMAT, "token occurs multiple times in pnm image"); + seen[token - 1] = 1; + + if (token != TOKEN_ENDHDR) + { + p = pnm_read_whites(ctx, p, eol, 1); + switch (token) + { + case TOKEN_WIDTH: pnm->width = 0; p = pnm_read_int(ctx, p, eol, &pnm->width); break; + case TOKEN_HEIGHT: pnm->height = 0; p = pnm_read_int(ctx, p, eol, &pnm->height); break; + case TOKEN_DEPTH: pnm->depth = 0; p = pnm_read_int(ctx, p, eol, &pnm->depth); break; + case TOKEN_MAXVAL: pnm->maxval = 0; p = pnm_read_int(ctx, p, eol, &pnm->maxval); break; + case TOKEN_TUPLTYPE: pnm->tupletype = 0; p = pnm_read_tupletype(ctx, p, eol, &pnm->tupletype); break; + } + } + + p = pnm_read_whites(ctx, p, eol, 0); + } + + if (p < eol && *p == '#') + p = pnm_read_comment(ctx, p, eol, 0); + + p = pnm_read_eol(ctx, p, e, 0); + } + + return p; +} + +static fz_pixmap * +pam_binary_read_image(fz_context *ctx, struct info *pnm, const unsigned char *p, const unsigned char *e, int onlymeta, const unsigned char **out) +{ + fz_pixmap *img = NULL; + int bitmap = 0; + int minval = 1; + int maxval = 65535; + + fz_var(img); + + p = pam_binary_read_header(ctx, pnm, p, e); + + if (pnm->tupletype == PAM_UNKNOWN) + switch (pnm->depth) + { + case 1: pnm->tupletype = pnm->maxval == 1 ? PAM_BW : PAM_GRAY; break; + case 2: pnm->tupletype = pnm->maxval == 1 ? PAM_BWA : PAM_GRAYA; break; + case 3: pnm->tupletype = PAM_RGB; break; + case 4: pnm->tupletype = PAM_CMYK; break; + case 5: pnm->tupletype = PAM_CMYKA; break; + default: + fz_throw(ctx, FZ_ERROR_FORMAT, "cannot guess tuple type based on depth in pnm image"); + } + + if (pnm->tupletype == PAM_BW && pnm->maxval > 1) + pnm->tupletype = PAM_GRAY; + else if (pnm->tupletype == PAM_GRAY && pnm->maxval == 1) + pnm->tupletype = PAM_BW; + else if (pnm->tupletype == PAM_BWA && pnm->maxval > 1) + pnm->tupletype = PAM_GRAYA; + else if (pnm->tupletype == PAM_GRAYA && pnm->maxval == 1) + pnm->tupletype = PAM_BWA; + + switch (pnm->tupletype) + { + case PAM_BWA: + pnm->alpha = 1; + /* fallthrough */ + case PAM_BW: + pnm->cs = fz_device_gray(ctx); + maxval = 1; + bitmap = 1; + break; + case PAM_GRAYA: + pnm->alpha = 1; + /* fallthrough */ + case PAM_GRAY: + pnm->cs = fz_device_gray(ctx); + minval = 2; + break; + case PAM_RGBA: + pnm->alpha = 1; + /* fallthrough */ + case PAM_RGB: + pnm->cs = fz_device_rgb(ctx); + break; + case PAM_CMYKA: + pnm->alpha = 1; + /* fallthrough */ + case PAM_CMYK: + pnm->cs = fz_device_cmyk(ctx); + break; + default: + fz_throw(ctx, FZ_ERROR_FORMAT, "unsupported tuple type"); + } + + if (pnm->depth != fz_colorspace_n(ctx, pnm->cs) + pnm->alpha) + fz_throw(ctx, FZ_ERROR_FORMAT, "depth out of tuple type range"); + if (pnm->maxval < minval || pnm->maxval > maxval) + fz_throw(ctx, FZ_ERROR_FORMAT, "maxval out of range"); + + pnm->bitdepth = bitdepth_from_maxval(pnm->maxval); + + if (pnm->height <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image height must be > 0"); + if (pnm->width <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image width must be > 0"); + if ((unsigned int)pnm->height > UINT_MAX / pnm->width / fz_colorspace_n(ctx, pnm->cs) / (pnm->bitdepth / 8 + 1)) + fz_throw(ctx, FZ_ERROR_LIMIT, "image too large"); + + if (onlymeta) + { + int packed; + int w, h, n; + size_t size; + + w = pnm->width; + h = pnm->height; + n = fz_colorspace_n(ctx, pnm->cs) + pnm->alpha; + + /* some encoders incorrectly pack bits into bytes and invert the image */ + packed = 0; + size = (size_t)w * h * n; + if (pnm->maxval == 1) + { + const unsigned char *e_packed = p + size / 8; + if (e_packed < e - 1 && e_packed[0] == 'P' && e_packed[1] >= '0' && e_packed[1] <= '7') + e = e_packed; + if (e < p || (size_t)(e - p) < size) + packed = 1; + } + if (packed && (e < p || (size_t)(e - p) < size / 8)) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated packed image"); + if (!packed && (e < p || (size_t)(e - p) < size * (pnm->maxval < 256 ? 1 : 2))) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated image"); + + if (pnm->maxval == 255) + p += size; + else if (bitmap && packed) + p += ((w + 7) / 8) * h; + else if (bitmap) + p += size; + else if (pnm->maxval < 255) + p += size; + else + p += 2 * size; + } + else + { + unsigned char *dp; + int x, y, k, packed; + int w, h, n; + size_t size; + + img = fz_new_pixmap(ctx, pnm->cs, pnm->width, pnm->height, NULL, pnm->alpha); + fz_try(ctx) + { + dp = img->samples; + + w = img->w; + h = img->h; + n = img->n; + + /* some encoders incorrectly pack bits into bytes and invert the image */ + size = (size_t)w * h * n; + packed = 0; + if (pnm->maxval == 1) + { + const unsigned char *e_packed = p + size / 8; + if (e_packed < e - 1 && e_packed[0] == 'P' && e_packed[1] >= '0' && e_packed[1] <= '7') + e = e_packed; + if (e < p || (size_t)(e - p) < size) + packed = 1; + } + if (packed && (e < p || (size_t)(e - p) < size / 8)) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated packed image"); + if (!packed && (e < p || (size_t)(e - p) < size * (pnm->maxval < 256 ? 1 : 2))) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated image"); + + if (pnm->maxval == 255) + memcpy(dp, p, size); + else if (bitmap && packed) + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + { + for (k = 0; k < n; k++) + { + *dp++ = (*p & (1 << (7 - (x & 0x7)))) ? 0x00 : 0xff; + if ((x & 0x7) == 7) + p++; + } + if (w & 0x7) + p++; + } + } + else if (bitmap) + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + *dp++ = *p++ ? 0xff : 0x00; + } + else if (pnm->maxval < 255) + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + *dp++ = map_color(ctx, *p++, pnm->maxval, 255); + } + else + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + { + *dp++ = map_color(ctx, (p[0] << 8) | p[1], pnm->maxval, 255); + p += 2; + } + } + + if (pnm->alpha) + fz_premultiply_pixmap(ctx, img); + } + fz_catch(ctx) + { + fz_drop_pixmap(ctx, img); + fz_rethrow(ctx); + } + } + + if (out) + *out = p; + + return img; +} + +static const unsigned char * +pfm_binary_read_header(fz_context *ctx, struct info *pnm, const unsigned char *p, const unsigned char *e) +{ + pnm->width = 0; + p = pnm_read_int(ctx, p, e, &pnm->width); + p = pnm_read_whites_and_eols(ctx, p, e,1); + + pnm->height = 0; + p = pnm_read_int(ctx, p, e, &pnm->height); + p = pnm_read_whites_and_eols(ctx, p, e,1); + + p = pnm_read_real(ctx, p, e, &pnm->scale); + + p = pnm_read_white_or_eol(ctx, p, e); + + if (pnm->scale >= 0) + pnm->endian = ENDIAN_BIG; + else + { + pnm->endian = ENDIAN_LITTLE; + pnm->scale = -pnm->scale; + } + + return p; +} + +static fz_pixmap * +pfm_binary_read_image(fz_context *ctx, struct info *pnm, const unsigned char *p, const unsigned char *e, int onlymeta, int rgb, const unsigned char **out) +{ + fz_pixmap *pix = NULL; + + fz_var(pix); + + p = pfm_binary_read_header(ctx, pnm, p, e); + pnm->cs = rgb ? fz_device_rgb(ctx) : fz_device_gray(ctx); + + if (pnm->height <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image height must be > 0"); + if (pnm->width <= 0) + fz_throw(ctx, FZ_ERROR_FORMAT, "image width must be > 0"); + if ((unsigned int)pnm->height > UINT_MAX / pnm->width / fz_colorspace_n(ctx, pnm->cs) / (pnm->bitdepth / 8 + 1)) + fz_throw(ctx, FZ_ERROR_LIMIT, "image too large"); + + if (onlymeta) + { + size_t w = pnm->width; + size_t h = pnm->height; + int n = fz_colorspace_n(ctx, pnm->cs); + size_t size = w * h * n * sizeof(float); + + if (e < p || (size_t)(e - p) < size) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated image"); + + p += size; + } + else + { + float *samples = NULL; + float *sample; + int w = pnm->width; + int h = pnm->height; + int n = fz_colorspace_n(ctx, pnm->cs); + size_t size = (size_t) w * h * n * sizeof(float); + int x, y, k; + + if (e < p || (size_t)(e - p) < size) + fz_throw(ctx, FZ_ERROR_FORMAT, "truncated image"); + + sample = samples = fz_malloc(ctx, size); + fz_try(ctx) + { + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + for (k = 0; k < n; k++) + { + uint32_t u; + float f; + + if (pnm->endian == ENDIAN_LITTLE) + u = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + else + u = p[3] | (p[2] << 8) | (p[1] << 16) | (p[0] << 24); + memcpy(&f, &u, sizeof(float)); + + *sample++ = f / pnm->scale; + p += sizeof(float); + } + + pix = fz_new_pixmap_from_float_data(ctx, pnm->cs, w, h, samples); + } + fz_always(ctx) + fz_free(ctx, samples); + fz_catch(ctx) + fz_rethrow(ctx); + } + + if (out) + *out = p; + + return pix; +} + +static fz_pixmap * +pnm_read_image(fz_context *ctx, struct info *pnm, const unsigned char *p, size_t total, int onlymeta, int subimage) +{ + const unsigned char *e = p + total; + char signature[3] = { 0 }; + fz_pixmap *pix = NULL; + + while (p < e && ((!onlymeta && subimage >= 0) || onlymeta)) + { + int subonlymeta = onlymeta || (subimage > 0); + + p = pnm_read_whites_and_eols(ctx, p, e, 0); + p = pnm_read_signature(ctx, p, e, signature); + p = pnm_read_whites_and_eols(ctx, p, e, 1); + + if (!strcmp(signature, "P1")) + { + pnm->cs = fz_device_gray(ctx); + pix = pnm_ascii_read_image(ctx, pnm, p, e, subonlymeta, 1, &p); + } + else if (!strcmp(signature, "P2")) + { + pnm->cs = fz_device_gray(ctx); + pix = pnm_ascii_read_image(ctx, pnm, p, e, subonlymeta, 0, &p); + } + else if (!strcmp(signature, "P3")) + { + pnm->cs = fz_device_rgb(ctx); + pix = pnm_ascii_read_image(ctx, pnm, p, e, subonlymeta, 0, &p); + } + else if (!strcmp(signature, "P4")) + { + pnm->cs = fz_device_gray(ctx); + pix = pnm_binary_read_image(ctx, pnm, p, e, subonlymeta, 1, &p); + } + else if (!strcmp(signature, "P5")) + { + pnm->cs = fz_device_gray(ctx); + pix = pnm_binary_read_image(ctx, pnm, p, e, subonlymeta, 0, &p); + } + else if (!strcmp(signature, "P6")) + { + pnm->cs = fz_device_rgb(ctx); + pix = pnm_binary_read_image(ctx, pnm, p, e, subonlymeta, 0, &p); + } + else if (!strcmp(signature, "P7")) + pix = pam_binary_read_image(ctx, pnm, p, e, subonlymeta, &p); + else if (!strcmp(signature, "Pf")) + pix = pfm_binary_read_image(ctx, pnm, p, e, subonlymeta, 0, &p); + else if (!strcmp(signature, "PF")) + pix = pfm_binary_read_image(ctx, pnm, p, e, subonlymeta, 1, &p); + else + fz_throw(ctx, FZ_ERROR_FORMAT, "unsupported portable anymap signature (0x%02x, 0x%02x)", signature[0], signature[1]); + + p = pnm_read_whites_and_eols(ctx, p, e, 0); + + if (onlymeta) + pnm->subimages++; + if (subimage >= 0) + subimage--; + } + + if (p >= e && subimage >= 0) + fz_throw(ctx, FZ_ERROR_ARGUMENT, "subimage count out of range"); + + return pix; +} + +fz_pixmap * +fz_load_pnm(fz_context *ctx, const unsigned char *p, size_t total) +{ + struct info pnm = { 0 }; + return pnm_read_image(ctx, &pnm, p, total, 0, 0); +} + +void +fz_load_pnm_info(fz_context *ctx, const unsigned char *p, size_t total, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep) +{ + struct info pnm = { 0 }; + (void) pnm_read_image(ctx, &pnm, p, total, 1, 0); + *cspacep = fz_keep_colorspace(ctx, pnm.cs); /* pnm.cs is a borrowed device colorspace */ + *wp = pnm.width; + *hp = pnm.height; + *xresp = 72; + *yresp = 72; +} + +fz_pixmap * +fz_load_pnm_subimage(fz_context *ctx, const unsigned char *p, size_t total, int subimage) +{ + struct info pnm = { 0 }; + return pnm_read_image(ctx, &pnm, p, total, 0, subimage); +} + +int +fz_load_pnm_subimage_count(fz_context *ctx, const unsigned char *p, size_t total) +{ + struct info pnm = { 0 }; + (void) pnm_read_image(ctx, &pnm, p, total, 1, -1); + return pnm.subimages; +}
