1 /*====================================================================*
2  -  Copyright (C) 2001 Leptonica.  All rights reserved.
3  -
4  -  Redistribution and use in source and binary forms, with or without
5  -  modification, are permitted provided that the following conditions
6  -  are met:
7  -  1. Redistributions of source code must retain the above copyright
8  -     notice, this list of conditions and the following disclaimer.
9  -  2. Redistributions in binary form must reproduce the above
10  -     copyright notice, this list of conditions and the following
11  -     disclaimer in the documentation and/or other materials
12  -     provided with the distribution.
13  -
14  -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
18  -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23  -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *====================================================================*/
26 
27 
28 /*!
29  * \file textops.c
30  * <pre>
31  *
32  *    Font layout
33  *       PIX             *pixAddSingleTextblock()
34  *       PIX             *pixAddTextlines()
35  *       l_int32          pixSetTextblock()
36  *       l_int32          pixSetTextline()
37  *       PIXA            *pixaAddTextNumber()
38  *       PIXA            *pixaAddTextlines()
39  *       l_int32          pixaAddPixWithText()
40  *
41  *    Text size estimation and partitioning
42  *       SARRAY          *bmfGetLineStrings()
43  *       NUMA            *bmfGetWordWidths()
44  *       l_int32          bmfGetStringWidth()
45  *
46  *    Text splitting
47  *       SARRAY          *splitStringToParagraphs()
48  *       static l_int32   stringAllWhitespace()
49  *       static l_int32   stringLeadingWhitespace()
50  *
51  *    This is a simple utility to put text on images.  One font and style
52  *    is provided, with a variety of pt sizes.  For example, to put a
53  *    line of green 10 pt text on an image, with the beginning baseline
54  *    at (50, 50):
55  *        L_Bmf  *bmf = bmfCreate(NULL, 10);
56  *        const char *textstr = "This is a funny cat";
57  *        pixSetTextline(pixs, bmf, textstr, 0x00ff0000, 50, 50, NULL, NULL);
58  *
59  *    The simplest interfaces for adding text to an image are
60  *    pixAddTextlines() and pixAddSingleTextblock().
61  *    For example, to add the same text in red, centered, below the image:
62  *        Pix *pixd = pixAddTextlines(pixs, bmf, textstr, 0xff000000,
63  *                                    L_ADD_BELOW);  // red text
64  *
65  *    To add text to all pix in a pixa, generating a new pixa, use
66  *    either an sarray to hold the strings for each pix, or use the
67  *    strings in the text field of each pix; e.g.,
68  *        Pixa *pixa2 = pixaAddTextlines(pixa1, bmf, sa, 0x0000ff00,
69  *                                    L_ADD_LEFT);  // blue text
70  *        Pixa *pixa2 = pixaAddTextlines(pixa1, bmf, NULL, 0x00ff0000,
71  *                                    L_ADD_RIGHT);  // green text
72  * </pre>
73  */
74 
75 #include <string.h>
76 #include "allheaders.h"
77 
78 static l_int32 stringAllWhitespace(char *textstr, l_int32 *pval);
79 static l_int32 stringLeadingWhitespace(char *textstr, l_int32 *pval);
80 
81 
82 /*---------------------------------------------------------------------*
83  *                                 Font layout                         *
84  *---------------------------------------------------------------------*/
85 /*!
86  * \brief   pixAddSingleTextblock()
87  *
88  * \param[in]    pixs input pix; colormap ok
89  * \param[in]    bmf bitmap font data
90  * \param[in]    textstr [optional] text string to be added
91  * \param[in]    val color to set the text
92  * \param[in]    location L_ADD_ABOVE, L_ADD_AT_TOP, L_ADD_AT_BOT, L_ADD_BELOW
93  * \param[out]   poverflow [optional] 1 if text overflows
94  *                         allocated region and is clipped; 0 otherwise
95  * \return  pixd new pix with rendered text, or either a copy
96  *                    or NULL on error
97  *
98  * <pre>
99  * Notes:
100  *      (1) This function paints a set of lines of text over an image.
101  *          If %location is L_ADD_ABOVE or L_ADD_BELOW, the pix size
102  *          is expanded with a border and rendered over the border.
103  *      (2) %val is the pixel value to be painted through the font mask.
104  *          It should be chosen to agree with the depth of pixs.
105  *          If it is out of bounds, an intermediate value is chosen.
106  *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
107  *          hex representation of the red intensity, etc.
108  *      (3) If textstr == NULL, use the text field in the pix.
109  *      (4) If there is a colormap, this does the best it can to use
110  *          the requested color, or something similar to it.
111  *      (5) Typical usage is for labelling a pix with some text data.
112  * </pre>
113  */
114 PIX *
pixAddSingleTextblock(PIX * pixs,L_BMF * bmf,const char * textstr,l_uint32 val,l_int32 location,l_int32 * poverflow)115 pixAddSingleTextblock(PIX         *pixs,
116                       L_BMF       *bmf,
117                       const char  *textstr,
118                       l_uint32     val,
119                       l_int32      location,
120                       l_int32     *poverflow)
121 {
122 char     *linestr;
123 l_int32   w, h, d, i, y, xstart, ystart, extra, spacer, rval, gval, bval;
124 l_int32   nlines, htext, ovf, overflow, offset, index;
125 l_uint32  textcolor;
126 PIX      *pixd;
127 PIXCMAP  *cmap, *cmapd;
128 SARRAY   *salines;
129 
130     PROCNAME("pixAddSingleTextblock");
131 
132     if (poverflow) *poverflow = 0;
133     if (!pixs)
134         return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
135     if (location != L_ADD_ABOVE && location != L_ADD_AT_TOP &&
136         location != L_ADD_AT_BOT && location != L_ADD_BELOW)
137         return (PIX *)ERROR_PTR("invalid location", procName, NULL);
138     if (!bmf) {
139         L_ERROR("no bitmap fonts; returning a copy\n", procName);
140         return pixCopy(NULL, pixs);
141     }
142     if (!textstr)
143         textstr = pixGetText(pixs);
144     if (!textstr) {
145         L_WARNING("no textstring defined; returning a copy\n", procName);
146         return pixCopy(NULL, pixs);
147     }
148 
149         /* Make sure the "color" value for the text will work
150          * for the pix.  If the pix is not colormapped and the
151          * value is out of range, set it to mid-range. */
152     pixGetDimensions(pixs, &w, &h, &d);
153     cmap = pixGetColormap(pixs);
154     if (d == 1 && val > 1)
155         val = 1;
156     else if (d == 2 && val > 3 && !cmap)
157         val = 2;
158     else if (d == 4 && val > 15 && !cmap)
159         val = 8;
160     else if (d == 8 && val > 0xff && !cmap)
161         val = 128;
162     else if (d == 16 && val > 0xffff)
163         val = 0x8000;
164     else if (d == 32 && val < 256)
165         val = 0x80808000;
166 
167     xstart = (l_int32)(0.1 * w);
168     salines = bmfGetLineStrings(bmf, textstr, w - 2 * xstart, 0, &htext);
169     if (!salines)
170         return (PIX *)ERROR_PTR("line string sa not made", procName, NULL);
171     nlines = sarrayGetCount(salines);
172 
173         /* Add white border if required */
174     spacer = 10;  /* pixels away from image boundary or added border */
175     if (location == L_ADD_ABOVE || location == L_ADD_BELOW) {
176         extra = htext + 2 * spacer;
177         pixd = pixCreate(w, h + extra, d);
178         pixCopyColormap(pixd, pixs);
179         pixCopyResolution(pixd, pixs);
180         pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
181         if (location == L_ADD_ABOVE)
182             pixRasterop(pixd, 0, extra, w, h, PIX_SRC, pixs, 0, 0);
183         else  /* add below */
184             pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
185     } else {
186         pixd = pixCopy(NULL, pixs);
187     }
188     cmapd = pixGetColormap(pixd);
189 
190         /* bmf->baselinetab[93] is the approximate distance from
191          * the top of the tallest character to the baseline.  93 was chosen
192          * at random, as all the baselines are essentially equal for
193          * each character in a font. */
194     offset = bmf->baselinetab[93];
195     if (location == L_ADD_ABOVE || location == L_ADD_AT_TOP)
196         ystart = offset + spacer;
197     else if (location == L_ADD_AT_BOT)
198         ystart = h - htext - spacer + offset;
199     else   /* add below */
200         ystart = h + offset + spacer;
201 
202         /* If cmapped, add the color if necessary to the cmap.  If the
203          * cmap is full, use the nearest color to the requested color. */
204     if (cmapd) {
205         extractRGBValues(val, &rval, &gval, &bval);
206         pixcmapAddNearestColor(cmapd, rval, gval, bval, &index);
207         pixcmapGetColor(cmapd, index, &rval, &gval, &bval);
208         composeRGBPixel(rval, gval, bval, &textcolor);
209     } else {
210         textcolor = val;
211     }
212 
213         /* Keep track of overflow condition on line width */
214     overflow = 0;
215     for (i = 0, y = ystart; i < nlines; i++) {
216         linestr = sarrayGetString(salines, i, L_NOCOPY);
217         pixSetTextline(pixd, bmf, linestr, textcolor,
218                        xstart, y, NULL, &ovf);
219         y += bmf->lineheight + bmf->vertlinesep;
220         if (ovf)
221             overflow = 1;
222     }
223 
224        /* Also consider vertical overflow where there is too much text to
225         * fit inside the image: the cases L_ADD_AT_TOP and L_ADD_AT_BOT.
226         *  The text requires a total of htext + 2 * spacer vertical pixels. */
227     if (location == L_ADD_AT_TOP || location == L_ADD_AT_BOT) {
228         if (h < htext + 2 * spacer)
229             overflow = 1;
230     }
231     if (poverflow) *poverflow = overflow;
232 
233     sarrayDestroy(&salines);
234     return pixd;
235 }
236 
237 
238 /*!
239  * \brief   pixAddTextlines()
240  *
241  * \param[in]    pixs input pix; colormap ok
242  * \param[in]    bmf bitmap font data
243  * \param[in]    textstr [optional] text string to be added
244  * \param[in]    val color to set the text
245  * \param[in]    location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
246  * \return  pixd new pix with rendered text, or either a copy
247  *                    or NULL on error
248  *
249  * <pre>
250  * Notes:
251  *      (1) This function expands an image as required to paint one or
252  *          more lines of text adjacent to the image.  If %bmf == NULL,
253  *          this returns a copy.  If above or below, the lines are
254  *          centered with respect to the image; if left or right, they
255  *          are left justified.
256  *      (2) %val is the pixel value to be painted through the font mask.
257  *          It should be chosen to agree with the depth of pixs.
258  *          If it is out of bounds, an intermediate value is chosen.
259  *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
260  *          hex representation of the red intensity, etc.
261  *      (3) If textstr == NULL, use the text field in the pix.  The
262  *          text field contains one or most "lines" of text, where newlines
263  *          are used as line separators.
264  *      (4) If there is a colormap, this does the best it can to use
265  *          the requested color, or something similar to it.
266  *      (5) Typical usage is for labelling a pix with some text data.
267  * </pre>
268  */
269 PIX *
pixAddTextlines(PIX * pixs,L_BMF * bmf,const char * textstr,l_uint32 val,l_int32 location)270 pixAddTextlines(PIX         *pixs,
271                 L_BMF       *bmf,
272                 const char  *textstr,
273                 l_uint32     val,
274                 l_int32      location)
275 {
276 char     *str;
277 l_int32   i, w, h, d, rval, gval, bval, index;
278 l_int32   wline, wtext, htext, wadd, hadd, spacer, hbaseline, nlines;
279 l_uint32  textcolor;
280 PIX      *pixd;
281 PIXCMAP  *cmap, *cmapd;
282 SARRAY   *sa;
283 
284     PROCNAME("pixAddTextlines");
285 
286     if (!pixs)
287         return (PIX *)ERROR_PTR("pixs not defined", procName, NULL);
288     if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
289         location != L_ADD_LEFT && location != L_ADD_RIGHT)
290         return (PIX *)ERROR_PTR("invalid location", procName, NULL);
291     if (!bmf) {
292         L_ERROR("no bitmap fonts; returning a copy\n", procName);
293         return pixCopy(NULL, pixs);
294     }
295     if (!textstr) {
296         textstr = pixGetText(pixs);
297         if (!textstr) {
298             L_WARNING("no textstring defined; returning a copy\n", procName);
299             return pixCopy(NULL, pixs);
300         }
301     }
302 
303         /* Make sure the "color" value for the text will work
304          * for the pix.  If the pix is not colormapped and the
305          * value is out of range, set it to mid-range. */
306     pixGetDimensions(pixs, &w, &h, &d);
307     cmap = pixGetColormap(pixs);
308     if (d == 1 && val > 1)
309         val = 1;
310     else if (d == 2 && val > 3 && !cmap)
311         val = 2;
312     else if (d == 4 && val > 15 && !cmap)
313         val = 8;
314     else if (d == 8 && val > 0xff && !cmap)
315         val = 128;
316     else if (d == 16 && val > 0xffff)
317         val = 0x8000;
318     else if (d == 32 && val < 256)
319         val = 0x80808000;
320 
321         /* Get the text in each line */
322     sa = sarrayCreateLinesFromString(textstr, 0);
323     nlines = sarrayGetCount(sa);
324 
325         /* Get the necessary text size */
326     wtext = 0;
327     for (i = 0; i < nlines; i++) {
328         str = sarrayGetString(sa, i, L_NOCOPY);
329         bmfGetStringWidth(bmf, str, &wline);
330         if (wline > wtext)
331             wtext = wline;
332     }
333     hbaseline = bmf->baselinetab[93];
334     htext = 1.5 * hbaseline * nlines;
335 
336         /* Add white border */
337     spacer = 10;  /* pixels away from the added border */
338     if (location == L_ADD_ABOVE || location == L_ADD_BELOW) {
339         hadd = htext + 2 * spacer;
340         pixd = pixCreate(w, h + hadd, d);
341         pixCopyColormap(pixd, pixs);
342         pixCopyResolution(pixd, pixs);
343         pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
344         if (location == L_ADD_ABOVE)
345             pixRasterop(pixd, 0, hadd, w, h, PIX_SRC, pixs, 0, 0);
346         else  /* add below */
347             pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
348     } else {  /*  L_ADD_LEFT or L_ADD_RIGHT */
349         wadd = wtext + 2 * spacer;
350         pixd = pixCreate(w + wadd, h, d);
351         pixCopyColormap(pixd, pixs);
352         pixCopyResolution(pixd, pixs);
353         pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE);
354         if (location == L_ADD_LEFT)
355             pixRasterop(pixd, wadd, 0, w, h, PIX_SRC, pixs, 0, 0);
356         else  /* add to right */
357             pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0);
358     }
359 
360         /* If cmapped, add the color if necessary to the cmap.  If the
361          * cmap is full, use the nearest color to the requested color. */
362     cmapd = pixGetColormap(pixd);
363     if (cmapd) {
364         extractRGBValues(val, &rval, &gval, &bval);
365         pixcmapAddNearestColor(cmapd, rval, gval, bval, &index);
366         pixcmapGetColor(cmapd, index, &rval, &gval, &bval);
367         composeRGBPixel(rval, gval, bval, &textcolor);
368     } else {
369         textcolor = val;
370     }
371 
372         /* Add the text */
373     for (i = 0; i < nlines; i++) {
374         str = sarrayGetString(sa, i, L_NOCOPY);
375         bmfGetStringWidth(bmf, str, &wtext);
376         if (location == L_ADD_ABOVE)
377             pixSetTextline(pixd, bmf, str, textcolor,
378                            (w - wtext) / 2, spacer + hbaseline * (1 + 1.5 * i),
379                            NULL, NULL);
380         else if (location == L_ADD_BELOW)
381             pixSetTextline(pixd, bmf, str, textcolor,
382                            (w - wtext) / 2, h + spacer +
383                            hbaseline * (1 + 1.5 * i), NULL, NULL);
384         else if (location == L_ADD_LEFT)
385             pixSetTextline(pixd, bmf, str, textcolor,
386                            spacer, (h - htext) / 2 + hbaseline * (1 + 1.5 * i),
387                            NULL, NULL);
388         else  /* location == L_ADD_RIGHT */
389             pixSetTextline(pixd, bmf, str, textcolor,
390                            w + spacer, (h - htext) / 2 +
391                            hbaseline * (1 + 1.5 * i), NULL, NULL);
392     }
393 
394     sarrayDestroy(&sa);
395     return pixd;
396 }
397 
398 
399 /*!
400  * \brief   pixSetTextblock()
401  *
402  * \param[in]    pixs input image
403  * \param[in]    bmf bitmap font data
404  * \param[in]    textstr block text string to be set
405  * \param[in]    val color to set the text
406  * \param[in]    x0 left edge for each line of text
407  * \param[in]    y0 baseline location for the first text line
408  * \param[in]    wtext max width of each line of generated text
409  * \param[in]    firstindent indentation of first line, in x-widths
410  * \param[out]   poverflow [optional] 0 if text is contained in
411  *                         input pix; 1 if it is clipped
412  * \return  0 if OK, 1 on error
413  *
414  * <pre>
415  * Notes:
416  *      (1) This function paints a set of lines of text over an image.
417  *      (2) %val is the pixel value to be painted through the font mask.
418  *          It should be chosen to agree with the depth of pixs.
419  *          If it is out of bounds, an intermediate value is chosen.
420  *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
421  *          hex representation of the red intensity, etc.
422  *          The last two hex digits are 00 (byte value 0), assigned to
423  *          the A component.  Note that, as usual, RGBA proceeds from
424  *          left to right in the order from MSB to LSB (see pix.h
425  *          for details).
426  *      (3) If there is a colormap, this does the best it can to use
427  *          the requested color, or something similar to it.
428  * </pre>
429  */
430 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)431 pixSetTextblock(PIX         *pixs,
432                 L_BMF       *bmf,
433                 const char  *textstr,
434                 l_uint32     val,
435                 l_int32      x0,
436                 l_int32      y0,
437                 l_int32      wtext,
438                 l_int32      firstindent,
439                 l_int32     *poverflow)
440 {
441 char     *linestr;
442 l_int32   d, h, i, w, x, y, nlines, htext, xwidth, wline, ovf, overflow;
443 SARRAY   *salines;
444 PIXCMAP  *cmap;
445 
446     PROCNAME("pixSetTextblock");
447 
448     if (!pixs)
449         return ERROR_INT("pixs not defined", procName, 1);
450     if (!bmf)
451         return ERROR_INT("bmf not defined", procName, 1);
452     if (!textstr)
453         return ERROR_INT("textstr not defined", procName, 1);
454 
455         /* Make sure the "color" value for the text will work
456          * for the pix.  If the pix is not colormapped and the
457          * value is out of range, set it to mid-range. */
458     pixGetDimensions(pixs, &w, &h, &d);
459     cmap = pixGetColormap(pixs);
460     if (d == 1 && val > 1)
461         val = 1;
462     else if (d == 2 && val > 3 && !cmap)
463         val = 2;
464     else if (d == 4 && val > 15 && !cmap)
465         val = 8;
466     else if (d == 8 && val > 0xff && !cmap)
467         val = 128;
468     else if (d == 16 && val > 0xffff)
469         val = 0x8000;
470     else if (d == 32 && val < 256)
471         val = 0x80808000;
472 
473     if (w < x0 + wtext) {
474         L_WARNING("reducing width of textblock\n", procName);
475         wtext = w - x0 - w / 10;
476         if (wtext <= 0)
477             return ERROR_INT("wtext too small; no room for text", procName, 1);
478     }
479 
480     salines = bmfGetLineStrings(bmf, textstr, wtext, firstindent, &htext);
481     if (!salines)
482         return ERROR_INT("line string sa not made", procName, 1);
483     nlines = sarrayGetCount(salines);
484     bmfGetWidth(bmf, 'x', &xwidth);
485 
486     y = y0;
487     overflow = 0;
488     for (i = 0; i < nlines; i++) {
489         if (i == 0)
490             x = x0 + firstindent * xwidth;
491         else
492             x = x0;
493         linestr = sarrayGetString(salines, i, L_NOCOPY);
494         pixSetTextline(pixs, bmf, linestr, val, x, y, &wline, &ovf);
495         y += bmf->lineheight + bmf->vertlinesep;
496         if (ovf)
497             overflow = 1;
498     }
499 
500        /* (y0 - baseline) is the top of the printed text.  Character
501         * 93 was chosen at random, as all the baselines are essentially
502         * equal for each character in a font. */
503     if (h < y0 - bmf->baselinetab[93] + htext)
504         overflow = 1;
505     if (poverflow)
506         *poverflow = overflow;
507 
508     sarrayDestroy(&salines);
509     return 0;
510 }
511 
512 
513 /*!
514  * \brief   pixSetTextline()
515  *
516  * \param[in]    pixs input image
517  * \param[in]    bmf bitmap font data
518  * \param[in]    textstr text string to be set on the line
519  * \param[in]    val color to set the text
520  * \param[in]    x0 left edge for first char
521  * \param[in]    y0 baseline location for all text on line
522  * \param[out]   pwidth [optional] width of generated text
523  * \param[out]   poverflow [optional] 0 if text is contained in
524  *                         input pix; 1 if it is clipped
525  * \return  0 if OK, 1 on error
526  *
527  * <pre>
528  * Notes:
529  *      (1) This function paints a line of text over an image.
530  *      (2) %val is the pixel value to be painted through the font mask.
531  *          It should be chosen to agree with the depth of pixs.
532  *          If it is out of bounds, an intermediate value is chosen.
533  *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
534  *          hex representation of the red intensity, etc.
535  *          The last two hex digits are 00 (byte value 0), assigned to
536  *          the A component.  Note that, as usual, RGBA proceeds from
537  *          left to right in the order from MSB to LSB (see pix.h
538  *          for details).
539  *      (3) If there is a colormap, this does the best it can to use
540  *          the requested color, or something similar to it.
541  * </pre>
542  */
543 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)544 pixSetTextline(PIX         *pixs,
545                L_BMF       *bmf,
546                const char  *textstr,
547                l_uint32     val,
548                l_int32      x0,
549                l_int32      y0,
550                l_int32     *pwidth,
551                l_int32     *poverflow)
552 {
553 char      chr;
554 l_int32   d, i, x, w, nchar, baseline, index, rval, gval, bval;
555 l_uint32  textcolor;
556 PIX      *pix;
557 PIXCMAP  *cmap;
558 
559     PROCNAME("pixSetTextline");
560 
561     if (!pixs)
562         return ERROR_INT("pixs not defined", procName, 1);
563     if (!bmf)
564         return ERROR_INT("bmf not defined", procName, 1);
565     if (!textstr)
566         return ERROR_INT("teststr not defined", procName, 1);
567 
568     d = pixGetDepth(pixs);
569     cmap = pixGetColormap(pixs);
570     if (d == 1 && val > 1)
571         val = 1;
572     else if (d == 2 && val > 3 && !cmap)
573         val = 2;
574     else if (d == 4 && val > 15 && !cmap)
575         val = 8;
576     else if (d == 8 && val > 0xff && !cmap)
577         val = 128;
578     else if (d == 16 && val > 0xffff)
579         val = 0x8000;
580     else if (d == 32 && val < 256)
581         val = 0x80808000;
582 
583         /* If cmapped, add the color if necessary to the cmap.  If the
584          * cmap is full, use the nearest color to the requested color. */
585     if (cmap) {
586         extractRGBValues(val, &rval, &gval, &bval);
587         pixcmapAddNearestColor(cmap, rval, gval, bval, &index);
588         pixcmapGetColor(cmap, index, &rval, &gval, &bval);
589         composeRGBPixel(rval, gval, bval, &textcolor);
590     } else
591         textcolor = val;
592 
593     nchar = strlen(textstr);
594     x = x0;
595     for (i = 0; i < nchar; i++) {
596         chr = textstr[i];
597         if ((l_int32)chr == 10) continue;  /* NL */
598         pix = bmfGetPix(bmf, chr);
599         bmfGetBaseline(bmf, chr, &baseline);
600         pixPaintThroughMask(pixs, pix, x, y0 - baseline, textcolor);
601         w = pixGetWidth(pix);
602         x += w + bmf->kernwidth;
603         pixDestroy(&pix);
604     }
605 
606     if (pwidth)
607         *pwidth = x - bmf->kernwidth - x0;
608     if (poverflow)
609         *poverflow = (x > pixGetWidth(pixs) - 1) ? 1 : 0;
610     return 0;
611 }
612 
613 
614 /*!
615  * \brief   pixaAddTextNumber()
616  *
617  * \param[in]    pixas input pixa; colormap ok
618  * \param[in]    bmf bitmap font data
619  * \param[in]    na [optional] number array; use 1 ... n if null
620  * \param[in]    val color to set the text
621  * \param[in]    location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
622  * \return  pixad new pixa with rendered numbers, or NULL on error
623  *
624  * <pre>
625  * Notes:
626  *      (1) Typical usage is for labelling each pix in a pixa with a number.
627  *      (2) This function paints numbers external to each pix, in a position
628  *          given by %location.  In all cases, the pix is expanded on
629  *          on side and the number is painted over white in the added region.
630  *      (3) %val is the pixel value to be painted through the font mask.
631  *          It should be chosen to agree with the depth of pixs.
632  *          If it is out of bounds, an intermediate value is chosen.
633  *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
634  *          hex representation of the red intensity, etc.
635  *      (4) If na == NULL, number each pix sequentially, starting with 1.
636  *      (5) If there is a colormap, this does the best it can to use
637  *          the requested color, or something similar to it.
638  * </pre>
639  */
640 PIXA *
pixaAddTextNumber(PIXA * pixas,L_BMF * bmf,NUMA * na,l_uint32 val,l_int32 location)641 pixaAddTextNumber(PIXA     *pixas,
642                   L_BMF    *bmf,
643                   NUMA     *na,
644                   l_uint32  val,
645                   l_int32   location)
646 {
647 char     textstr[128];
648 l_int32  i, n, index;
649 PIX     *pix1, *pix2;
650 PIXA    *pixad;
651 
652     PROCNAME("pixaAddTextNumber");
653 
654     if (!pixas)
655         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
656     if (!bmf)
657         return (PIXA *)ERROR_PTR("bmf not defined", procName, NULL);
658     if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
659         location != L_ADD_LEFT && location != L_ADD_RIGHT)
660         return (PIXA *)ERROR_PTR("invalid location", procName, NULL);
661 
662     n = pixaGetCount(pixas);
663     pixad = pixaCreate(n);
664     for (i = 0; i < n; i++) {
665         pix1 = pixaGetPix(pixas, i, L_CLONE);
666         if (na)
667             numaGetIValue(na, i, &index);
668         else
669             index = i + 1;
670         snprintf(textstr, sizeof(textstr), "%d", index);
671         pix2 = pixAddTextlines(pix1, bmf, textstr, val, location);
672         pixaAddPix(pixad, pix2, L_INSERT);
673         pixDestroy(&pix1);
674     }
675 
676     return pixad;
677 }
678 
679 
680 /*!
681  * \brief   pixaAddTextlines()
682  *
683  * \param[in]    pixas input pixa; colormap ok
684  * \param[in]    bmf bitmap font data
685  * \param[in]    sa [optional] sarray; use text embedded in each pix if null
686  * \param[in]    val color to set the text
687  * \param[in]    location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
688  * \return  pixad new pixa with rendered text, or NULL on error
689  *
690  * <pre>
691  * Notes:
692  *      (1) This function adds one or more lines of text externally to
693  *          each pix, in a position given by %location.  In all cases,
694  *          the pix is expanded as necessary to accommodate the text.
695  *      (2) %val is the pixel value to be painted through the font mask.
696  *          It should be chosen to agree with the depth of pixs.
697  *          If it is out of bounds, an intermediate value is chosen.
698  *          For RGB, use hex notation: 0xRRGGBB00, where RR is the
699  *          hex representation of the red intensity, etc.
700  *      (3) If sa == NULL, use the text embedded in each pix.  In all
701  *          cases, newlines in the text string are used to separate the
702  *          lines of text that are added to the pix.
703  *      (4) If sa has a smaller count than pixa, issue a warning
704  *          and do not use any embedded text.
705  *      (5) If there is a colormap, this does the best it can to use
706  *          the requested color, or something similar to it.
707  * </pre>
708  */
709 PIXA *
pixaAddTextlines(PIXA * pixas,L_BMF * bmf,SARRAY * sa,l_uint32 val,l_int32 location)710 pixaAddTextlines(PIXA     *pixas,
711                  L_BMF    *bmf,
712                  SARRAY   *sa,
713                  l_uint32  val,
714                  l_int32   location)
715 {
716 char    *textstr;
717 l_int32  i, n, nstr;
718 PIX     *pix1, *pix2;
719 PIXA    *pixad;
720 
721     PROCNAME("pixaAddTextlines");
722 
723     if (!pixas)
724         return (PIXA *)ERROR_PTR("pixas not defined", procName, NULL);
725     if (!bmf)
726         return (PIXA *)ERROR_PTR("bmf not defined", procName, NULL);
727     if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
728         location != L_ADD_LEFT && location != L_ADD_RIGHT)
729         return (PIXA *)ERROR_PTR("invalid location", procName, NULL);
730 
731     n = pixaGetCount(pixas);
732     pixad = pixaCreate(n);
733     nstr = (sa) ? sarrayGetCount(sa) : 0;
734     if (nstr > 0 && nstr < n)
735         L_WARNING("There are %d strings and %d pix\n", procName, nstr, n);
736     for (i = 0; i < n; i++) {
737         pix1 = pixaGetPix(pixas, i, L_CLONE);
738         if (i < nstr)
739             textstr = sarrayGetString(sa, i, L_NOCOPY);
740         else
741             textstr = pixGetText(pix1);
742         pix2 = pixAddTextlines(pix1, bmf, textstr, val, location);
743         pixaAddPix(pixad, pix2, L_INSERT);
744         pixDestroy(&pix1);
745     }
746 
747     return pixad;
748 }
749 
750 
751 /*!
752  * \brief   pixaAddPixWithText()
753  *
754  * \param[in]    pixa
755  * \param[in]    pixs any depth, colormap ok
756  * \param[in]    reduction integer subsampling factor
757  * \param[in]    bmf [optional] bitmap font data
758  * \param[in]    textstr [optional] text string to be added
759  * \param[in]    val color to set the text
760  * \param[in]    location L_ADD_ABOVE, L_ADD_BELOW, L_ADD_LEFT, L_ADD_RIGHT
761  * \return  0 if OK, 1 on error.
762  *
763  * <pre>
764  * Notes:
765  *      (1) This function generates a new pix with added text, and adds
766  *          it by insertion into the pixa.
767  *      (2) If the input pixs is not cmapped and not 32 bpp, it is
768  *          converted to 32 bpp rgb.  %val is a standard 32 bpp pixel,
769  *          expressed as 0xrrggbb00.  If there is a colormap, this does
770  *          the best it can to use the requested color, or something close.
771  *      (3) if %bmf == NULL, generate an 8 pt font; this takes about 5 msec.
772  *      (4) If %textstr == NULL, use the text field in the pix.
773  *      (5) In general, the text string can be written in multiple lines;
774  *          use newlines as the separators.
775  *      (6) Typical usage is for debugging, where the pixa of labeled images
776  *          is used to generate a pdf.  Suggest using 1.0 for scalefactor.
777  * </pre>
778  */
779 l_int32
pixaAddPixWithText(PIXA * pixa,PIX * pixs,l_int32 reduction,L_BMF * bmf,const char * textstr,l_uint32 val,l_int32 location)780 pixaAddPixWithText(PIXA        *pixa,
781                    PIX         *pixs,
782                    l_int32      reduction,
783                    L_BMF       *bmf,
784                    const char  *textstr,
785                    l_uint32     val,
786                    l_int32      location)
787 {
788 l_int32   d;
789 L_BMF    *bmf8;
790 PIX      *pix1, *pix2, *pix3;
791 PIXCMAP  *cmap;
792 
793     PROCNAME("pixaAddPixWithText");
794 
795     if (!pixa)
796         return ERROR_INT("pixa not defined", procName, 1);
797     if (!pixs)
798         return ERROR_INT("pixs not defined", procName, 1);
799     if (location != L_ADD_ABOVE && location != L_ADD_BELOW &&
800         location != L_ADD_LEFT && location != L_ADD_RIGHT)
801         return ERROR_INT("invalid location", procName, 1);
802 
803     if (!textstr) {
804         textstr = pixGetText(pixs);
805         if (!textstr) {
806             L_WARNING("no textstring defined; inserting copy", procName);
807             pixaAddPix(pixa, pixs, L_COPY);
808             return 0;
809         }
810     }
811 
812         /* Default font size is 8. */
813     bmf8 = (bmf) ? bmf : bmfCreate(NULL, 8);
814 
815     if (reduction != 1)
816         pix1 = pixScaleByIntSampling(pixs, reduction);
817     else
818         pix1 = pixClone(pixs);
819 
820         /* We want the text to be rendered in color.  This works
821          * automatically if pixs is cmapped or 32 bpp rgb; otherwise,
822          * we need to convert to rgb. */
823     cmap = pixGetColormap(pix1);
824     d = pixGetDepth(pix1);
825     if (!cmap && d != 32)
826         pix2 = pixConvertTo32(pix1);
827     else
828         pix2 = pixClone(pix1);
829 
830     pix3 = pixAddTextlines(pix2, bmf, textstr, val, location);
831     pixDestroy(&pix1);
832     pixDestroy(&pix2);
833     if (!bmf) bmfDestroy(&bmf8);
834     if (!pix3)
835         return ERROR_INT("pix3 not made", procName, 1);
836 
837     pixaAddPix(pixa, pix3, L_INSERT);
838     return 0;
839 }
840 
841 
842 /*---------------------------------------------------------------------*
843  *                   Text size estimation and partitioning             *
844  *---------------------------------------------------------------------*/
845 /*!
846  * \brief   bmfGetLineStrings()
847  *
848  * \param[in]    bmf
849  * \param[in]    textstr
850  * \param[in]    maxw max width of a text line in pixels
851  * \param[in]    firstindent indentation of first line, in x-widths
852  * \param[out]   ph height required to hold text bitmap
853  * \return  sarray of text strings for each line, or NULL on error
854  *
855  * <pre>
856  * Notes:
857  *      (1) Divides the input text string into an array of text strings,
858  *          each of which will fit within maxw bits of width.
859  * </pre>
860  */
861 SARRAY *
bmfGetLineStrings(L_BMF * bmf,const char * textstr,l_int32 maxw,l_int32 firstindent,l_int32 * ph)862 bmfGetLineStrings(L_BMF       *bmf,
863                   const char  *textstr,
864                   l_int32      maxw,
865                   l_int32      firstindent,
866                   l_int32     *ph)
867 {
868 char    *linestr;
869 l_int32  i, ifirst, sumw, newsum, w, nwords, nlines, len, xwidth;
870 NUMA    *na;
871 SARRAY  *sa, *sawords;
872 
873     PROCNAME("bmfGetLineStrings");
874 
875     if (!bmf)
876         return (SARRAY *)ERROR_PTR("bmf not defined", procName, NULL);
877     if (!textstr)
878         return (SARRAY *)ERROR_PTR("teststr not defined", procName, NULL);
879 
880     if ((sawords = sarrayCreateWordsFromString(textstr)) == NULL)
881         return (SARRAY *)ERROR_PTR("sawords not made", procName, NULL);
882 
883     if ((na = bmfGetWordWidths(bmf, textstr, sawords)) == NULL) {
884         sarrayDestroy(&sawords);
885         return (SARRAY *)ERROR_PTR("na not made", procName, NULL);
886     }
887     nwords = numaGetCount(na);
888     if (nwords == 0) {
889         sarrayDestroy(&sawords);
890         numaDestroy(&na);
891         return (SARRAY *)ERROR_PTR("no words in textstr", procName, NULL);
892     }
893     bmfGetWidth(bmf, 'x', &xwidth);
894 
895     sa = sarrayCreate(0);
896     ifirst = 0;
897     numaGetIValue(na, 0, &w);
898     sumw = firstindent * xwidth + w;
899     for (i = 1; i < nwords; i++) {
900         numaGetIValue(na, i, &w);
901         newsum = sumw + bmf->spacewidth + w;
902         if (newsum > maxw) {
903             linestr = sarrayToStringRange(sawords, ifirst, i - ifirst, 2);
904             if (!linestr)
905                 continue;
906             len = strlen(linestr);
907             if (len > 0)  /* it should always be */
908                 linestr[len - 1] = '\0';  /* remove the last space */
909             sarrayAddString(sa, linestr, L_INSERT);
910             ifirst = i;
911             sumw = w;
912         }
913         else
914             sumw += bmf->spacewidth + w;
915     }
916     linestr = sarrayToStringRange(sawords, ifirst, nwords - ifirst, 2);
917     if (linestr)
918         sarrayAddString(sa, linestr, L_INSERT);
919     nlines = sarrayGetCount(sa);
920     *ph = nlines * bmf->lineheight + (nlines - 1) * bmf->vertlinesep;
921 
922     sarrayDestroy(&sawords);
923     numaDestroy(&na);
924     return sa;
925 }
926 
927 
928 /*!
929  * \brief   bmfGetWordWidths()
930  *
931  * \param[in]    bmf
932  * \param[in]    textstr
933  * \param[in]    sa of individual words
934  * \return  numa of word lengths in pixels for the font represented
935  *                    by the bmf, or NULL on error
936  */
937 NUMA *
bmfGetWordWidths(L_BMF * bmf,const char * textstr,SARRAY * sa)938 bmfGetWordWidths(L_BMF       *bmf,
939                  const char  *textstr,
940                  SARRAY      *sa)
941 {
942 char    *wordstr;
943 l_int32  i, nwords, width;
944 NUMA    *na;
945 
946     PROCNAME("bmfGetWordWidths");
947 
948     if (!bmf)
949         return (NUMA *)ERROR_PTR("bmf not defined", procName, NULL);
950     if (!textstr)
951         return (NUMA *)ERROR_PTR("teststr not defined", procName, NULL);
952     if (!sa)
953         return (NUMA *)ERROR_PTR("sa not defined", procName, NULL);
954 
955     nwords = sarrayGetCount(sa);
956     if ((na = numaCreate(nwords)) == NULL)
957         return (NUMA *)ERROR_PTR("na not made", procName, NULL);
958 
959     for (i = 0; i < nwords; i++) {
960         wordstr = sarrayGetString(sa, i, L_NOCOPY);
961         bmfGetStringWidth(bmf, wordstr, &width);
962         numaAddNumber(na, width);
963     }
964 
965     return na;
966 }
967 
968 
969 /*!
970  * \brief   bmfGetStringWidth()
971  *
972  * \param[in]    bmf
973  * \param[in]    textstr
974  * \param[out]   pw width of text string, in pixels for the
975  *                 font represented by the bmf
976  * \return  0 if OK, 1 on error
977  */
978 l_int32
bmfGetStringWidth(L_BMF * bmf,const char * textstr,l_int32 * pw)979 bmfGetStringWidth(L_BMF       *bmf,
980                   const char  *textstr,
981                   l_int32     *pw)
982 {
983 char     chr;
984 l_int32  i, w, width, nchar;
985 
986     PROCNAME("bmfGetStringWidth");
987 
988     if (!bmf)
989         return ERROR_INT("bmf not defined", procName, 1);
990     if (!textstr)
991         return ERROR_INT("teststr not defined", procName, 1);
992     if (!pw)
993         return ERROR_INT("&w not defined", procName, 1);
994 
995     nchar = strlen(textstr);
996     w = 0;
997     for (i = 0; i < nchar; i++) {
998         chr = textstr[i];
999         bmfGetWidth(bmf, chr, &width);
1000         if (width != UNDEF)
1001             w += width + bmf->kernwidth;
1002     }
1003     w -= bmf->kernwidth;  /* remove last one */
1004 
1005     *pw = w;
1006     return 0;
1007 }
1008 
1009 
1010 
1011 /*---------------------------------------------------------------------*
1012  *                             Text splitting                          *
1013  *---------------------------------------------------------------------*/
1014 /*!
1015  * \brief   splitStringToParagraphs()
1016  *
1017  * \param[in]    textstr text string
1018  * \param[in]    splitflag see enum in bmf.h; valid values in {1,2,3}
1019  * \return  sarray where each string is a paragraph of the input,
1020  *                      or NULL on error.
1021  */
1022 SARRAY *
splitStringToParagraphs(char * textstr,l_int32 splitflag)1023 splitStringToParagraphs(char    *textstr,
1024                         l_int32  splitflag)
1025 {
1026 char    *linestr, *parastring;
1027 l_int32  nlines, i, allwhite, leadwhite;
1028 SARRAY  *salines, *satemp, *saout;
1029 
1030     PROCNAME("splitStringToParagraphs");
1031 
1032     if (!textstr)
1033         return (SARRAY *)ERROR_PTR("textstr not defined", procName, NULL);
1034 
1035     if ((salines = sarrayCreateLinesFromString(textstr, 1)) == NULL)
1036         return (SARRAY *)ERROR_PTR("salines not made", procName, NULL);
1037     nlines = sarrayGetCount(salines);
1038     saout = sarrayCreate(0);
1039     satemp = sarrayCreate(0);
1040 
1041     linestr = sarrayGetString(salines, 0, L_NOCOPY);
1042     sarrayAddString(satemp, linestr, L_COPY);
1043     for (i = 1; i < nlines; i++) {
1044         linestr = sarrayGetString(salines, i, L_NOCOPY);
1045         stringAllWhitespace(linestr, &allwhite);
1046         stringLeadingWhitespace(linestr, &leadwhite);
1047         if ((splitflag == SPLIT_ON_LEADING_WHITE && leadwhite) ||
1048             (splitflag == SPLIT_ON_BLANK_LINE && allwhite) ||
1049             (splitflag == SPLIT_ON_BOTH && (allwhite || leadwhite))) {
1050             parastring = sarrayToString(satemp, 1);  /* add nl to each line */
1051             sarrayAddString(saout, parastring, L_INSERT);
1052             sarrayDestroy(&satemp);
1053             satemp = sarrayCreate(0);
1054         }
1055         sarrayAddString(satemp, linestr, L_COPY);
1056     }
1057     parastring = sarrayToString(satemp, 1);  /* add nl to each line */
1058     sarrayAddString(saout, parastring, L_INSERT);
1059     sarrayDestroy(&satemp);
1060     sarrayDestroy(&salines);
1061     return saout;
1062 }
1063 
1064 
1065 /*!
1066  * \brief   stringAllWhitespace()
1067  *
1068  * \param[in]    textstr text string
1069  * \param[out]   pval 1 if all whitespace; 0 otherwise
1070  * \return  0 if OK, 1 on error
1071  */
1072 static l_int32
stringAllWhitespace(char * textstr,l_int32 * pval)1073 stringAllWhitespace(char     *textstr,
1074                     l_int32  *pval)
1075 {
1076 l_int32  len, i;
1077 
1078     PROCNAME("stringAllWhitespace");
1079 
1080     if (!textstr)
1081         return ERROR_INT("textstr not defined", procName, 1);
1082     if (!pval)
1083         return ERROR_INT("&va not defined", procName, 1);
1084 
1085     len = strlen(textstr);
1086     *pval = 1;
1087     for (i = 0; i < len; i++) {
1088         if (textstr[i] != ' ' && textstr[i] != '\t' && textstr[i] != '\n') {
1089             *pval = 0;
1090             return 0;
1091         }
1092     }
1093     return 0;
1094 }
1095 
1096 
1097 /*!
1098  * \brief   stringLeadingWhitespace()
1099  *
1100  * \param[in]    textstr text string
1101  * \param[out]   pval 1 if leading char is [space] or [tab]; 0 otherwise
1102  * \return  0 if OK, 1 on error
1103  */
1104 static l_int32
stringLeadingWhitespace(char * textstr,l_int32 * pval)1105 stringLeadingWhitespace(char     *textstr,
1106                         l_int32  *pval)
1107 {
1108     PROCNAME("stringLeadingWhitespace");
1109 
1110     if (!textstr)
1111         return ERROR_INT("textstr not defined", procName, 1);
1112     if (!pval)
1113         return ERROR_INT("&va not defined", procName, 1);
1114 
1115     *pval = 0;
1116     if (textstr[0] == ' ' || textstr[0] == '\t')
1117         *pval = 1;
1118 
1119     return 0;
1120 }
1121