summaryrefslogtreecommitdiff
path: root/src/textops.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/textops.c')
-rw-r--r--src/textops.c1094
1 files changed, 1094 insertions, 0 deletions
diff --git a/src/textops.c b/src/textops.c
new file mode 100644
index 0000000..f1a22f5
--- /dev/null
+++ b/src/textops.c
@@ -0,0 +1,1094 @@
+/*====================================================================*
+ - 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.
+ *====================================================================*/
+
+
+/*
+ * 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
+ */
+
+#include <string.h>
+#include "allheaders.h"
+
+static l_int32 stringAllWhitespace(char *textstr, l_int32 *pval);
+static l_int32 stringLeadingWhitespace(char *textstr, l_int32 *pval);
+
+
+/*---------------------------------------------------------------------*
+ * Font layout *
+ *---------------------------------------------------------------------*/
+/*!
+ * pixAddSingleTextblock()
+ *
+ * Input: pixs (input pix; colormap ok)
+ * bmf (bitmap font data)
+ * textstr (<optional> text string to be added)
+ * val (color to set the text)
+ * location (L_ADD_ABOVE, L_ADD_AT_TOP, L_ADD_AT_BOT, L_ADD_BELOW)
+ * &overflow (<optional return> 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_ERROR("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);
+ 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;
+}
+
+
+/*!
+ * pixAddTextlines()
+ *
+ * Input: pixs (input pix; colormap ok)
+ * bmf (bitmap font data)
+ * textstr (<optional> text string to be added)
+ * val (color to set the text)
+ * 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_ERROR("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);
+ 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);
+ 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;
+}
+
+
+/*!
+ * pixSetTextblock()
+ *
+ * Input: pixs (input image)
+ * bmf (bitmap font data)
+ * textstr (block text string to be set)
+ * val (color to set the text)
+ * x0 (left edge for each line of text)
+ * y0 (baseline location for the first text line)
+ * wtext (max width of each line of generated text)
+ * firstindent (indentation of first line, in x-widths)
+ * &overflow (<optional return> 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_int32
+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;
+}
+
+
+/*!
+ * pixSetTextline()
+ *
+ * Input: pixs (input image)
+ * bmf (bitmap font data)
+ * textstr (text string to be set on the line)
+ * val (color to set the text)
+ * x0 (left edge for first char)
+ * y0 (baseline location for all text on line)
+ * &width (<optional return> width of generated text)
+ * &overflow (<optional return> 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_int32
+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;
+}
+
+
+/*!
+ * pixaAddTextNumber()
+ *
+ * Input: pixas (input pixa; colormap ok)
+ * bmf (bitmap font data)
+ * numa (<optional> number array; use 1 ... n if null)
+ * val (color to set the text)
+ * 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;
+}
+
+
+/*!
+ * pixaAddTextlines()
+ *
+ * Input: pixas (input pixa; colormap ok)
+ * bmf (bitmap font data)
+ * sa (<optional> sarray; use text embedded in each pix if null)
+ * val (color to set the text)
+ * 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;
+}
+
+
+/*!
+ * pixaAddPixWithText()
+ *
+ * Input: pixa
+ * pixs (any depth, colormap ok)
+ * reduction (integer subsampling factor)
+ * bmf (<optional> bitmap font data)
+ * textstr (<optional> text string to be added)
+ * val (color to set the text)
+ * 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 labelled images
+ * is used to generate a pdf. Suggest using 1.0 for scalefactor.
+ */
+l_int32
+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 (!textstr) {
+ textstr = pixGetText(pixs);
+ if (!textstr) {
+ L_ERROR("no textstring defined; inserting copy", procName);
+ pixaAddPix(pixa, pixs, L_COPY);
+ return 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);
+
+ /* 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 (!pix3)
+ return ERROR_INT("pix3 not made", procName, 1);
+
+ pixaAddPix(pixa, pix3, L_INSERT);
+ if (!bmf) bmfDestroy(&bmf8);
+ return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ * Text size estimation and partitioning *
+ *---------------------------------------------------------------------*/
+/*!
+ * bmfGetLineStrings()
+ *
+ * Input: bmf
+ * textstr
+ * maxw (max width of a text line in pixels)
+ * firstindent (indentation of first line, in x-widths)
+ * &h (<return> 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)
+ return (SARRAY *)ERROR_PTR("na not made", procName, NULL);
+ nwords = numaGetCount(na);
+ if (nwords == 0)
+ return (SARRAY *)ERROR_PTR("no words in textstr", procName, NULL);
+ bmfGetWidth(bmf, 'x', &xwidth);
+
+ if ((sa = sarrayCreate(0)) == NULL)
+ return (SARRAY *)ERROR_PTR("sa not made", procName, NULL);
+
+ 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;
+}
+
+
+/*!
+ * bmfGetWordWidths()
+ *
+ * Input: bmf
+ * textstr
+ * 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;
+}
+
+
+/*!
+ * bmfGetStringWidth()
+ *
+ * Input: bmf
+ * textstr
+ * &w (<return> width of text string, in pixels for the
+ * font represented by the bmf)
+ * Return: 0 if OK, 1 on error
+ */
+l_int32
+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 *
+ *---------------------------------------------------------------------*/
+/*!
+ * splitStringToParagraphs()
+ *
+ * Input: textstring
+ * splitting flag (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);
+
+ return saout;
+}
+
+
+/*!
+ * stringAllWhitespace()
+ *
+ * Input: textstring
+ * &val (<return> 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;
+}
+
+
+/*!
+ * stringLeadingWhitespace()
+ *
+ * Input: textstring
+ * &val (<return> 1 if leading char is ' ' or '\t'; 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;
+}