Mercurial > hgrepos > Python2 > PyMuPDF
diff mupdf-source/thirdparty/leptonica/src/psio2.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/thirdparty/leptonica/src/psio2.c Mon Sep 15 11:43:07 2025 +0200 @@ -0,0 +1,2006 @@ +/*====================================================================* + - Copyright (C) 2001 Leptonica. All rights reserved. + - + - Redistribution and use in source and binary forms, with or without + - modification, are permitted provided that the following conditions + - are met: + - 1. Redistributions of source code must retain the above copyright + - notice, this list of conditions and the following disclaimer. + - 2. Redistributions in binary form must reproduce the above + - copyright notice, this list of conditions and the following + - disclaimer in the documentation and/or other materials + - provided with the distribution. + - + - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY + - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *====================================================================*/ + +/*! + * \file psio2.c + * <pre> + * + * |=============================================================| + * | Important note | + * |=============================================================| + * | Some of these functions require I/O libraries such as | + * | libtiff, libjpeg, and libz. If you do not have these | + * | libraries, some calls will fail. | + * | | + * | You can manually deactivate all PostScript writing by | + * | setting this in environ.h: | + * | \code | + * | #define USE_PSIO 0 | + * | \endcode | + * | in environ.h. This will link psio2stub.c | + * |=============================================================| + * + * These are lower-level functions that implement a PostScript + * "device driver" for wrapping images in PostScript. The images + * can be rendered by a PostScript interpreter for viewing, + * using evince or gv. They can also be rasterized for printing, + * using gs or an embedded interpreter in a PostScript printer. + * And they can be converted to a pdf using gs (ps2pdf). + * + * For uncompressed images + * l_int32 pixWritePSEmbed() + * l_int32 pixWriteStreamPS() + * char *pixWriteStringPS() + * char *generateUncompressedPS() + * static void getScaledParametersPS() + * static l_int32 convertByteToHexAscii() + * + * For jpeg compressed images (use dct compression) + * l_int32 convertJpegToPSEmbed() + * l_int32 convertJpegToPS() + * static l_int32 convertJpegToPSString() + * static char *generateJpegPS() + * + * For g4 fax compressed images (use ccitt g4 compression) + * l_int32 convertG4ToPSEmbed() + * l_int32 convertG4ToPS() + * static l_int32 convertG4ToPSString() + * static char *generateG4PS() + * + * For multipage tiff images + * l_int32 convertTiffMultipageToPS() + * + * For flate (gzip) compressed images (e.g., png) + * l_int32 convertFlateToPSEmbed() + * l_int32 convertFlateToPS() + * static l_int32 convertFlateToPSString() + * static char *generateFlatePS() + * + * Write to memory + * l_int32 pixWriteMemPS() + * + * Converting resolution + * l_int32 getResLetterPage() + * static l_int32 getResA4Page() + * + * Setting flag for writing bounding box hint + * void l_psWriteBoundingBox() + * + * See psio1.c for higher-level functions and their usage. + * </pre> + */ + +#ifdef HAVE_CONFIG_H +#include <config_auto.h> +#endif /* HAVE_CONFIG_H */ + +#include <string.h> +#include "allheaders.h" + +/* --------------------------------------------*/ +#if USE_PSIO /* defined in environ.h */ + /* --------------------------------------------*/ + + /* Set default for writing bounding box hint */ +static l_int32 var_PS_WRITE_BOUNDING_BOX = 1; + +#define Bufsize 512 +static const l_int32 DefaultInputRes = 300; /* typical scan res, ppi */ +static const l_int32 MinRes = 5; +static const l_int32 MaxRes = 3000; + + /* For computing resolution that fills page to desired amount */ +static const l_int32 LetterWidth = 612; /* points */ +static const l_int32 LetterHeight = 792; /* points */ +static const l_int32 A4Width = 595; /* points */ +static const l_int32 A4Height = 842; /* points */ +static const l_float32 DefaultFillFraction = 0.95f; + +#ifndef NO_CONSOLE_IO +#define DEBUG_JPEG 0 +#define DEBUG_G4 0 +#define DEBUG_FLATE 0 +#endif /* ~NO_CONSOLE_IO */ + +/* Note that the bounding box hint at the top of the generated PostScript + * file is required for the "*Embed" functions. These generate a + * PostScript file for an individual image that can be translated and + * scaled by an application that embeds the image in its output + * (e.g., in the PS output from a TeX file). + * However, bounding box hints should not be embedded in any + * PostScript image that will be composited with other images, + * where more than one image may be placed in an arbitrary location + * on a page. */ + + /* Static helper functions */ +static void getScaledParametersPS(BOX *box, l_int32 wpix, l_int32 hpix, + l_int32 res, l_float32 scale, + l_float32 *pxpt, l_float32 *pypt, + l_float32 *pwpt, l_float32 *phpt); +static void convertByteToHexAscii(l_uint8 byteval, char *pnib1, char *pnib2); +static l_ok convertJpegToPSString(const char *filein, char **poutstr, + l_int32 *pnbytes, l_int32 x, l_int32 y, + l_int32 res, l_float32 scale, + l_int32 pageno, l_int32 endpage); +static char *generateJpegPS(const char *filein, L_COMP_DATA *cid, + l_float32 xpt, l_float32 ypt, l_float32 wpt, + l_float32 hpt, l_int32 pageno, l_int32 endpage); +static l_ok convertG4ToPSString(const char *filein, char **poutstr, + l_int32 *pnbytes, l_int32 x, l_int32 y, + l_int32 res, l_float32 scale, l_int32 pageno, + l_int32 maskflag, l_int32 endpage); +static char *generateG4PS(const char *filein, L_COMP_DATA *cid, l_float32 xpt, + l_float32 ypt, l_float32 wpt, l_float32 hpt, + l_int32 maskflag, l_int32 pageno, l_int32 endpage); +static l_ok convertFlateToPSString(const char *filein, char **poutstr, + l_int32 *pnbytes, l_int32 x, l_int32 y, + l_int32 res, l_float32 scale, + l_int32 pageno, l_int32 endpage); +static char *generateFlatePS(const char *filein, L_COMP_DATA *cid, + l_float32 xpt, l_float32 ypt, l_float32 wpt, + l_float32 hpt, l_int32 pageno, l_int32 endpage); + + +/*-------------------------------------------------------------* + * For uncompressed images * + *-------------------------------------------------------------*/ +/*! + * \brief pixWritePSEmbed() + * + * \param[in] filein input file, all depths, colormap OK + * \param[in] fileout output ps file + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This is a simple wrapper function that generates an + * uncompressed PS file, with a bounding box. + * (2) The bounding box is required when a program such as TeX + * (through epsf) places and rescales the image. + * (3) The bounding box is sized for fitting the image to an + * 8.5 x 11.0 inch page. + * </pre> + */ +l_ok +pixWritePSEmbed(const char *filein, + const char *fileout) +{ +l_int32 w, h, ret; +l_float32 scale; +FILE *fp; +PIX *pix; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + if ((pix = pixRead(filein)) == NULL) + return ERROR_INT("image not read from file", __func__, 1); + w = pixGetWidth(pix); + h = pixGetHeight(pix); + if (w * 11.0 > h * 8.5) + scale = 8.5f * 300.f / (l_float32)w; + else + scale = 11.0f * 300.f / (l_float32)h; + + if ((fp = fopenWriteStream(fileout, "wb")) == NULL) + return ERROR_INT_1("file not opened for write", fileout, __func__, 1); + ret = pixWriteStreamPS(fp, pix, NULL, 0, scale); + fclose(fp); + + pixDestroy(&pix); + return ret; +} + + +/*! + * \brief pixWriteStreamPS() + * + * \param[in] fp file stream + * \param[in] pix + * \param[in] box [optional] + * \param[in] res can use 0 for default of 300 ppi + * \param[in] scale to prevent scaling, use either 1.0 or 0.0 + * \return 0 if OK; 1 on error + * + * <pre> + * Notes: + * (1) This writes image in PS format, optionally scaled, + * adjusted for the printer resolution, and with + * a bounding box. + * (2) For details on use of parameters, see pixWriteStringPS(). + * </pre> + */ +l_ok +pixWriteStreamPS(FILE *fp, + PIX *pix, + BOX *box, + l_int32 res, + l_float32 scale) +{ +char *outstr; +l_int32 length; +PIX *pixc; + + if (!fp) + return (l_int32)ERROR_INT("stream not open", __func__, 1); + if (!pix) + return (l_int32)ERROR_INT("pix not defined", __func__, 1); + + if ((pixc = pixConvertForPSWrap(pix)) == NULL) + return (l_int32)ERROR_INT("pixc not made", __func__, 1); + + if ((outstr = pixWriteStringPS(pixc, box, res, scale)) == NULL) { + pixDestroy(&pixc); + return (l_int32)ERROR_INT("outstr not made", __func__, 1); + } + length = strlen(outstr); + fwrite(outstr, 1, length, fp); + LEPT_FREE(outstr); + pixDestroy(&pixc); + return 0; +} + + +/*! + * \brief pixWriteStringPS() + * + * \param[in] pixs all depths, colormap OK + * \param[in] box bounding box; can be NULL + * \param[in] res resolution, in printer ppi. Use 0 for default 300 ppi. + * \param[in] scale scale factor. If no scaling is desired, use + * either 1.0 or 0.0. Scaling just resets the resolution + * parameter; the actual scaling is done in the + * interpreter at rendering time. This is important: + * it allows you to scale the image up without + * increasing the file size. + * \return ps string if OK, or NULL on error + * + * <pre> + * a) If %box == NULL, image is placed, optionally scaled, + * in a standard b.b. at the center of the page. + * This is to be used when another program like + * TeX through epsf places the image. + * b) If %box != NULL, image is placed without a + * b.b. at the specified page location and with + * optional scaling. This is to be used when + * you want to specify exactly where and optionally + * how big you want the image to be. + * Note that all coordinates are in PS convention, + * with 0,0 at LL corner of the page: + * x,y location of LL corner of image, in mils. + * w,h scaled size, in mils. Use 0 to + * scale with "scale" and "res" input. + * + * %scale: If no scaling is desired, use either 1.0 or 0.0. + * Scaling just resets the resolution parameter; the actual + * scaling is done in the interpreter at rendering time. + * This is important: * it allows you to scale the image up + * without increasing the file size. + * + * Notes: + * (1) OK, this seems a bit complicated, because there are various + * ways to scale and not to scale. Here's a summary: + * (2) If you don't want any scaling at all: + * * if you are using a box: + * set w = 0, h = 0, and use scale = 1.0; it will print + * each pixel unscaled at printer resolution + * * if you are not using a box: + * set scale = 1.0; it will print at printer resolution + * (3) If you want the image to be a certain size in inches: + * * you must use a box and set the box (w,h) in mils + * (4) If you want the image to be scaled by a scale factor != 1.0: + * * if you are using a box: + * set w = 0, h = 0, and use the desired scale factor; + * the higher the printer resolution, the smaller the + * image will actually appear. + * * if you are not using a box: + * set the desired scale factor; the higher the printer + * resolution, the smaller the image will actually appear. + * (5) Another complication is the proliferation of distance units: + * * The interface distances are in milli-inches. + * * Three different units are used internally: + * ~ pixels (units of 1/res inch) + * ~ printer pts (units of 1/72 inch) + * ~ inches + * * Here is a quiz on volume units from a reviewer: + * How many UK milli-cups in a US kilo-teaspoon? + * (Hint: 1.0 US cup = 0.75 UK cup + 0.2 US gill; + * 1.0 US gill = 24.0 US teaspoons) + * </pre> + */ +char * +pixWriteStringPS(PIX *pixs, + BOX *box, + l_int32 res, + l_float32 scale) +{ +char nib1, nib2; +char *hexdata, *outstr; +l_uint8 byteval; +l_int32 i, j, k, w, h, d; +l_float32 wpt, hpt, xpt, ypt; +l_int32 wpl, psbpl, hexbytes, boxflag, bps; +l_uint32 *line, *data; +PIX *pix; + + if (!pixs) + return (char *)ERROR_PTR("pixs not defined", __func__, NULL); + + if ((pix = pixConvertForPSWrap(pixs)) == NULL) + return (char *)ERROR_PTR("pix not made", __func__, NULL); + pixGetDimensions(pix, &w, &h, &d); + + /* Get the factors by which PS scales and translates, in pts */ + if (!box) + boxflag = 0; /* no scaling; b.b. at center */ + else + boxflag = 1; /* no b.b., specify placement and optional scaling */ + getScaledParametersPS(box, w, h, res, scale, &xpt, &ypt, &wpt, &hpt); + + if (d == 1) + bps = 1; /* bits/sample */ + else /* d == 8 || d == 32 */ + bps = 8; + + /* Convert image data to hex string. psbpl is the number of + * bytes in each raster line when it is packed to the byte + * boundary (not the 32 bit word boundary, as with the pix). + * When converted to hex, the hex string has 2 bytes for + * every byte of raster data. */ + wpl = pixGetWpl(pix); + if (d == 1 || d == 8) + psbpl = (w * d + 7) / 8; + else /* d == 32 */ + psbpl = 3 * w; + data = pixGetData(pix); + hexbytes = 2 * psbpl * h; /* size of ps hex array */ + if ((hexdata = (char *)LEPT_CALLOC(hexbytes + 1, sizeof(char))) == NULL) + return (char *)ERROR_PTR("hexdata not made", __func__, NULL); + if (d == 1 || d == 8) { + for (i = 0, k = 0; i < h; i++) { + line = data + i * wpl; + for (j = 0; j < psbpl; j++) { + byteval = GET_DATA_BYTE(line, j); + convertByteToHexAscii(byteval, &nib1, &nib2); + hexdata[k++] = nib1; + hexdata[k++] = nib2; + } + } + } else { /* d == 32; hexdata bytes packed RGBRGB..., 2 per sample */ + for (i = 0, k = 0; i < h; i++) { + line = data + i * wpl; + for (j = 0; j < w; j++) { + byteval = GET_DATA_BYTE(line + j, 0); /* red */ + convertByteToHexAscii(byteval, &nib1, &nib2); + hexdata[k++] = nib1; + hexdata[k++] = nib2; + byteval = GET_DATA_BYTE(line + j, 1); /* green */ + convertByteToHexAscii(byteval, &nib1, &nib2); + hexdata[k++] = nib1; + hexdata[k++] = nib2; + byteval = GET_DATA_BYTE(line + j, 2); /* blue */ + convertByteToHexAscii(byteval, &nib1, &nib2); + hexdata[k++] = nib1; + hexdata[k++] = nib2; + } + } + } + hexdata[k] = '\0'; + + outstr = generateUncompressedPS(hexdata, w, h, d, psbpl, bps, + xpt, ypt, wpt, hpt, boxflag); + pixDestroy(&pix); + if (!outstr) + return (char *)ERROR_PTR("outstr not made", __func__, NULL); + return outstr; +} + + +/*! + * \brief generateUncompressedPS() + * + * \param[in] hexdata + * \param[in] w, h raster image size in pixels + * \param[in] d image depth in bpp; rgb is 32 + * \param[in] psbpl raster bytes/line, when packed to the byte boundary + * \param[in] bps bits/sample: either 1 or 8 + * \param[in] xpt, ypt location of LL corner of image, in pts, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] wpt, hpt rendered image size in pts + * \param[in] boxflag 1 to print out bounding box hint; 0 to skip + * \return PS string, or NULL on error + * + * <pre> + * Notes: + * (1) Low-level function. + * </pre> + */ +char * +generateUncompressedPS(char *hexdata, + l_int32 w, + l_int32 h, + l_int32 d, + l_int32 psbpl, + l_int32 bps, + l_float32 xpt, + l_float32 ypt, + l_float32 wpt, + l_float32 hpt, + l_int32 boxflag) +{ +char *outstr; +char bigbuf[Bufsize]; +SARRAY *sa; + + if (!hexdata) + return (char *)ERROR_PTR("hexdata not defined", __func__, NULL); + + sa = sarrayCreate(0); + sarrayAddString(sa, "%!Adobe-PS", L_COPY); + if (boxflag == 0) { + snprintf(bigbuf, sizeof(bigbuf), + "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f", + xpt, ypt, xpt + wpt, ypt + hpt); + sarrayAddString(sa, bigbuf, L_COPY); + } else { /* boxflag == 1 */ + sarrayAddString(sa, "gsave", L_COPY); + } + + if (d == 1) + sarrayAddString(sa, + "{1 exch sub} settransfer %invert binary", L_COPY); + + snprintf(bigbuf, sizeof(bigbuf), + "/bpl %d string def %%bpl as a string", psbpl); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f scale %%set image size in pts", wpt, hpt); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + "%d %d %d %%image dimensions in pixels", w, h, bps); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + "[%d %d %d %d %d %d] %%mapping matrix: [w 0 0 -h 0 h]", + w, 0, 0, -h, 0, h); + sarrayAddString(sa, bigbuf, L_COPY); + + if (boxflag == 0) { + if (d == 1 || d == 8) + sarrayAddString(sa, + "{currentfile bpl readhexstring pop} image", L_COPY); + else /* d == 32 */ + sarrayAddString(sa, + "{currentfile bpl readhexstring pop} false 3 colorimage", + L_COPY); + } else { /* boxflag == 1 */ + if (d == 1 || d == 8) + sarrayAddString(sa, + "{currentfile bpl readhexstring pop} bind image", L_COPY); + else /* d == 32 */ + sarrayAddString(sa, + "{currentfile bpl readhexstring pop} bind false 3 colorimage", + L_COPY); + } + + sarrayAddString(sa, hexdata, L_INSERT); + + if (boxflag == 0) + sarrayAddString(sa, "\nshowpage", L_COPY); + else /* boxflag == 1 */ + sarrayAddString(sa, "\ngrestore", L_COPY); + + outstr = sarrayToString(sa, 1); + sarrayDestroy(&sa); + if (!outstr) L_ERROR("outstr not made\n", __func__); + return outstr; +} + + +/*! + * \brief getScaledParametersPS() + * + * \param[in] box [optional] location of image in mils; x,y is LL corner + * \param[in] wpix pix width in pixels + * \param[in] hpix pix height in pixels + * \param[in] res of printer; use 0 for default + * \param[in] scale use 1.0 or 0.0 for no scaling + * \param[out] pxpt location of llx in pts + * \param[out] pypt location of lly in pts + * \param[out] pwpt image width in pts + * \param[out] phpt image height in pts + * \return void no arg checking + * + * <pre> + * Notes: + * (1) The image is always scaled, depending on res and scale. + * (2) If no box, the image is centered on the page. + * (3) If there is a box, the image is placed within it. + * </pre> + */ +static void +getScaledParametersPS(BOX *box, + l_int32 wpix, + l_int32 hpix, + l_int32 res, + l_float32 scale, + l_float32 *pxpt, + l_float32 *pypt, + l_float32 *pwpt, + l_float32 *phpt) +{ +l_int32 bx, by, bw, bh; +l_float32 winch, hinch, xinch, yinch, fres; + + if (res == 0) + res = DefaultInputRes; + fres = (l_float32)res; + + /* Allow the PS interpreter to scale the resolution */ + if (scale == 0.0) + scale = 1.0; + if (scale != 1.0) { + fres = (l_float32)res / scale; + res = (l_int32)fres; + } + + /* Limit valid resolution interval */ + if (res < MinRes || res > MaxRes) { + L_WARNING("res %d out of bounds; using default res; no scaling\n", + __func__, res); + res = DefaultInputRes; + fres = (l_float32)res; + } + + if (!box) { /* center on page */ + winch = (l_float32)wpix / fres; + hinch = (l_float32)hpix / fres; + xinch = (8.5f - winch) / 2.f; + yinch = (11.0f - hinch) / 2.f; + } else { + boxGetGeometry(box, &bx, &by, &bw, &bh); + if (bw == 0) + winch = (l_float32)wpix / fres; + else + winch = (l_float32)bw / 1000.f; + if (bh == 0) + hinch = (l_float32)hpix / fres; + else + hinch = (l_float32)bh / 1000.f; + xinch = (l_float32)bx / 1000.f; + yinch = (l_float32)by / 1000.f; + } + + if (xinch < 0) + L_WARNING("left edge < 0.0 inch\n", __func__); + if (xinch + winch > 8.5) + L_WARNING("right edge > 8.5 inch\n", __func__); + if (yinch < 0.0) + L_WARNING("bottom edge < 0.0 inch\n", __func__); + if (yinch + hinch > 11.0) + L_WARNING("top edge > 11.0 inch\n", __func__); + + *pwpt = 72.f * winch; + *phpt = 72.f * hinch; + *pxpt = 72.f * xinch; + *pypt = 72.f * yinch; + return; +} + + +/*! + * \brief convertByteToHexAscii() + * + * \param[in] byteval input byte + * \param[out] pnib1, pnib2 two hex ascii characters + * \return void + */ +static void +convertByteToHexAscii(l_uint8 byteval, + char *pnib1, + char *pnib2) +{ +l_uint8 nib; + + nib = byteval >> 4; + if (nib < 10) + *pnib1 = '0' + nib; + else + *pnib1 = 'a' + (nib - 10); + nib = byteval & 0xf; + if (nib < 10) + *pnib2 = '0' + nib; + else + *pnib2 = 'a' + (nib - 10); + return; +} + + +/*-------------------------------------------------------------* + * For jpeg compressed images * + *-------------------------------------------------------------*/ +/*! + * \brief convertJpegToPSEmbed() + * + * \param[in] filein input jpeg file + * \param[in] fileout output ps file + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This function takes a jpeg file as input and generates a DCT + * compressed, ascii85 encoded PS file, with a bounding box. + * (2) The bounding box is required when a program such as TeX + * (through epsf) places and rescales the image. + * (3) The bounding box is sized for fitting the image to an + * 8.5 x 11.0 inch page. + * </pre> + */ +l_ok +convertJpegToPSEmbed(const char *filein, + const char *fileout) +{ +char *outstr; +l_int32 w, h, nbytes, ret; +l_float32 xpt, ypt, wpt, hpt; +L_COMP_DATA *cid; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + /* Generate the ascii encoded jpeg data */ + if ((cid = l_generateJpegData(filein, 1)) == NULL) + return ERROR_INT("jpeg data not made", __func__, 1); + w = cid->w; + h = cid->h; + + /* Scale for 20 pt boundary and otherwise full filling + * in one direction on 8.5 x 11 inch device */ + xpt = 20.0; + ypt = 20.0; + if (w * 11.0 > h * 8.5) { + wpt = 572.0; /* 612 - 2 * 20 */ + hpt = wpt * (l_float32)h / (l_float32)w; + } else { + hpt = 752.0; /* 792 - 2 * 20 */ + wpt = hpt * (l_float32)w / (l_float32)h; + } + + /* Generate the PS. + * The bounding box information should be inserted (default). */ + outstr = generateJpegPS(NULL, cid, xpt, ypt, wpt, hpt, 1, 1); + l_CIDataDestroy(&cid); + if (!outstr) + return ERROR_INT("outstr not made", __func__, 1); + nbytes = strlen(outstr); + + ret = l_binaryWrite(fileout, "w", outstr, nbytes); + LEPT_FREE(outstr); + if (ret) L_ERROR("ps string not written to file\n", __func__); + return ret; +} + + +/*! + * \brief convertJpegToPS() + * + * \param[in] filein input jpeg file + * \param[in] fileout output ps file + * \param[in] operation "w" for write; "a" for append + * \param[in] x, y location of LL corner of image, in pixels, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] res resolution of the input image, in ppi; + * use 0 for default + * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This is simpler to use than pixWriteStringPS(), and + * it outputs in level 2 PS as compressed DCT (overlaid + * with ascii85 encoding). + * (2) An output file can contain multiple pages, each with + * multiple images. The arguments to convertJpegToPS() + * allow you to control placement of jpeg images on multiple + * pages within a PostScript file. + * (3) For the first image written to a file, use "w", which + * opens for write and clears the file. For all subsequent + * images written to that file, use "a". + * (4) The (x, y) parameters give the LL corner of the image + * relative to the LL corner of the page. They are in + * units of pixels if scale = 1.0. If you use (e.g.) + * scale = 2.0, the image is placed at (2x, 2y) on the page, + * and the image dimensions are also doubled. + * (5) Display vs printed resolution: + * * If your display is 75 ppi and your image was created + * at a resolution of 300 ppi, you can get the image + * to print at the same size as it appears on your display + * by either setting scale = 4.0 or by setting res = 75. + * Both tell the printer to make a 4x enlarged image. + * * If your image is generated at 150 ppi and you use scale = 1, + * it will be rendered such that 150 pixels correspond + * to 72 pts (1 inch on the printer). This function does + * the conversion from pixels (with or without scaling) to + * pts, which are the units that the printer uses. + * * The printer will choose its own resolution to use + * in rendering the image, which will not affect the size + * of the rendered image. That is because the output + * PostScript file describes the geometry in terms of pts, + * which are defined to be 1/72 inch. The printer will + * only see the size of the image in pts, through the + * scale and translate parameters and the affine + * transform (the ImageMatrix) of the image. + * (6) To render multiple images on the same page, set + * endpage = FALSE for each image until you get to the + * last, for which you set endpage = TRUE. This causes the + * "showpage" command to be invoked. Showpage outputs + * the entire page and clears the raster buffer for the + * next page to be added. Without a "showpage", + * subsequent images from the next page will overlay those + * previously put down. + * (7) For multiple pages, increment the page number, starting + * with page 1. This allows PostScript (and PDF) to build + * a page directory, which viewers use for navigation. + * </pre> + */ +l_ok +convertJpegToPS(const char *filein, + const char *fileout, + const char *operation, + l_int32 x, + l_int32 y, + l_int32 res, + l_float32 scale, + l_int32 pageno, + l_int32 endpage) +{ +char *outstr; +l_int32 nbytes; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + if (strcmp(operation, "w") && strcmp(operation, "a")) + return ERROR_INT("operation must be \"w\" or \"a\"", __func__, 1); + + if (convertJpegToPSString(filein, &outstr, &nbytes, x, y, res, scale, + pageno, endpage)) + return ERROR_INT("ps string not made", __func__, 1); + + if (l_binaryWrite(fileout, operation, outstr, nbytes)) { + LEPT_FREE(outstr); + return ERROR_INT("ps string not written to file", __func__, 1); + } + + LEPT_FREE(outstr); + return 0; +} + + +/*! + * \brief convertJpegToPSString() + * + * Generates PS string in jpeg format from jpeg file + * + * \param[in] filein input jpeg file + * \param[out] poutstr PS string + * \param[out] pnbytes number of bytes in PS string + * \param[in] x, y location of LL corner of image, in pixels, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] res resolution of the input image, in ppi; + * use 0 for default + * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) For usage, see convertJpegToPS() + * </pre> + */ +static l_ok +convertJpegToPSString(const char *filein, + char **poutstr, + l_int32 *pnbytes, + l_int32 x, + l_int32 y, + l_int32 res, + l_float32 scale, + l_int32 pageno, + l_int32 endpage) +{ +char *outstr; +l_float32 xpt, ypt, wpt, hpt; +L_COMP_DATA *cid; + + if (!poutstr) + return ERROR_INT("&outstr not defined", __func__, 1); + if (!pnbytes) + return ERROR_INT("&nbytes not defined", __func__, 1); + *poutstr = NULL; + *pnbytes = 0; + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + + /* Generate the ascii encoded jpeg data */ + if ((cid = l_generateJpegData(filein, 1)) == NULL) + return ERROR_INT("jpeg data not made", __func__, 1); + + /* Get scaled location in pts. Guess the input scan resolution + * based on the input parameter %res, the resolution data in + * the pix, and the size of the image. */ + if (scale == 0.0) + scale = 1.0; + if (res <= 0) { + if (cid->res > 0) + res = cid->res; + else + res = DefaultInputRes; + } + + /* Get scaled location in pts */ + if (scale == 0.0) + scale = 1.0; + xpt = scale * x * 72.f / res; + ypt = scale * y * 72.f / res; + wpt = scale * cid->w * 72.f / res; + hpt = scale * cid->h * 72.f / res; + + if (pageno == 0) + pageno = 1; + +#if DEBUG_JPEG + lept_stderr("w = %d, h = %d, bps = %d, spp = %d\n", + cid->w, cid->h, cid->bps, cid->spp); + lept_stderr("comp bytes = %ld, nbytes85 = %ld, ratio = %5.3f\n", + (unsigned long)cid->nbytescomp, (unsigned long)cid->nbytes85, + (l_float32)cid->nbytes85 / (l_float32)cid->nbytescomp); + lept_stderr("xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n", + xpt, ypt, wpt, hpt); +#endif /* DEBUG_JPEG */ + + /* Generate the PS */ + outstr = generateJpegPS(NULL, cid, xpt, ypt, wpt, hpt, pageno, endpage); + l_CIDataDestroy(&cid); + if (!outstr) + return ERROR_INT("outstr not made", __func__, 1); + *poutstr = outstr; + *pnbytes = strlen(outstr); + return 0; +} + + +/*! + * \brief generateJpegPS() + * + * \param[in] filein [optional] input jpeg filename; can be null + * \param[in] cid jpeg compressed image data + * \param[in] xpt, ypt location of LL corner of image, in pts, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] wpt, hpt rendered image size in pts + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page. + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return PS string, or NULL on error + * + * <pre> + * Notes: + * (1) Low-level function. + * </pre> + */ +static char * +generateJpegPS(const char *filein, + L_COMP_DATA *cid, + l_float32 xpt, + l_float32 ypt, + l_float32 wpt, + l_float32 hpt, + l_int32 pageno, + l_int32 endpage) +{ +l_int32 w, h, bps, spp; +char *outstr; +char bigbuf[Bufsize]; +SARRAY *sa; + + if (!cid) + return (char *)ERROR_PTR("jpeg data not defined", __func__, NULL); + w = cid->w; + h = cid->h; + bps = cid->bps; + spp = cid->spp; + + sa = sarrayCreate(50); + sarrayAddString(sa, "%!PS-Adobe-3.0", L_COPY); + sarrayAddString(sa, "%%Creator: leptonica", L_COPY); + if (filein) + snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: %s", filein); + else + snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: Jpeg compressed PS"); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, "%%DocumentData: Clean7Bit", L_COPY); + + if (var_PS_WRITE_BOUNDING_BOX == 1) { + snprintf(bigbuf, sizeof(bigbuf), + "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f", + xpt, ypt, xpt + wpt, ypt + hpt); + sarrayAddString(sa, bigbuf, L_COPY); + } + + sarrayAddString(sa, "%%LanguageLevel: 2", L_COPY); + sarrayAddString(sa, "%%EndComments", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), "%%%%Page: %d %d", pageno, pageno); + sarrayAddString(sa, bigbuf, L_COPY); + + sarrayAddString(sa, "save", L_COPY); + sarrayAddString(sa, + "/RawData currentfile /ASCII85Decode filter def", L_COPY); + sarrayAddString(sa, "/Data RawData << >> /DCTDecode filter def", L_COPY); + + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt); + sarrayAddString(sa, bigbuf, L_COPY); + + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f scale %%set image size in pts", wpt, hpt); + sarrayAddString(sa, bigbuf, L_COPY); + + if (spp == 1) + sarrayAddString(sa, "/DeviceGray setcolorspace", L_COPY); + else if (spp == 3) + sarrayAddString(sa, "/DeviceRGB setcolorspace", L_COPY); + else /*spp == 4 */ + sarrayAddString(sa, "/DeviceCMYK setcolorspace", L_COPY); + + sarrayAddString(sa, "{ << /ImageType 1", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Width %d", w); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Height %d", h); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + " /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, " /DataSource Data", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /BitsPerComponent %d", bps); + sarrayAddString(sa, bigbuf, L_COPY); + + if (spp == 1) + sarrayAddString(sa, " /Decode [0 1]", L_COPY); + else if (spp == 3) + sarrayAddString(sa, " /Decode [0 1 0 1 0 1]", L_COPY); + else /* spp == 4 */ + sarrayAddString(sa, " /Decode [0 1 0 1 0 1 0 1]", L_COPY); + + sarrayAddString(sa, " >> image", L_COPY); + sarrayAddString(sa, " Data closefile", L_COPY); + sarrayAddString(sa, " RawData flushfile", L_COPY); + if (endpage == TRUE) + sarrayAddString(sa, " showpage", L_COPY); + sarrayAddString(sa, " restore", L_COPY); + sarrayAddString(sa, "} exec", L_COPY); + + /* Insert the ascii85 jpeg data; this is now owned by sa */ + sarrayAddString(sa, cid->data85, L_INSERT); + cid->data85 = NULL; /* it has been transferred and destroyed */ + + /* Generate and return the output string */ + outstr = sarrayToString(sa, 1); + sarrayDestroy(&sa); + return outstr; +} + + +/*-------------------------------------------------------------* + * For ccitt g4 compressed images * + *-------------------------------------------------------------*/ +/*! + * \brief convertG4ToPSEmbed() + * + * \param[in] filein input tiff file + * \param[in] fileout output ps file + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This function takes a g4 compressed tif file as input and + * generates a g4 compressed, ascii85 encoded PS file, with + * a bounding box. + * (2) The bounding box is required when a program such as TeX + * (through epsf) places and rescales the image. + * (3) The bounding box is sized for fitting the image to an + * 8.5 x 11.0 inch page. + * (4) We paint this through a mask, over whatever is below. + * </pre> + */ +l_ok +convertG4ToPSEmbed(const char *filein, + const char *fileout) +{ +char *outstr; +l_int32 w, h, nbytes, ret; +l_float32 xpt, ypt, wpt, hpt; +L_COMP_DATA *cid; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + if ((cid = l_generateG4Data(filein, 1)) == NULL) + return ERROR_INT("g4 data not made", __func__, 1); + w = cid->w; + h = cid->h; + + /* Scale for 20 pt boundary and otherwise full filling + * in one direction on 8.5 x 11 inch device */ + xpt = 20.0; + ypt = 20.0; + if (w * 11.0 > h * 8.5) { + wpt = 572.0; /* 612 - 2 * 20 */ + hpt = wpt * (l_float32)h / (l_float32)w; + } else { + hpt = 752.0; /* 792 - 2 * 20 */ + wpt = hpt * (l_float32)w / (l_float32)h; + } + + /* Generate the PS, painting through the image mask. + * The bounding box information should be inserted (default). */ + outstr = generateG4PS(NULL, cid, xpt, ypt, wpt, hpt, 1, 1, 1); + l_CIDataDestroy(&cid); + if (!outstr) + return ERROR_INT("outstr not made", __func__, 1); + nbytes = strlen(outstr); + + ret = l_binaryWrite(fileout, "w", outstr, nbytes); + LEPT_FREE(outstr); + if (ret) L_ERROR("ps string not written to file\n", __func__); + return ret; +} + + +/*! + * \brief convertG4ToPS() + * + * \param[in] filein input tiff g4 file + * \param[in] fileout output ps file + * \param[in] operation "w" for write; "a" for append + * \param[in] x, y location of LL corner of image, in pixels, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] res resolution of the input image, in ppi; typ. values + * are 300 and 600; use 0 for automatic determination + * based on image size + * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page. + * \param[in] maskflag boolean: use TRUE if just painting through fg; + * FALSE if painting both fg and bg. + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) See the usage comments in convertJpegToPS(), some of + * which are repeated here. + * (2) This is a wrapper for tiff g4. The PostScript that + * is generated is expanded by about 5/4 (due to the + * ascii85 encoding. If you convert to pdf (ps2pdf), the + * ascii85 decoder is automatically invoked, so that the + * pdf wrapped g4 file is essentially the same size as + * the original g4 file. It's useful to have the PS + * file ascii85 encoded, because many printers will not + * print binary PS files. + * (3) For the first image written to a file, use "w", which + * opens for write and clears the file. For all subsequent + * images written to that file, use "a". + * (4) To render multiple images on the same page, set + * endpage = FALSE for each image until you get to the + * last, for which you set endpage = TRUE. This causes the + * "showpage" command to be invoked. Showpage outputs + * the entire page and clears the raster buffer for the + * next page to be added. Without a "showpage", + * subsequent images from the next page will overlay those + * previously put down. + * (5) For multiple images to the same page, where you are writing + * both jpeg and tiff-g4, you have two options: + * (a) write the g4 first, as either image (maskflag == FALSE) + * or imagemask (maskflag == TRUE), and then write the + * jpeg over it. + * (b) write the jpeg first and as the last item, write + * the g4 as an imagemask (maskflag == TRUE), to paint + * through the foreground only. + * We have this flexibility with the tiff-g4 because it is 1 bpp. + * (6) For multiple pages, increment the page number, starting + * with page 1. This allows PostScript (and PDF) to build + * a page directory, which viewers use for navigation. + * </pre> + */ +l_ok +convertG4ToPS(const char *filein, + const char *fileout, + const char *operation, + l_int32 x, + l_int32 y, + l_int32 res, + l_float32 scale, + l_int32 pageno, + l_int32 maskflag, + l_int32 endpage) +{ +char *outstr; +l_int32 nbytes, ret; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + if (strcmp(operation, "w") && strcmp(operation, "a")) + return ERROR_INT("operation must be \"w\" or \"a\"", __func__, 1); + + if (convertG4ToPSString(filein, &outstr, &nbytes, x, y, res, scale, + pageno, maskflag, endpage)) + return ERROR_INT("ps string not made", __func__, 1); + + ret = l_binaryWrite(fileout, operation, outstr, nbytes); + LEPT_FREE(outstr); + if (ret) + return ERROR_INT("ps string not written to file", __func__, 1); + return 0; +} + + +/*! + * \brief convertG4ToPSString() + * + * \param[in] filein input tiff g4 file + * \param[out] poutstr PS string + * \param[out] pnbytes number of bytes in PS string + * \param[in] x, y location of LL corner of image, in pixels, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] res resolution of the input image, in ppi; typ. values + * are 300 and 600; use 0 for automatic determination + * based on image size + * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page. + * \param[in] maskflag boolean: use TRUE if just painting through fg; + * FALSE if painting both fg and bg. + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) Generates PS string in G4 compressed tiff format from G4 tiff file. + * (2) For usage, see convertG4ToPS(). + * </pre> + */ +static l_ok +convertG4ToPSString(const char *filein, + char **poutstr, + l_int32 *pnbytes, + l_int32 x, + l_int32 y, + l_int32 res, + l_float32 scale, + l_int32 pageno, + l_int32 maskflag, + l_int32 endpage) +{ +char *outstr; +l_float32 xpt, ypt, wpt, hpt; +L_COMP_DATA *cid; + + if (!poutstr) + return ERROR_INT("&outstr not defined", __func__, 1); + if (!pnbytes) + return ERROR_INT("&nbytes not defined", __func__, 1); + *poutstr = NULL; + *pnbytes = 0; + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + + if ((cid = l_generateG4Data(filein, 1)) == NULL) + return ERROR_INT("g4 data not made", __func__, 1); + + /* Get scaled location in pts. Guess the input scan resolution + * based on the input parameter %res, the resolution data in + * the pix, and the size of the image. */ + if (scale == 0.0) + scale = 1.0; + if (res <= 0) { + if (cid->res > 0) { + res = cid->res; + } else { + if (cid->h <= 3509) /* A4 height at 300 ppi */ + res = 300; + else + res = 600; + } + } + xpt = scale * x * 72.f / res; + ypt = scale * y * 72.f / res; + wpt = scale * cid->w * 72.f / res; + hpt = scale * cid->h * 72.f / res; + + if (pageno == 0) + pageno = 1; + +#if DEBUG_G4 + lept_stderr("w = %d, h = %d, minisblack = %d\n", + cid->w, cid->h, cid->minisblack); + lept_stderr("comp bytes = %ld, nbytes85 = %ld\n", + (unsigned long)cid->nbytescomp, (unsigned long)cid->nbytes85); + lept_stderr("xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n", + xpt, ypt, wpt, hpt); +#endif /* DEBUG_G4 */ + + /* Generate the PS */ + outstr = generateG4PS(NULL, cid, xpt, ypt, wpt, hpt, + maskflag, pageno, endpage); + l_CIDataDestroy(&cid); + if (!outstr) + return ERROR_INT("outstr not made", __func__, 1); + *poutstr = outstr; + *pnbytes = strlen(outstr); + return 0; +} + + +/*! + * \brief generateG4PS() + * + * \param[in] filein [optional] input tiff g4 file; can be null + * \param[in] cid g4 compressed image data + * \param[in] xpt, ypt location of LL corner of image, in pts, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] wpt, hpt rendered image size in pts + * \param[in] maskflag boolean: use TRUE if just painting through fg; + * FALSE if painting both fg and bg. + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page. + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return PS string, or NULL on error + * + * <pre> + * Notes: + * (1) Low-level function. + * </pre> + */ +static char * +generateG4PS(const char *filein, + L_COMP_DATA *cid, + l_float32 xpt, + l_float32 ypt, + l_float32 wpt, + l_float32 hpt, + l_int32 maskflag, + l_int32 pageno, + l_int32 endpage) +{ +l_int32 w, h; +char *outstr; +char bigbuf[Bufsize]; +SARRAY *sa; + + if (!cid) + return (char *)ERROR_PTR("g4 data not defined", __func__, NULL); + w = cid->w; + h = cid->h; + + sa = sarrayCreate(50); + sarrayAddString(sa, "%!PS-Adobe-3.0", L_COPY); + sarrayAddString(sa, "%%Creator: leptonica", L_COPY); + if (filein) + snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: %s", filein); + else + snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: G4 compressed PS"); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, "%%DocumentData: Clean7Bit", L_COPY); + + if (var_PS_WRITE_BOUNDING_BOX == 1) { + snprintf(bigbuf, sizeof(bigbuf), + "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f", + xpt, ypt, xpt + wpt, ypt + hpt); + sarrayAddString(sa, bigbuf, L_COPY); + } + + sarrayAddString(sa, "%%LanguageLevel: 2", L_COPY); + sarrayAddString(sa, "%%EndComments", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), "%%%%Page: %d %d", pageno, pageno); + sarrayAddString(sa, bigbuf, L_COPY); + + sarrayAddString(sa, "save", L_COPY); + sarrayAddString(sa, "100 dict begin", L_COPY); + + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt); + sarrayAddString(sa, bigbuf, L_COPY); + + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f scale %%set image size in pts", wpt, hpt); + sarrayAddString(sa, bigbuf, L_COPY); + + sarrayAddString(sa, "/DeviceGray setcolorspace", L_COPY); + + sarrayAddString(sa, "{", L_COPY); + sarrayAddString(sa, + " /RawData currentfile /ASCII85Decode filter def", L_COPY); + sarrayAddString(sa, " << ", L_COPY); + sarrayAddString(sa, " /ImageType 1", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Width %d", w); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Height %d", h); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + " /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, " /BitsPerComponent 1", L_COPY); + sarrayAddString(sa, " /Interpolate true", L_COPY); + if (cid->minisblack) + sarrayAddString(sa, " /Decode [1 0]", L_COPY); + else /* miniswhite; typical for 1 bpp */ + sarrayAddString(sa, " /Decode [0 1]", L_COPY); + sarrayAddString(sa, " /DataSource RawData", L_COPY); + sarrayAddString(sa, " <<", L_COPY); + sarrayAddString(sa, " /K -1", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Columns %d", w); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Rows %d", h); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, " >> /CCITTFaxDecode filter", L_COPY); + if (maskflag == TRUE) /* just paint through the fg */ + sarrayAddString(sa, " >> imagemask", L_COPY); + else /* Paint full image */ + sarrayAddString(sa, " >> image", L_COPY); + sarrayAddString(sa, " RawData flushfile", L_COPY); + if (endpage == TRUE) + sarrayAddString(sa, " showpage", L_COPY); + sarrayAddString(sa, "}", L_COPY); + + sarrayAddString(sa, "%%BeginData:", L_COPY); + sarrayAddString(sa, "exec", L_COPY); + + /* Insert the ascii85 ccittg4 data; this is now owned by sa */ + sarrayAddString(sa, cid->data85, L_INSERT); + + /* Concat the trailing data */ + sarrayAddString(sa, "%%EndData", L_COPY); + sarrayAddString(sa, "end", L_COPY); + sarrayAddString(sa, "restore", L_COPY); + + outstr = sarrayToString(sa, 1); + sarrayDestroy(&sa); + cid->data85 = NULL; /* it has been transferred and destroyed */ + return outstr; +} + + +/*-------------------------------------------------------------* + * For tiff multipage files * + *-------------------------------------------------------------*/ +/*! + * \brief convertTiffMultipageToPS() + * + * \param[in] filein input tiff multipage file + * \param[in] fileout output ps file + * \param[in] fillfract factor for filling 8.5 x 11 inch page; + * use 0.0 for DefaultFillFraction + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This converts a multipage tiff file of binary page images + * into a ccitt g4 compressed PS file. + * (2) If the images are generated from a standard resolution fax, + * the vertical resolution is doubled to give a normal-looking + * aspect ratio. + * </pre> + */ +l_ok +convertTiffMultipageToPS(const char *filein, + const char *fileout, + l_float32 fillfract) +{ +char *tempfile; +l_int32 i, npages, w, h, istiff; +l_float32 scale; +PIX *pix, *pixs; +FILE *fp; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + if ((fp = fopenReadStream(filein)) == NULL) + return ERROR_INT_1("file not found", filein, __func__, 1); + istiff = fileFormatIsTiff(fp); + if (!istiff) { + fclose(fp); + return ERROR_INT_1("file not tiff format", filein, __func__, 1); + } + tiffGetCount(fp, &npages); + fclose(fp); + + if (fillfract == 0.0) + fillfract = DefaultFillFraction; + + for (i = 0; i < npages; i++) { + if ((pix = pixReadTiff(filein, i)) == NULL) + return ERROR_INT_1("pix not made", filein, __func__, 1); + + pixGetDimensions(pix, &w, &h, NULL); + if (w == 1728 && h < w) /* it's a std res fax */ + pixs = pixScale(pix, 1.0, 2.0); + else + pixs = pixClone(pix); + + tempfile = l_makeTempFilename(); + pixWrite(tempfile, pixs, IFF_TIFF_G4); + scale = L_MIN(fillfract * 2550 / w, fillfract * 3300 / h); + if (i == 0) + convertG4ToPS(tempfile, fileout, "w", 0, 0, 300, scale, + i + 1, FALSE, TRUE); + else + convertG4ToPS(tempfile, fileout, "a", 0, 0, 300, scale, + i + 1, FALSE, TRUE); + lept_rmfile(tempfile); + LEPT_FREE(tempfile); + pixDestroy(&pix); + pixDestroy(&pixs); + } + + return 0; +} + + +/*---------------------------------------------------------------------* + * For flate (gzip) compressed images (e.g., png) * + *---------------------------------------------------------------------*/ +/*! + * \brief convertFlateToPSEmbed() + * + * \param[in] filein input file -- any format + * \param[in] fileout output ps file + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This function takes any image file as input and generates a + * flate-compressed, ascii85 encoded PS file, with a bounding box. + * (2) The bounding box is required when a program such as TeX + * (through epsf) places and rescales the image. + * (3) The bounding box is sized for fitting the image to an + * 8.5 x 11.0 inch page. + * </pre> + */ +l_ok +convertFlateToPSEmbed(const char *filein, + const char *fileout) +{ +char *outstr; +l_int32 w, h, nbytes, ret; +l_float32 xpt, ypt, wpt, hpt; +L_COMP_DATA *cid; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + + if ((cid = l_generateFlateData(filein, 1)) == NULL) + return ERROR_INT("flate data not made", __func__, 1); + w = cid->w; + h = cid->h; + + /* Scale for 20 pt boundary and otherwise full filling + * in one direction on 8.5 x 11 inch device */ + xpt = 20.0; + ypt = 20.0; + if (w * 11.0 > h * 8.5) { + wpt = 572.0; /* 612 - 2 * 20 */ + hpt = wpt * (l_float32)h / (l_float32)w; + } else { + hpt = 752.0; /* 792 - 2 * 20 */ + wpt = hpt * (l_float32)w / (l_float32)h; + } + + /* Generate the PS. + * The bounding box information should be inserted (default). */ + outstr = generateFlatePS(NULL, cid, xpt, ypt, wpt, hpt, 1, 1); + l_CIDataDestroy(&cid); + if (!outstr) + return ERROR_INT("outstr not made", __func__, 1); + nbytes = strlen(outstr); + + ret = l_binaryWrite(fileout, "w", outstr, nbytes); + LEPT_FREE(outstr); + if (ret) L_ERROR("ps string not written to file\n", __func__); + return ret; +} + + +/*! + * \brief convertFlateToPS() + * + * \param[in] filein input file -- any format + * \param[in] fileout output ps file + * \param[in] operation "w" for write; "a" for append + * \param[in] x, y location of LL corner of image, in pixels, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] res resolution of the input image, in ppi; + * use 0 for default + * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page. + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) This outputs level 3 PS as flate compressed (overlaid + * with ascii85 encoding). + * (2) An output file can contain multiple pages, each with + * multiple images. The arguments to convertFlateToPS() + * allow you to control placement of png images on multiple + * pages within a PostScript file. + * (3) For the first image written to a file, use "w", which + * opens for write and clears the file. For all subsequent + * images written to that file, use "a". + * (4) The (x, y) parameters give the LL corner of the image + * relative to the LL corner of the page. They are in + * units of pixels if scale = 1.0. If you use (e.g.) + * scale = 2.0, the image is placed at (2x, 2y) on the page, + * and the image dimensions are also doubled. + * (5) Display vs printed resolution: + * * If your display is 75 ppi and your image was created + * at a resolution of 300 ppi, you can get the image + * to print at the same size as it appears on your display + * by either setting scale = 4.0 or by setting res = 75. + * Both tell the printer to make a 4x enlarged image. + * * If your image is generated at 150 ppi and you use scale = 1, + * it will be rendered such that 150 pixels correspond + * to 72 pts (1 inch on the printer). This function does + * the conversion from pixels (with or without scaling) to + * pts, which are the units that the printer uses. + * * The printer will choose its own resolution to use + * in rendering the image, which will not affect the size + * of the rendered image. That is because the output + * PostScript file describes the geometry in terms of pts, + * which are defined to be 1/72 inch. The printer will + * only see the size of the image in pts, through the + * scale and translate parameters and the affine + * transform (the ImageMatrix) of the image. + * (6) To render multiple images on the same page, set + * endpage = FALSE for each image until you get to the + * last, for which you set endpage = TRUE. This causes the + * "showpage" command to be invoked. Showpage outputs + * the entire page and clears the raster buffer for the + * next page to be added. Without a "showpage", + * subsequent images from the next page will overlay those + * previously put down. + * (7) For multiple pages, increment the page number, starting + * with page 1. This allows PostScript (and PDF) to build + * a page directory, which viewers use for navigation. + * </pre> + */ +l_ok +convertFlateToPS(const char *filein, + const char *fileout, + const char *operation, + l_int32 x, + l_int32 y, + l_int32 res, + l_float32 scale, + l_int32 pageno, + l_int32 endpage) +{ +char *outstr; +l_int32 nbytes, ret; + + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + if (!fileout) + return ERROR_INT("fileout not defined", __func__, 1); + if (strcmp(operation, "w") && strcmp(operation, "a")) + return ERROR_INT("operation must be \"w\" or \"a\"", __func__, 1); + + if (convertFlateToPSString(filein, &outstr, &nbytes, x, y, res, scale, + pageno, endpage)) + return ERROR_INT("ps string not made", __func__, 1); + + ret = l_binaryWrite(fileout, operation, outstr, nbytes); + LEPT_FREE(outstr); + if (ret) L_ERROR("ps string not written to file\n", __func__); + return ret; +} + + +/*! + * \brief convertFlateToPSString() + * + * Generates level 3 PS string in flate compressed format. + * + * \param[in] filein input image file + * \param[out] poutstr PS string + * \param[out] pnbytes number of bytes in PS string + * \param[in] x, y location of LL corner of image, in pixels, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] res resolution of the input image, in ppi; + * use 0 for default + * \param[in] scale scaling by printer; use 0.0 or 1.0 for no scaling + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page. + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) The returned PS character array is a null-terminated + * ascii string. All the raster data is ascii85 encoded, so + * there are no null bytes embedded in it. + * (2) The raster encoding is made with gzip, the same as that + * in a png file that is compressed without prediction. + * The raster data itself is 25% larger than that in the + * binary form, due to the ascii85 encoding. + * + * Usage: See convertFlateToPS() + * </pre> + */ +static l_ok +convertFlateToPSString(const char *filein, + char **poutstr, + l_int32 *pnbytes, + l_int32 x, + l_int32 y, + l_int32 res, + l_float32 scale, + l_int32 pageno, + l_int32 endpage) +{ +char *outstr; +l_float32 xpt, ypt, wpt, hpt; +L_COMP_DATA *cid; + + if (!poutstr) + return ERROR_INT("&outstr not defined", __func__, 1); + if (!pnbytes) + return ERROR_INT("&nbytes not defined", __func__, 1); + *pnbytes = 0; + *poutstr = NULL; + if (!filein) + return ERROR_INT("filein not defined", __func__, 1); + + if ((cid = l_generateFlateData(filein, 1)) == NULL) + return ERROR_INT("flate data not made", __func__, 1); + + /* Get scaled location in pts. Guess the input scan resolution + * based on the input parameter %res, the resolution data in + * the pix, and the size of the image. */ + if (scale == 0.0) + scale = 1.0; + if (res <= 0) { + if (cid->res > 0) + res = cid->res; + else + res = DefaultInputRes; + } + xpt = scale * x * 72.f / res; + ypt = scale * y * 72.f / res; + wpt = scale * cid->w * 72.f / res; + hpt = scale * cid->h * 72.f / res; + + if (pageno == 0) + pageno = 1; + +#if DEBUG_FLATE + lept_stderr("w = %d, h = %d, bps = %d, spp = %d\n", + cid->w, cid->h, cid->bps, cid->spp); + lept_stderr("uncomp bytes = %ld, comp bytes = %ld, nbytes85 = %ld\n", + (unsigned long)cid->nbytes, (unsigned long)cid->nbytescomp, + (unsigned long)cid->nbytes85); + lept_stderr("xpt = %7.2f, ypt = %7.2f, wpt = %7.2f, hpt = %7.2f\n", + xpt, ypt, wpt, hpt); +#endif /* DEBUG_FLATE */ + + /* Generate the PS */ + outstr = generateFlatePS(NULL, cid, xpt, ypt, wpt, hpt, pageno, endpage); + l_CIDataDestroy(&cid); + if (!outstr) + return ERROR_INT("outstr not made", __func__, 1); + *poutstr = outstr; + *pnbytes = strlen(outstr); + return 0; +} + + +/*! + * \brief generateFlatePS() + * + * \param[in] filein [optional] input filename; can be null + * \param[in] cid flate compressed image data + * \param[in] xpt, ypt location of LL corner of image, in pts, relative + * to the PostScript origin (0,0) at the LL corner + * of the page + * \param[in] wpt, hpt rendered image size in pts + * \param[in] pageno page number; must start with 1; you can use 0 + * if there is only one page + * \param[in] endpage boolean: use TRUE if this is the last image to be + * added to the page; FALSE otherwise + * \return PS string, or NULL on error + */ +static char * +generateFlatePS(const char *filein, + L_COMP_DATA *cid, + l_float32 xpt, + l_float32 ypt, + l_float32 wpt, + l_float32 hpt, + l_int32 pageno, + l_int32 endpage) +{ +l_int32 w, h, bps, spp; +char *outstr; +char bigbuf[Bufsize]; +SARRAY *sa; + + if (!cid) + return (char *)ERROR_PTR("flate data not defined", __func__, NULL); + w = cid->w; + h = cid->h; + bps = cid->bps; + spp = cid->spp; + + sa = sarrayCreate(50); + sarrayAddString(sa, "%!PS-Adobe-3.0 EPSF-3.0", L_COPY); + sarrayAddString(sa, "%%Creator: leptonica", L_COPY); + if (filein) + snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: %s", filein); + else + snprintf(bigbuf, sizeof(bigbuf), "%%%%Title: Flate compressed PS"); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, "%%DocumentData: Clean7Bit", L_COPY); + + if (var_PS_WRITE_BOUNDING_BOX == 1) { + snprintf(bigbuf, sizeof(bigbuf), + "%%%%BoundingBox: %7.2f %7.2f %7.2f %7.2f", + xpt, ypt, xpt + wpt, ypt + hpt); + sarrayAddString(sa, bigbuf, L_COPY); + } + + sarrayAddString(sa, "%%LanguageLevel: 3", L_COPY); + sarrayAddString(sa, "%%EndComments", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), "%%%%Page: %d %d", pageno, pageno); + sarrayAddString(sa, bigbuf, L_COPY); + + sarrayAddString(sa, "save", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f translate %%set image origin in pts", xpt, ypt); + sarrayAddString(sa, bigbuf, L_COPY); + + snprintf(bigbuf, sizeof(bigbuf), + "%7.2f %7.2f scale %%set image size in pts", wpt, hpt); + sarrayAddString(sa, bigbuf, L_COPY); + + /* If there is a colormap, add the data; it is now owned by sa */ + if (cid->cmapdata85) { + snprintf(bigbuf, sizeof(bigbuf), + "[ /Indexed /DeviceRGB %d %%set colormap type/size", + cid->ncolors - 1); + sarrayAddString(sa, bigbuf, L_COPY); + sarrayAddString(sa, " <~", L_COPY); + sarrayAddString(sa, cid->cmapdata85, L_INSERT); + sarrayAddString(sa, " ] setcolorspace", L_COPY); + } else if (spp == 1) { + sarrayAddString(sa, "/DeviceGray setcolorspace", L_COPY); + } else { /* spp == 3 */ + sarrayAddString(sa, "/DeviceRGB setcolorspace", L_COPY); + } + + sarrayAddString(sa, + "/RawData currentfile /ASCII85Decode filter def", L_COPY); + sarrayAddString(sa, + "/Data RawData << >> /FlateDecode filter def", L_COPY); + + sarrayAddString(sa, "{ << /ImageType 1", L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Width %d", w); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /Height %d", h); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), " /BitsPerComponent %d", bps); + sarrayAddString(sa, bigbuf, L_COPY); + snprintf(bigbuf, sizeof(bigbuf), + " /ImageMatrix [ %d 0 0 %d 0 %d ]", w, -h, h); + sarrayAddString(sa, bigbuf, L_COPY); + + if (cid->cmapdata85) { + sarrayAddString(sa, " /Decode [0 255]", L_COPY); + } else if (spp == 1) { + if (bps == 1) /* miniswhite photometry */ + sarrayAddString(sa, " /Decode [1 0]", L_COPY); + else /* bps > 1 */ + sarrayAddString(sa, " /Decode [0 1]", L_COPY); + } else { /* spp == 3 */ + sarrayAddString(sa, " /Decode [0 1 0 1 0 1]", L_COPY); + } + + sarrayAddString(sa, " /DataSource Data", L_COPY); + sarrayAddString(sa, " >> image", L_COPY); + sarrayAddString(sa, " Data closefile", L_COPY); + sarrayAddString(sa, " RawData flushfile", L_COPY); + if (endpage == TRUE) + sarrayAddString(sa, " showpage", L_COPY); + sarrayAddString(sa, " restore", L_COPY); + sarrayAddString(sa, "} exec", L_COPY); + + /* Insert the ascii85 gzipped data; this is now owned by sa */ + sarrayAddString(sa, cid->data85, L_INSERT); + + /* Generate and return the output string */ + outstr = sarrayToString(sa, 1); + sarrayDestroy(&sa); + cid->cmapdata85 = NULL; /* it has been transferred to sa and destroyed */ + cid->data85 = NULL; /* it has been transferred to sa and destroyed */ + return outstr; +} + + +/*---------------------------------------------------------------------* + * Write to memory * + *---------------------------------------------------------------------*/ +/*! + * \brief pixWriteMemPS() + * + * \param[out] pdata data of tiff compressed image + * \param[out] psize size of returned data + * \param[in] pix + * \param[in] box [optional] + * \param[in] res can use 0 for default of 300 ppi + * \param[in] scale to prevent scaling, use either 1.0 or 0.0 + * \return 0 if OK, 1 on error + * + * <pre> + * Notes: + * (1) See pixWriteStringPS() for usage. + * (2) This is just a wrapper for pixWriteStringPS(), which + * writes uncompressed image data to memory. + * </pre> + */ +l_ok +pixWriteMemPS(l_uint8 **pdata, + size_t *psize, + PIX *pix, + BOX *box, + l_int32 res, + l_float32 scale) +{ + if (!pdata) + return ERROR_INT("&data not defined", __func__, 1 ); + if (!psize) + return ERROR_INT("&size not defined", __func__, 1 ); + if (!pix) + return ERROR_INT("&pix not defined", __func__, 1 ); + + *pdata = (l_uint8 *)pixWriteStringPS(pix, box, res, scale); + *psize = strlen((char *)(*pdata)); + return 0; +} + + +/*-------------------------------------------------------------* + * Converting resolution * + *-------------------------------------------------------------*/ +/*! + * \brief getResLetterPage() + * + * \param[in] w image width, pixels + * \param[in] h image height, pixels + * \param[in] fillfract fraction in linear dimension of full page, + * not to be exceeded; use 0 for default + * \return resolution + */ +l_int32 +getResLetterPage(l_int32 w, + l_int32 h, + l_float32 fillfract) +{ +l_int32 resw, resh, res; + + if (fillfract == 0.0) + fillfract = DefaultFillFraction; + resw = (l_int32)((w * 72.) / (LetterWidth * fillfract)); + resh = (l_int32)((h * 72.) / (LetterHeight * fillfract)); + res = L_MAX(resw, resh); + return res; +} + + +/*! + * \brief getResA4Page() + * + * \param[in] w image width, pixels + * \param[in] h image height, pixels + * \param[in] fillfract fraction in linear dimension of full page, + * not to be exceeded; use 0 for default + * \return resolution + */ +l_int32 +getResA4Page(l_int32 w, + l_int32 h, + l_float32 fillfract) +{ +l_int32 resw, resh, res; + + if (fillfract == 0.0) + fillfract = DefaultFillFraction; + resw = (l_int32)((w * 72.) / (A4Width * fillfract)); + resh = (l_int32)((h * 72.) / (A4Height * fillfract)); + res = L_MAX(resw, resh); + return res; +} + + +/*-------------------------------------------------------------* + * Setting flag for writing bounding box hint * + *-------------------------------------------------------------*/ +void +l_psWriteBoundingBox(l_int32 flag) +{ + var_PS_WRITE_BOUNDING_BOX = flag; +} + + +/* --------------------------------------------*/ +#endif /* USE_PSIO */ +/* --------------------------------------------*/
