comparison mupdf-source/thirdparty/leptonica/src/textops.c @ 2:b50eed0cc0ef upstream

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