/*====================================================================* - 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 textops.c *
 *
 *    Font layout
 *       PIX             *pixAddSingleTextblock()
 *       PIX             *pixAddTextlines()
 *       l_int32          pixSetTextblock()
 *       l_int32          pixSetTextline()
 *       PIXA            *pixaAddTextNumber()
 *       PIXA            *pixaAddTextlines()
 *       l_int32          pixaAddPixWithText()
 *
 *    Text size estimation and partitioning
 *       SARRAY          *bmfGetLineStrings()
 *       NUMA            *bmfGetWordWidths()
 *       l_int32          bmfGetStringWidth()
 *
 *    Text splitting
 *       SARRAY          *splitStringToParagraphs()
 *       static l_int32   stringAllWhitespace()
 *       static l_int32   stringLeadingWhitespace()
 *
 *    This is a simple utility to put text on images.  One font and style
 *    is provided, with a variety of pt sizes.  For example, to put a
 *    line of green 10 pt text on an image, with the beginning baseline
 *    at (50, 50):
 *        L_Bmf  *bmf = bmfCreate(NULL, 10);
 *        const char *textstr = "This is a funny cat";
 *        pixSetTextline(pixs, bmf, textstr, 0x00ff0000, 50, 50, NULL, NULL);
 *
 *    The simplest interfaces for adding text to an image are
 *    pixAddTextlines() and pixAddSingleTextblock().
 *    For example, to add the same text in red, centered, below the image:
 *        Pix *pixd = pixAddTextlines(pixs, bmf, textstr, 0xff000000,
 *                                    L_ADD_BELOW);  // red text
 *
 *    To add text to all pix in a pixa, generating a new pixa, use
 *    either an sarray to hold the strings for each pix, or use the
 *    strings in the text field of each pix; e.g.,
 *        Pixa *pixa2 = pixaAddTextlines(pixa1, bmf, sa, 0x0000ff00,
 *                                    L_ADD_LEFT);  // blue text
 *        Pixa *pixa2 = pixaAddTextlines(pixa1, bmf, NULL, 0x00ff0000,
 *                                    L_ADD_RIGHT);  // green text
 * 
*/ #ifdef HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ #include #include "allheaders.h" static l_int32 stringAllWhitespace(char *textstr, l_int32 *pval); static l_int32 stringLeadingWhitespace(char *textstr, l_int32 *pval); /*---------------------------------------------------------------------* * Font layout * *---------------------------------------------------------------------*/ /*! * \brief pixAddSingleTextblock() * * \param[in] pixs input pix; colormap ok * \param[in] bmf bitmap font data * \param[in] textstr [optional] text string to be added * \param[in] val color to set the text * \param[in] location L_ADD_ABOVE, L_ADD_AT_TOP, * L_ADD_AT_BOT, L_ADD_BELOW * \param[out] poverflow [optional] 1 if text overflows allocated * region and is clipped; 0 otherwise * \return pixd new pix with rendered text, or either a copy, * or NULL on error * *
 * Notes:
 *      (1) This function paints a set of lines of text over an image.
 *          If %location is L_ADD_ABOVE or L_ADD_BELOW, the pix size
 *          is expanded with a border and rendered over the border.
 *      (2) %val is the pixel value to be painted through the font mask.
 *          It should be chosen to agree with the depth of pixs.
 *          If it is out of bounds, an intermediate value is chosen.
 *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
 *          hex representation of the red intensity, etc.
 *      (3) If textstr == NULL, use the text field in the pix.
 *      (4) If there is a colormap, this does the best it can to use
 *          the requested color, or something similar to it.
 *      (5) Typical usage is for labelling a pix with some text data.
 * 
*/ PIX * pixAddSingleTextblock(PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location, l_int32 *poverflow) { char *linestr; l_int32 w, h, d, i, y, xstart, ystart, extra, spacer, rval, gval, bval; l_int32 nlines, htext, ovf, overflow, offset, index; l_uint32 textcolor; PIX *pixd; PIXCMAP *cmap, *cmapd; SARRAY *salines; PROCNAME("pixAddSingleTextblock"); if (poverflow) *poverflow = 0; if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (location != L_ADD_ABOVE && location != L_ADD_AT_TOP && location != L_ADD_AT_BOT && location != L_ADD_BELOW) return (PIX *)ERROR_PTR("invalid location", procName, NULL); if (!bmf) { L_ERROR("no bitmap fonts; returning a copy\n", procName); return pixCopy(NULL, pixs); } if (!textstr) textstr = pixGetText(pixs); if (!textstr) { L_WARNING("no textstring defined; returning a copy\n", procName); return pixCopy(NULL, pixs); } /* Make sure the "color" value for the text will work * for the pix. If the pix is not colormapped and the * value is out of range, set it to mid-range. */ pixGetDimensions(pixs, &w, &h, &d); cmap = pixGetColormap(pixs); if (d == 1 && val > 1) val = 1; else if (d == 2 && val > 3 && !cmap) val = 2; else if (d == 4 && val > 15 && !cmap) val = 8; else if (d == 8 && val > 0xff && !cmap) val = 128; else if (d == 16 && val > 0xffff) val = 0x8000; else if (d == 32 && val < 256) val = 0x80808000; xstart = (l_int32)(0.1 * w); salines = bmfGetLineStrings(bmf, textstr, w - 2 * xstart, 0, &htext); if (!salines) return (PIX *)ERROR_PTR("line string sa not made", procName, NULL); nlines = sarrayGetCount(salines); /* Add white border if required */ spacer = 10; /* pixels away from image boundary or added border */ if (location == L_ADD_ABOVE || location == L_ADD_BELOW) { extra = htext + 2 * spacer; pixd = pixCreate(w, h + extra, d); pixCopyColormap(pixd, pixs); pixCopyResolution(pixd, pixs); pixCopyText(pixd, pixs); pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE); if (location == L_ADD_ABOVE) pixRasterop(pixd, 0, extra, w, h, PIX_SRC, pixs, 0, 0); else /* add below */ pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); } else { pixd = pixCopy(NULL, pixs); } cmapd = pixGetColormap(pixd); /* bmf->baselinetab[93] is the approximate distance from * the top of the tallest character to the baseline. 93 was chosen * at random, as all the baselines are essentially equal for * each character in a font. */ offset = bmf->baselinetab[93]; if (location == L_ADD_ABOVE || location == L_ADD_AT_TOP) ystart = offset + spacer; else if (location == L_ADD_AT_BOT) ystart = h - htext - spacer + offset; else /* add below */ ystart = h + offset + spacer; /* If cmapped, add the color if necessary to the cmap. If the * cmap is full, use the nearest color to the requested color. */ if (cmapd) { extractRGBValues(val, &rval, &gval, &bval); pixcmapAddNearestColor(cmapd, rval, gval, bval, &index); pixcmapGetColor(cmapd, index, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, &textcolor); } else { textcolor = val; } /* Keep track of overflow condition on line width */ overflow = 0; for (i = 0, y = ystart; i < nlines; i++) { linestr = sarrayGetString(salines, i, L_NOCOPY); pixSetTextline(pixd, bmf, linestr, textcolor, xstart, y, NULL, &ovf); y += bmf->lineheight + bmf->vertlinesep; if (ovf) overflow = 1; } /* Also consider vertical overflow where there is too much text to * fit inside the image: the cases L_ADD_AT_TOP and L_ADD_AT_BOT. * The text requires a total of htext + 2 * spacer vertical pixels. */ if (location == L_ADD_AT_TOP || location == L_ADD_AT_BOT) { if (h < htext + 2 * spacer) overflow = 1; } if (poverflow) *poverflow = overflow; sarrayDestroy(&salines); return pixd; } /*! * \brief pixAddTextlines() * * \param[in] pixs input pix; colormap ok * \param[in] bmf bitmap font data * \param[in] textstr [optional] text string to be added * \param[in] val color to set the text * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT * \return pixd new pix with rendered text, or either a copy, * or NULL on error * *
 * Notes:
 *      (1) This function expands an image as required to paint one or
 *          more lines of text adjacent to the image.  If %bmf == NULL,
 *          this returns a copy.  If above or below, the lines are
 *          centered with respect to the image; if left or right, they
 *          are left justified.
 *      (2) %val is the pixel value to be painted through the font mask.
 *          It should be chosen to agree with the depth of pixs.
 *          If it is out of bounds, an intermediate value is chosen.
 *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
 *          hex representation of the red intensity, etc.
 *      (3) If textstr == NULL, use the text field in the pix.  The
 *          text field contains one or most "lines" of text, where newlines
 *          are used as line separators.
 *      (4) If there is a colormap, this does the best it can to use
 *          the requested color, or something similar to it.
 *      (5) Typical usage is for labelling a pix with some text data.
 * 
*/ PIX * pixAddTextlines(PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location) { char *str; l_int32 i, w, h, d, rval, gval, bval, index; l_int32 wline, wtext, htext, wadd, hadd, spacer, hbaseline, nlines; l_uint32 textcolor; PIX *pixd; PIXCMAP *cmap, *cmapd; SARRAY *sa; PROCNAME("pixAddTextlines"); if (!pixs) return (PIX *)ERROR_PTR("pixs not defined", procName, NULL); if (location != L_ADD_ABOVE && location != L_ADD_BELOW && location != L_ADD_LEFT && location != L_ADD_RIGHT) return (PIX *)ERROR_PTR("invalid location", procName, NULL); if (!bmf) { L_ERROR("no bitmap fonts; returning a copy\n", procName); return pixCopy(NULL, pixs); } if (!textstr) { textstr = pixGetText(pixs); if (!textstr) { L_WARNING("no textstring defined; returning a copy\n", procName); return pixCopy(NULL, pixs); } } /* Make sure the "color" value for the text will work * for the pix. If the pix is not colormapped and the * value is out of range, set it to mid-range. */ pixGetDimensions(pixs, &w, &h, &d); cmap = pixGetColormap(pixs); if (d == 1 && val > 1) val = 1; else if (d == 2 && val > 3 && !cmap) val = 2; else if (d == 4 && val > 15 && !cmap) val = 8; else if (d == 8 && val > 0xff && !cmap) val = 128; else if (d == 16 && val > 0xffff) val = 0x8000; else if (d == 32 && val < 256) val = 0x80808000; /* Get the text in each line */ sa = sarrayCreateLinesFromString(textstr, 0); nlines = sarrayGetCount(sa); /* Get the necessary text size */ wtext = 0; for (i = 0; i < nlines; i++) { str = sarrayGetString(sa, i, L_NOCOPY); bmfGetStringWidth(bmf, str, &wline); if (wline > wtext) wtext = wline; } hbaseline = bmf->baselinetab[93]; htext = 1.5 * hbaseline * nlines; /* Add white border */ spacer = 10; /* pixels away from the added border */ if (location == L_ADD_ABOVE || location == L_ADD_BELOW) { hadd = htext + 2 * spacer; pixd = pixCreate(w, h + hadd, d); pixCopyColormap(pixd, pixs); pixCopyResolution(pixd, pixs); pixCopyText(pixd, pixs); pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE); if (location == L_ADD_ABOVE) pixRasterop(pixd, 0, hadd, w, h, PIX_SRC, pixs, 0, 0); else /* add below */ pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); } else { /* L_ADD_LEFT or L_ADD_RIGHT */ wadd = wtext + 2 * spacer; pixd = pixCreate(w + wadd, h, d); pixCopyColormap(pixd, pixs); pixCopyResolution(pixd, pixs); pixCopyText(pixd, pixs); pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE); if (location == L_ADD_LEFT) pixRasterop(pixd, wadd, 0, w, h, PIX_SRC, pixs, 0, 0); else /* add to right */ pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); } /* If cmapped, add the color if necessary to the cmap. If the * cmap is full, use the nearest color to the requested color. */ cmapd = pixGetColormap(pixd); if (cmapd) { extractRGBValues(val, &rval, &gval, &bval); pixcmapAddNearestColor(cmapd, rval, gval, bval, &index); pixcmapGetColor(cmapd, index, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, &textcolor); } else { textcolor = val; } /* Add the text */ for (i = 0; i < nlines; i++) { str = sarrayGetString(sa, i, L_NOCOPY); bmfGetStringWidth(bmf, str, &wtext); if (location == L_ADD_ABOVE) pixSetTextline(pixd, bmf, str, textcolor, (w - wtext) / 2, spacer + hbaseline * (1 + 1.5 * i), NULL, NULL); else if (location == L_ADD_BELOW) pixSetTextline(pixd, bmf, str, textcolor, (w - wtext) / 2, h + spacer + hbaseline * (1 + 1.5 * i), NULL, NULL); else if (location == L_ADD_LEFT) pixSetTextline(pixd, bmf, str, textcolor, spacer, (h - htext) / 2 + hbaseline * (1 + 1.5 * i), NULL, NULL); else /* location == L_ADD_RIGHT */ pixSetTextline(pixd, bmf, str, textcolor, w + spacer, (h - htext) / 2 + hbaseline * (1 + 1.5 * i), NULL, NULL); } sarrayDestroy(&sa); return pixd; } /*! * \brief pixSetTextblock() * * \param[in] pixs input image * \param[in] bmf bitmap font data * \param[in] textstr block text string to be set * \param[in] val color to set the text * \param[in] x0 left edge for each line of text * \param[in] y0 baseline location for the first text line * \param[in] wtext max width of each line of generated text * \param[in] firstindent indentation of first line, in x-widths * \param[out] poverflow [optional] 0 if text is contained in input pix; * 1 if it is clipped * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This function paints a set of lines of text over an image.
 *      (2) %val is the pixel value to be painted through the font mask.
 *          It should be chosen to agree with the depth of pixs.
 *          If it is out of bounds, an intermediate value is chosen.
 *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
 *          hex representation of the red intensity, etc.
 *          The last two hex digits are 00 (byte value 0), assigned to
 *          the A component.  Note that, as usual, RGBA proceeds from
 *          left to right in the order from MSB to LSB (see pix.h
 *          for details).
 *      (3) If there is a colormap, this does the best it can to use
 *          the requested color, or something similar to it.
 * 
*/ l_ok pixSetTextblock(PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 x0, l_int32 y0, l_int32 wtext, l_int32 firstindent, l_int32 *poverflow) { char *linestr; l_int32 d, h, i, w, x, y, nlines, htext, xwidth, wline, ovf, overflow; SARRAY *salines; PIXCMAP *cmap; PROCNAME("pixSetTextblock"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!bmf) return ERROR_INT("bmf not defined", procName, 1); if (!textstr) return ERROR_INT("textstr not defined", procName, 1); /* Make sure the "color" value for the text will work * for the pix. If the pix is not colormapped and the * value is out of range, set it to mid-range. */ pixGetDimensions(pixs, &w, &h, &d); cmap = pixGetColormap(pixs); if (d == 1 && val > 1) val = 1; else if (d == 2 && val > 3 && !cmap) val = 2; else if (d == 4 && val > 15 && !cmap) val = 8; else if (d == 8 && val > 0xff && !cmap) val = 128; else if (d == 16 && val > 0xffff) val = 0x8000; else if (d == 32 && val < 256) val = 0x80808000; if (w < x0 + wtext) { L_WARNING("reducing width of textblock\n", procName); wtext = w - x0 - w / 10; if (wtext <= 0) return ERROR_INT("wtext too small; no room for text", procName, 1); } salines = bmfGetLineStrings(bmf, textstr, wtext, firstindent, &htext); if (!salines) return ERROR_INT("line string sa not made", procName, 1); nlines = sarrayGetCount(salines); bmfGetWidth(bmf, 'x', &xwidth); y = y0; overflow = 0; for (i = 0; i < nlines; i++) { if (i == 0) x = x0 + firstindent * xwidth; else x = x0; linestr = sarrayGetString(salines, i, L_NOCOPY); pixSetTextline(pixs, bmf, linestr, val, x, y, &wline, &ovf); y += bmf->lineheight + bmf->vertlinesep; if (ovf) overflow = 1; } /* (y0 - baseline) is the top of the printed text. Character * 93 was chosen at random, as all the baselines are essentially * equal for each character in a font. */ if (h < y0 - bmf->baselinetab[93] + htext) overflow = 1; if (poverflow) *poverflow = overflow; sarrayDestroy(&salines); return 0; } /*! * \brief pixSetTextline() * * \param[in] pixs input image * \param[in] bmf bitmap font data * \param[in] textstr text string to be set on the line * \param[in] val color to set the text * \param[in] x0 left edge for first char * \param[in] y0 baseline location for all text on line * \param[out] pwidth [optional] width of generated text * \param[out] poverflow [optional] 0 if text is contained in input pix; * 1 if it is clipped * \return 0 if OK, 1 on error * *
 * Notes:
 *      (1) This function paints a line of text over an image.
 *      (2) %val is the pixel value to be painted through the font mask.
 *          It should be chosen to agree with the depth of pixs.
 *          If it is out of bounds, an intermediate value is chosen.
 *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
 *          hex representation of the red intensity, etc.
 *          The last two hex digits are 00 (byte value 0), assigned to
 *          the A component.  Note that, as usual, RGBA proceeds from
 *          left to right in the order from MSB to LSB (see pix.h
 *          for details).
 *      (3) If there is a colormap, this does the best it can to use
 *          the requested color, or something similar to it.
 * 
*/ l_ok pixSetTextline(PIX *pixs, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 x0, l_int32 y0, l_int32 *pwidth, l_int32 *poverflow) { char chr; l_int32 d, i, x, w, nchar, baseline, index, rval, gval, bval; l_uint32 textcolor; PIX *pix; PIXCMAP *cmap; PROCNAME("pixSetTextline"); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (!bmf) return ERROR_INT("bmf not defined", procName, 1); if (!textstr) return ERROR_INT("teststr not defined", procName, 1); d = pixGetDepth(pixs); cmap = pixGetColormap(pixs); if (d == 1 && val > 1) val = 1; else if (d == 2 && val > 3 && !cmap) val = 2; else if (d == 4 && val > 15 && !cmap) val = 8; else if (d == 8 && val > 0xff && !cmap) val = 128; else if (d == 16 && val > 0xffff) val = 0x8000; else if (d == 32 && val < 256) val = 0x80808000; /* If cmapped, add the color if necessary to the cmap. If the * cmap is full, use the nearest color to the requested color. */ if (cmap) { extractRGBValues(val, &rval, &gval, &bval); pixcmapAddNearestColor(cmap, rval, gval, bval, &index); pixcmapGetColor(cmap, index, &rval, &gval, &bval); composeRGBPixel(rval, gval, bval, &textcolor); } else textcolor = val; nchar = strlen(textstr); x = x0; for (i = 0; i < nchar; i++) { chr = textstr[i]; if ((l_int32)chr == 10) continue; /* NL */ pix = bmfGetPix(bmf, chr); bmfGetBaseline(bmf, chr, &baseline); pixPaintThroughMask(pixs, pix, x, y0 - baseline, textcolor); w = pixGetWidth(pix); x += w + bmf->kernwidth; pixDestroy(&pix); } if (pwidth) *pwidth = x - bmf->kernwidth - x0; if (poverflow) *poverflow = (x > pixGetWidth(pixs) - 1) ? 1 : 0; return 0; } /*! * \brief pixaAddTextNumber() * * \param[in] pixas input pixa; colormap ok * \param[in] bmf bitmap font data * \param[in] na [optional] number array; use 1 ... n if null * \param[in] val color to set the text * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT * \return pixad new pixa with rendered numbers, or NULL on error * *
 * Notes:
 *      (1) Typical usage is for labelling each pix in a pixa with a number.
 *      (2) This function paints numbers external to each pix, in a position
 *          given by %location.  In all cases, the pix is expanded on
 *          on side and the number is painted over white in the added region.
 *      (3) %val is the pixel value to be painted through the font mask.
 *          It should be chosen to agree with the depth of pixs.
 *          If it is out of bounds, an intermediate value is chosen.
 *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
 *          hex representation of the red intensity, etc.
 *      (4) If na == NULL, number each pix sequentially, starting with 1.
 *      (5) If there is a colormap, this does the best it can to use
 *          the requested color, or something similar to it.
 * 
*/ PIXA * pixaAddTextNumber(PIXA *pixas, L_BMF *bmf, NUMA *na, l_uint32 val, l_int32 location) { char textstr[128]; l_int32 i, n, index; PIX *pix1, *pix2; PIXA *pixad; PROCNAME("pixaAddTextNumber"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!bmf) return (PIXA *)ERROR_PTR("bmf not defined", procName, NULL); if (location != L_ADD_ABOVE && location != L_ADD_BELOW && location != L_ADD_LEFT && location != L_ADD_RIGHT) return (PIXA *)ERROR_PTR("invalid location", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); if (na) numaGetIValue(na, i, &index); else index = i + 1; snprintf(textstr, sizeof(textstr), "%d", index); pix2 = pixAddTextlines(pix1, bmf, textstr, val, location); pixaAddPix(pixad, pix2, L_INSERT); pixDestroy(&pix1); } return pixad; } /*! * \brief pixaAddTextlines() * * \param[in] pixas input pixa; colormap ok * \param[in] bmf bitmap font data * \param[in] sa [optional] sarray; use text embedded in * each pix if null * \param[in] val color to set the text * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT * \return pixad new pixa with rendered text, or NULL on error * *
 * Notes:
 *      (1) This function adds one or more lines of text externally to
 *          each pix, in a position given by %location.  In all cases,
 *          the pix is expanded as necessary to accommodate the text.
 *      (2) %val is the pixel value to be painted through the font mask.
 *          It should be chosen to agree with the depth of pixs.
 *          If it is out of bounds, an intermediate value is chosen.
 *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
 *          hex representation of the red intensity, etc.
 *      (3) If sa == NULL, use the text embedded in each pix.  In all
 *          cases, newlines in the text string are used to separate the
 *          lines of text that are added to the pix.
 *      (4) If sa has a smaller count than pixa, issue a warning
 *          and do not use any embedded text.
 *      (5) If there is a colormap, this does the best it can to use
 *          the requested color, or something similar to it.
 * 
*/ PIXA * pixaAddTextlines(PIXA *pixas, L_BMF *bmf, SARRAY *sa, l_uint32 val, l_int32 location) { char *textstr; l_int32 i, n, nstr; PIX *pix1, *pix2; PIXA *pixad; PROCNAME("pixaAddTextlines"); if (!pixas) return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL); if (!bmf) return (PIXA *)ERROR_PTR("bmf not defined", procName, NULL); if (location != L_ADD_ABOVE && location != L_ADD_BELOW && location != L_ADD_LEFT && location != L_ADD_RIGHT) return (PIXA *)ERROR_PTR("invalid location", procName, NULL); n = pixaGetCount(pixas); pixad = pixaCreate(n); nstr = (sa) ? sarrayGetCount(sa) : 0; if (nstr > 0 && nstr < n) L_WARNING("There are %d strings and %d pix\n", procName, nstr, n); for (i = 0; i < n; i++) { pix1 = pixaGetPix(pixas, i, L_CLONE); if (i < nstr) textstr = sarrayGetString(sa, i, L_NOCOPY); else textstr = pixGetText(pix1); pix2 = pixAddTextlines(pix1, bmf, textstr, val, location); pixaAddPix(pixad, pix2, L_INSERT); pixDestroy(&pix1); } return pixad; } /*! * \brief pixaAddPixWithText() * * \param[in] pixa * \param[in] pixs any depth, colormap ok * \param[in] reduction integer subsampling factor * \param[in] bmf [optional] bitmap font data * \param[in] textstr [optional] text string to be added * \param[in] val color to set the text * \param[in] location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT * \return 0 if OK, 1 on error. * *
 * Notes:
 *      (1) This function generates a new pix with added text, and adds
 *          it by insertion into the pixa.
 *      (2) If the input pixs is not cmapped and not 32 bpp, it is
 *          converted to 32 bpp rgb.  %val is a standard 32 bpp pixel,
 *          expressed as 0xrrggbb00.  If there is a colormap, this does
 *          the best it can to use the requested color, or something close.
 *      (3) if %bmf == NULL, generate an 8 pt font; this takes about 5 msec.
 *      (4) If %textstr == NULL, use the text field in the pix.
 *      (5) In general, the text string can be written in multiple lines;
 *          use newlines as the separators.
 *      (6) Typical usage is for debugging, where the pixa of labeled images
 *          is used to generate a pdf.  Suggest using 1.0 for scalefactor.
 * 
*/ l_ok pixaAddPixWithText(PIXA *pixa, PIX *pixs, l_int32 reduction, L_BMF *bmf, const char *textstr, l_uint32 val, l_int32 location) { l_int32 d; L_BMF *bmf8; PIX *pix1, *pix2, *pix3; PIXCMAP *cmap; PROCNAME("pixaAddPixWithText"); if (!pixa) return ERROR_INT("pixa not defined", procName, 1); if (!pixs) return ERROR_INT("pixs not defined", procName, 1); if (location != L_ADD_ABOVE && location != L_ADD_BELOW && location != L_ADD_LEFT && location != L_ADD_RIGHT) return ERROR_INT("invalid location", procName, 1); if (!textstr) { textstr = pixGetText(pixs); if (!textstr) { L_WARNING("no textstring defined; inserting copy", procName); pixaAddPix(pixa, pixs, L_COPY); return 0; } } /* Default font size is 8. */ bmf8 = (bmf) ? bmf : bmfCreate(NULL, 8); if (reduction != 1) pix1 = pixScaleByIntSampling(pixs, reduction); else pix1 = pixClone(pixs); /* We want the text to be rendered in color. This works * automatically if pixs is cmapped or 32 bpp rgb; otherwise, * we need to convert to rgb. */ cmap = pixGetColormap(pix1); d = pixGetDepth(pix1); if (!cmap && d != 32) pix2 = pixConvertTo32(pix1); else pix2 = pixClone(pix1); pix3 = pixAddTextlines(pix2, bmf, textstr, val, location); pixDestroy(&pix1); pixDestroy(&pix2); if (!bmf) bmfDestroy(&bmf8); if (!pix3) return ERROR_INT("pix3 not made", procName, 1); pixaAddPix(pixa, pix3, L_INSERT); return 0; } /*---------------------------------------------------------------------* * Text size estimation and partitioning * *---------------------------------------------------------------------*/ /*! * \brief bmfGetLineStrings() * * \param[in] bmf * \param[in] textstr * \param[in] maxw max width of a text line in pixels * \param[in] firstindent indentation of first line, in x-widths * \param[out] ph height required to hold text bitmap * \return sarray of text strings for each line, or NULL on error * *
 * Notes:
 *      (1) Divides the input text string into an array of text strings,
 *          each of which will fit within maxw bits of width.
 * 
*/ SARRAY * bmfGetLineStrings(L_BMF *bmf, const char *textstr, l_int32 maxw, l_int32 firstindent, l_int32 *ph) { char *linestr; l_int32 i, ifirst, sumw, newsum, w, nwords, nlines, len, xwidth; NUMA *na; SARRAY *sa, *sawords; PROCNAME("bmfGetLineStrings"); if (!bmf) return (SARRAY *)ERROR_PTR("bmf not defined", procName, NULL); if (!textstr) return (SARRAY *)ERROR_PTR("teststr not defined", procName, NULL); if ((sawords = sarrayCreateWordsFromString(textstr)) == NULL) return (SARRAY *)ERROR_PTR("sawords not made", procName, NULL); if ((na = bmfGetWordWidths(bmf, textstr, sawords)) == NULL) { sarrayDestroy(&sawords); return (SARRAY *)ERROR_PTR("na not made", procName, NULL); } nwords = numaGetCount(na); if (nwords == 0) { sarrayDestroy(&sawords); numaDestroy(&na); return (SARRAY *)ERROR_PTR("no words in textstr", procName, NULL); } bmfGetWidth(bmf, 'x', &xwidth); sa = sarrayCreate(0); ifirst = 0; numaGetIValue(na, 0, &w); sumw = firstindent * xwidth + w; for (i = 1; i < nwords; i++) { numaGetIValue(na, i, &w); newsum = sumw + bmf->spacewidth + w; if (newsum > maxw) { linestr = sarrayToStringRange(sawords, ifirst, i - ifirst, 2); if (!linestr) continue; len = strlen(linestr); if (len > 0) /* it should always be */ linestr[len - 1] = '\0'; /* remove the last space */ sarrayAddString(sa, linestr, L_INSERT); ifirst = i; sumw = w; } else sumw += bmf->spacewidth + w; } linestr = sarrayToStringRange(sawords, ifirst, nwords - ifirst, 2); if (linestr) sarrayAddString(sa, linestr, L_INSERT); nlines = sarrayGetCount(sa); *ph = nlines * bmf->lineheight + (nlines - 1) * bmf->vertlinesep; sarrayDestroy(&sawords); numaDestroy(&na); return sa; } /*! * \brief bmfGetWordWidths() * * \param[in] bmf * \param[in] textstr * \param[in] sa of individual words * \return numa of word lengths in pixels for the font represented * by the bmf, or NULL on error */ NUMA * bmfGetWordWidths(L_BMF *bmf, const char *textstr, SARRAY *sa) { char *wordstr; l_int32 i, nwords, width; NUMA *na; PROCNAME("bmfGetWordWidths"); if (!bmf) return (NUMA *)ERROR_PTR("bmf not defined", procName, NULL); if (!textstr) return (NUMA *)ERROR_PTR("teststr not defined", procName, NULL); if (!sa) return (NUMA *)ERROR_PTR("sa not defined", procName, NULL); nwords = sarrayGetCount(sa); if ((na = numaCreate(nwords)) == NULL) return (NUMA *)ERROR_PTR("na not made", procName, NULL); for (i = 0; i < nwords; i++) { wordstr = sarrayGetString(sa, i, L_NOCOPY); bmfGetStringWidth(bmf, wordstr, &width); numaAddNumber(na, width); } return na; } /*! * \brief bmfGetStringWidth() * * \param[in] bmf * \param[in] textstr * \param[out] pw width of text string, in pixels for the * font represented by the bmf * \return 0 if OK, 1 on error */ l_ok bmfGetStringWidth(L_BMF *bmf, const char *textstr, l_int32 *pw) { char chr; l_int32 i, w, width, nchar; PROCNAME("bmfGetStringWidth"); if (!bmf) return ERROR_INT("bmf not defined", procName, 1); if (!textstr) return ERROR_INT("teststr not defined", procName, 1); if (!pw) return ERROR_INT("&w not defined", procName, 1); nchar = strlen(textstr); w = 0; for (i = 0; i < nchar; i++) { chr = textstr[i]; bmfGetWidth(bmf, chr, &width); if (width != UNDEF) w += width + bmf->kernwidth; } w -= bmf->kernwidth; /* remove last one */ *pw = w; return 0; } /*---------------------------------------------------------------------* * Text splitting * *---------------------------------------------------------------------*/ /*! * \brief splitStringToParagraphs() * * \param[in] textstr text string * \param[in] splitflag see enum in bmf.h; valid values in {1,2,3} * \return sarray where each string is a paragraph of the input, * or NULL on error. */ SARRAY * splitStringToParagraphs(char *textstr, l_int32 splitflag) { char *linestr, *parastring; l_int32 nlines, i, allwhite, leadwhite; SARRAY *salines, *satemp, *saout; PROCNAME("splitStringToParagraphs"); if (!textstr) return (SARRAY *)ERROR_PTR("textstr not defined", procName, NULL); if ((salines = sarrayCreateLinesFromString(textstr, 1)) == NULL) return (SARRAY *)ERROR_PTR("salines not made", procName, NULL); nlines = sarrayGetCount(salines); saout = sarrayCreate(0); satemp = sarrayCreate(0); linestr = sarrayGetString(salines, 0, L_NOCOPY); sarrayAddString(satemp, linestr, L_COPY); for (i = 1; i < nlines; i++) { linestr = sarrayGetString(salines, i, L_NOCOPY); stringAllWhitespace(linestr, &allwhite); stringLeadingWhitespace(linestr, &leadwhite); if ((splitflag == SPLIT_ON_LEADING_WHITE && leadwhite) || (splitflag == SPLIT_ON_BLANK_LINE && allwhite) || (splitflag == SPLIT_ON_BOTH && (allwhite || leadwhite))) { parastring = sarrayToString(satemp, 1); /* add nl to each line */ sarrayAddString(saout, parastring, L_INSERT); sarrayDestroy(&satemp); satemp = sarrayCreate(0); } sarrayAddString(satemp, linestr, L_COPY); } parastring = sarrayToString(satemp, 1); /* add nl to each line */ sarrayAddString(saout, parastring, L_INSERT); sarrayDestroy(&satemp); sarrayDestroy(&salines); return saout; } /*! * \brief stringAllWhitespace() * * \param[in] textstr text string * \param[out] pval 1 if all whitespace; 0 otherwise * \return 0 if OK, 1 on error */ static l_int32 stringAllWhitespace(char *textstr, l_int32 *pval) { l_int32 len, i; PROCNAME("stringAllWhitespace"); if (!textstr) return ERROR_INT("textstr not defined", procName, 1); if (!pval) return ERROR_INT("&va not defined", procName, 1); len = strlen(textstr); *pval = 1; for (i = 0; i < len; i++) { if (textstr[i] != ' ' && textstr[i] != '\t' && textstr[i] != '\n') { *pval = 0; return 0; } } return 0; } /*! * \brief stringLeadingWhitespace() * * \param[in] textstr text string * \param[out] pval 1 if leading char is [space] or [tab]; 0 otherwise * \return 0 if OK, 1 on error */ static l_int32 stringLeadingWhitespace(char *textstr, l_int32 *pval) { PROCNAME("stringLeadingWhitespace"); if (!textstr) return ERROR_INT("textstr not defined", procName, 1); if (!pval) return ERROR_INT("&va not defined", procName, 1); *pval = 0; if (textstr[0] == ' ' || textstr[0] == '\t') *pval = 1; return 0; }