/src/leptonica/src/textops.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | { |
127 | 0 | char *linestr; |
128 | 0 | l_int32 w, h, d, i, y, xstart, ystart, extra, spacer, rval, gval, bval; |
129 | 0 | l_int32 nlines, htext, ovf, overflow, offset, index; |
130 | 0 | l_uint32 textcolor; |
131 | 0 | PIX *pixd; |
132 | 0 | PIXCMAP *cmap, *cmapd; |
133 | 0 | SARRAY *salines; |
134 | |
|
135 | 0 | if (poverflow) *poverflow = 0; |
136 | 0 | if (!pixs) |
137 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
138 | 0 | if (location != L_ADD_ABOVE && location != L_ADD_AT_TOP && |
139 | 0 | location != L_ADD_AT_BOT && location != L_ADD_BELOW) |
140 | 0 | return (PIX *)ERROR_PTR("invalid location", __func__, NULL); |
141 | 0 | if (!bmf) { |
142 | 0 | L_ERROR("no bitmap fonts; returning a copy\n", __func__); |
143 | 0 | return pixCopy(NULL, pixs); |
144 | 0 | } |
145 | 0 | if (!textstr) |
146 | 0 | textstr = pixGetText(pixs); |
147 | 0 | if (!textstr) { |
148 | 0 | L_WARNING("no textstring defined; returning a copy\n", __func__); |
149 | 0 | return pixCopy(NULL, pixs); |
150 | 0 | } |
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 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
156 | 0 | cmap = pixGetColormap(pixs); |
157 | 0 | if (d == 1 && val > 1) |
158 | 0 | val = 1; |
159 | 0 | else if (d == 2 && val > 3 && !cmap) |
160 | 0 | val = 2; |
161 | 0 | else if (d == 4 && val > 15 && !cmap) |
162 | 0 | val = 8; |
163 | 0 | else if (d == 8 && val > 0xff && !cmap) |
164 | 0 | val = 128; |
165 | 0 | else if (d == 16 && val > 0xffff) |
166 | 0 | val = 0x8000; |
167 | 0 | else if (d == 32 && val < 256) |
168 | 0 | val = 0x80808000; |
169 | |
|
170 | 0 | xstart = (l_int32)(0.1 * w); |
171 | 0 | salines = bmfGetLineStrings(bmf, textstr, w - 2 * xstart, 0, &htext); |
172 | 0 | if (!salines) |
173 | 0 | return (PIX *)ERROR_PTR("line string sa not made", __func__, NULL); |
174 | 0 | nlines = sarrayGetCount(salines); |
175 | | |
176 | | /* Add white border if required */ |
177 | 0 | spacer = 10; /* pixels away from image boundary or added border */ |
178 | 0 | if (location == L_ADD_ABOVE || location == L_ADD_BELOW) { |
179 | 0 | extra = htext + 2 * spacer; |
180 | 0 | pixd = pixCreate(w, h + extra, d); |
181 | 0 | pixCopyColormap(pixd, pixs); |
182 | 0 | pixCopyResolution(pixd, pixs); |
183 | 0 | pixCopyText(pixd, pixs); |
184 | 0 | pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE); |
185 | 0 | if (location == L_ADD_ABOVE) |
186 | 0 | pixRasterop(pixd, 0, extra, w, h, PIX_SRC, pixs, 0, 0); |
187 | 0 | else /* add below */ |
188 | 0 | pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); |
189 | 0 | } else { |
190 | 0 | pixd = pixCopy(NULL, pixs); |
191 | 0 | } |
192 | 0 | 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 | 0 | offset = bmf->baselinetab[93]; |
199 | 0 | if (location == L_ADD_ABOVE || location == L_ADD_AT_TOP) |
200 | 0 | ystart = offset + spacer; |
201 | 0 | else if (location == L_ADD_AT_BOT) |
202 | 0 | ystart = h - htext - spacer + offset; |
203 | 0 | else /* add below */ |
204 | 0 | 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 | 0 | if (cmapd) { |
209 | 0 | extractRGBValues(val, &rval, &gval, &bval); |
210 | 0 | pixcmapAddNearestColor(cmapd, rval, gval, bval, &index); |
211 | 0 | pixcmapGetColor(cmapd, index, &rval, &gval, &bval); |
212 | 0 | composeRGBPixel(rval, gval, bval, &textcolor); |
213 | 0 | } else { |
214 | 0 | textcolor = val; |
215 | 0 | } |
216 | | |
217 | | /* Keep track of overflow condition on line width */ |
218 | 0 | overflow = 0; |
219 | 0 | for (i = 0, y = ystart; i < nlines; i++) { |
220 | 0 | linestr = sarrayGetString(salines, i, L_NOCOPY); |
221 | 0 | pixSetTextline(pixd, bmf, linestr, textcolor, |
222 | 0 | xstart, y, NULL, &ovf); |
223 | 0 | y += bmf->lineheight + bmf->vertlinesep; |
224 | 0 | if (ovf) |
225 | 0 | overflow = 1; |
226 | 0 | } |
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 | 0 | if (location == L_ADD_AT_TOP || location == L_ADD_AT_BOT) { |
232 | 0 | if (h < htext + 2 * spacer) |
233 | 0 | overflow = 1; |
234 | 0 | } |
235 | 0 | if (poverflow) *poverflow = overflow; |
236 | |
|
237 | 0 | sarrayDestroy(&salines); |
238 | 0 | return pixd; |
239 | 0 | } |
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 | 0 | { |
280 | 0 | char *str; |
281 | 0 | l_int32 i, w, h, d, rval, gval, bval, index; |
282 | 0 | l_int32 wline, wtext, htext, wadd, hadd, spacer, hbaseline, nlines; |
283 | 0 | l_uint32 textcolor; |
284 | 0 | PIX *pixd; |
285 | 0 | PIXCMAP *cmap, *cmapd; |
286 | 0 | SARRAY *sa; |
287 | |
|
288 | 0 | if (!pixs) |
289 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
290 | 0 | if (location != L_ADD_ABOVE && location != L_ADD_BELOW && |
291 | 0 | location != L_ADD_LEFT && location != L_ADD_RIGHT) |
292 | 0 | return (PIX *)ERROR_PTR("invalid location", __func__, NULL); |
293 | 0 | if (!bmf) { |
294 | 0 | L_ERROR("no bitmap fonts; returning a copy\n", __func__); |
295 | 0 | return pixCopy(NULL, pixs); |
296 | 0 | } |
297 | 0 | if (!textstr) { |
298 | 0 | textstr = pixGetText(pixs); |
299 | 0 | if (!textstr) { |
300 | 0 | L_WARNING("no textstring defined; returning a copy\n", __func__); |
301 | 0 | return pixCopy(NULL, pixs); |
302 | 0 | } |
303 | 0 | } |
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 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
309 | 0 | cmap = pixGetColormap(pixs); |
310 | 0 | if (d == 1 && val > 1) |
311 | 0 | val = 1; |
312 | 0 | else if (d == 2 && val > 3 && !cmap) |
313 | 0 | val = 2; |
314 | 0 | else if (d == 4 && val > 15 && !cmap) |
315 | 0 | val = 8; |
316 | 0 | else if (d == 8 && val > 0xff && !cmap) |
317 | 0 | val = 128; |
318 | 0 | else if (d == 16 && val > 0xffff) |
319 | 0 | val = 0x8000; |
320 | 0 | else if (d == 32 && val < 256) |
321 | 0 | val = 0x80808000; |
322 | | |
323 | | /* Get the text in each line */ |
324 | 0 | sa = sarrayCreateLinesFromString(textstr, 0); |
325 | 0 | nlines = sarrayGetCount(sa); |
326 | | |
327 | | /* Get the necessary text size */ |
328 | 0 | wtext = 0; |
329 | 0 | for (i = 0; i < nlines; i++) { |
330 | 0 | str = sarrayGetString(sa, i, L_NOCOPY); |
331 | 0 | bmfGetStringWidth(bmf, str, &wline); |
332 | 0 | if (wline > wtext) |
333 | 0 | wtext = wline; |
334 | 0 | } |
335 | 0 | hbaseline = bmf->baselinetab[93]; |
336 | 0 | htext = 1.5 * hbaseline * nlines; |
337 | | |
338 | | /* Add white border */ |
339 | 0 | spacer = 10; /* pixels away from the added border */ |
340 | 0 | if (location == L_ADD_ABOVE || location == L_ADD_BELOW) { |
341 | 0 | hadd = htext + 2 * spacer; |
342 | 0 | pixd = pixCreate(w, h + hadd, d); |
343 | 0 | pixCopyColormap(pixd, pixs); |
344 | 0 | pixCopyResolution(pixd, pixs); |
345 | 0 | pixCopyText(pixd, pixs); |
346 | 0 | pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE); |
347 | 0 | if (location == L_ADD_ABOVE) |
348 | 0 | pixRasterop(pixd, 0, hadd, w, h, PIX_SRC, pixs, 0, 0); |
349 | 0 | else /* add below */ |
350 | 0 | pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); |
351 | 0 | } else { /* L_ADD_LEFT or L_ADD_RIGHT */ |
352 | 0 | wadd = wtext + 2 * spacer; |
353 | 0 | pixd = pixCreate(w + wadd, h, d); |
354 | 0 | pixCopyColormap(pixd, pixs); |
355 | 0 | pixCopyResolution(pixd, pixs); |
356 | 0 | pixCopyText(pixd, pixs); |
357 | 0 | pixSetBlackOrWhite(pixd, L_BRING_IN_WHITE); |
358 | 0 | if (location == L_ADD_LEFT) |
359 | 0 | pixRasterop(pixd, wadd, 0, w, h, PIX_SRC, pixs, 0, 0); |
360 | 0 | else /* add to right */ |
361 | 0 | pixRasterop(pixd, 0, 0, w, h, PIX_SRC, pixs, 0, 0); |
362 | 0 | } |
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 | 0 | cmapd = pixGetColormap(pixd); |
367 | 0 | if (cmapd) { |
368 | 0 | extractRGBValues(val, &rval, &gval, &bval); |
369 | 0 | pixcmapAddNearestColor(cmapd, rval, gval, bval, &index); |
370 | 0 | pixcmapGetColor(cmapd, index, &rval, &gval, &bval); |
371 | 0 | composeRGBPixel(rval, gval, bval, &textcolor); |
372 | 0 | } else { |
373 | 0 | textcolor = val; |
374 | 0 | } |
375 | | |
376 | | /* Add the text */ |
377 | 0 | for (i = 0; i < nlines; i++) { |
378 | 0 | str = sarrayGetString(sa, i, L_NOCOPY); |
379 | 0 | bmfGetStringWidth(bmf, str, &wtext); |
380 | 0 | if (location == L_ADD_ABOVE) |
381 | 0 | pixSetTextline(pixd, bmf, str, textcolor, |
382 | 0 | (w - wtext) / 2, spacer + hbaseline * (1 + 1.5 * i), |
383 | 0 | NULL, NULL); |
384 | 0 | else if (location == L_ADD_BELOW) |
385 | 0 | pixSetTextline(pixd, bmf, str, textcolor, |
386 | 0 | (w - wtext) / 2, h + spacer + |
387 | 0 | hbaseline * (1 + 1.5 * i), NULL, NULL); |
388 | 0 | else if (location == L_ADD_LEFT) |
389 | 0 | pixSetTextline(pixd, bmf, str, textcolor, |
390 | 0 | spacer, (h - htext) / 2 + hbaseline * (1 + 1.5 * i), |
391 | 0 | NULL, NULL); |
392 | 0 | else /* location == L_ADD_RIGHT */ |
393 | 0 | pixSetTextline(pixd, bmf, str, textcolor, |
394 | 0 | w + spacer, (h - htext) / 2 + |
395 | 0 | hbaseline * (1 + 1.5 * i), NULL, NULL); |
396 | 0 | } |
397 | |
|
398 | 0 | sarrayDestroy(&sa); |
399 | 0 | return pixd; |
400 | 0 | } |
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 | 0 | { |
445 | 0 | char *linestr; |
446 | 0 | l_int32 d, h, i, w, x, y, nlines, htext, xwidth, wline, ovf, overflow; |
447 | 0 | SARRAY *salines; |
448 | 0 | PIXCMAP *cmap; |
449 | |
|
450 | 0 | if (!pixs) |
451 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
452 | 0 | if (!bmf) |
453 | 0 | return ERROR_INT("bmf not defined", __func__, 1); |
454 | 0 | if (!textstr) |
455 | 0 | 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 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
461 | 0 | cmap = pixGetColormap(pixs); |
462 | 0 | if (d == 1 && val > 1) |
463 | 0 | val = 1; |
464 | 0 | else if (d == 2 && val > 3 && !cmap) |
465 | 0 | val = 2; |
466 | 0 | else if (d == 4 && val > 15 && !cmap) |
467 | 0 | val = 8; |
468 | 0 | else if (d == 8 && val > 0xff && !cmap) |
469 | 0 | val = 128; |
470 | 0 | else if (d == 16 && val > 0xffff) |
471 | 0 | val = 0x8000; |
472 | 0 | else if (d == 32 && val < 256) |
473 | 0 | val = 0x80808000; |
474 | |
|
475 | 0 | if (w < x0 + wtext) { |
476 | 0 | L_WARNING("reducing width of textblock\n", __func__); |
477 | 0 | wtext = w - x0 - w / 10; |
478 | 0 | if (wtext <= 0) |
479 | 0 | return ERROR_INT("wtext too small; no room for text", __func__, 1); |
480 | 0 | } |
481 | | |
482 | 0 | salines = bmfGetLineStrings(bmf, textstr, wtext, firstindent, &htext); |
483 | 0 | if (!salines) |
484 | 0 | return ERROR_INT("line string sa not made", __func__, 1); |
485 | 0 | nlines = sarrayGetCount(salines); |
486 | 0 | bmfGetWidth(bmf, 'x', &xwidth); |
487 | |
|
488 | 0 | y = y0; |
489 | 0 | overflow = 0; |
490 | 0 | for (i = 0; i < nlines; i++) { |
491 | 0 | if (i == 0) |
492 | 0 | x = x0 + firstindent * xwidth; |
493 | 0 | else |
494 | 0 | x = x0; |
495 | 0 | linestr = sarrayGetString(salines, i, L_NOCOPY); |
496 | 0 | pixSetTextline(pixs, bmf, linestr, val, x, y, &wline, &ovf); |
497 | 0 | y += bmf->lineheight + bmf->vertlinesep; |
498 | 0 | if (ovf) |
499 | 0 | overflow = 1; |
500 | 0 | } |
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 | 0 | if (h < y0 - bmf->baselinetab[93] + htext) |
506 | 0 | overflow = 1; |
507 | 0 | if (poverflow) |
508 | 0 | *poverflow = overflow; |
509 | |
|
510 | 0 | sarrayDestroy(&salines); |
511 | 0 | return 0; |
512 | 0 | } |
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 | 0 | { |
555 | 0 | char chr; |
556 | 0 | l_int32 d, i, x, w, nchar, baseline, index, rval, gval, bval; |
557 | 0 | l_uint32 textcolor; |
558 | 0 | PIX *pix; |
559 | 0 | PIXCMAP *cmap; |
560 | |
|
561 | 0 | if (!pixs) |
562 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
563 | 0 | if (!bmf) |
564 | 0 | return ERROR_INT("bmf not defined", __func__, 1); |
565 | 0 | if (!textstr) |
566 | 0 | return ERROR_INT("teststr not defined", __func__, 1); |
567 | | |
568 | 0 | d = pixGetDepth(pixs); |
569 | 0 | cmap = pixGetColormap(pixs); |
570 | 0 | if (d == 1 && val > 1) |
571 | 0 | val = 1; |
572 | 0 | else if (d == 2 && val > 3 && !cmap) |
573 | 0 | val = 2; |
574 | 0 | else if (d == 4 && val > 15 && !cmap) |
575 | 0 | val = 8; |
576 | 0 | else if (d == 8 && val > 0xff && !cmap) |
577 | 0 | val = 128; |
578 | 0 | else if (d == 16 && val > 0xffff) |
579 | 0 | val = 0x8000; |
580 | 0 | else if (d == 32 && val < 256) |
581 | 0 | 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 | 0 | if (cmap) { |
586 | 0 | extractRGBValues(val, &rval, &gval, &bval); |
587 | 0 | pixcmapAddNearestColor(cmap, rval, gval, bval, &index); |
588 | 0 | pixcmapGetColor(cmap, index, &rval, &gval, &bval); |
589 | 0 | composeRGBPixel(rval, gval, bval, &textcolor); |
590 | 0 | } else |
591 | 0 | textcolor = val; |
592 | |
|
593 | 0 | nchar = strlen(textstr); |
594 | 0 | x = x0; |
595 | 0 | for (i = 0; i < nchar; i++) { |
596 | 0 | chr = textstr[i]; |
597 | 0 | if ((l_int32)chr == 10) continue; /* NL */ |
598 | 0 | pix = bmfGetPix(bmf, chr); |
599 | 0 | bmfGetBaseline(bmf, chr, &baseline); |
600 | 0 | pixPaintThroughMask(pixs, pix, x, y0 - baseline, textcolor); |
601 | 0 | w = pixGetWidth(pix); |
602 | 0 | x += w + bmf->kernwidth; |
603 | 0 | pixDestroy(&pix); |
604 | 0 | } |
605 | |
|
606 | 0 | if (pwidth) |
607 | 0 | *pwidth = x - bmf->kernwidth - x0; |
608 | 0 | if (poverflow) |
609 | 0 | *poverflow = (x > pixGetWidth(pixs) - 1) ? 1 : 0; |
610 | 0 | return 0; |
611 | 0 | } |
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 | 0 | { |
647 | 0 | char textstr[128]; |
648 | 0 | l_int32 i, n, index; |
649 | 0 | PIX *pix1, *pix2; |
650 | 0 | PIXA *pixad; |
651 | |
|
652 | 0 | if (!pixas) |
653 | 0 | return (PIXA *)ERROR_PTR("pixas not defined", __func__, NULL); |
654 | 0 | if (!bmf) |
655 | 0 | return (PIXA *)ERROR_PTR("bmf not defined", __func__, NULL); |
656 | 0 | if (location != L_ADD_ABOVE && location != L_ADD_BELOW && |
657 | 0 | location != L_ADD_LEFT && location != L_ADD_RIGHT) |
658 | 0 | return (PIXA *)ERROR_PTR("invalid location", __func__, NULL); |
659 | | |
660 | 0 | n = pixaGetCount(pixas); |
661 | 0 | pixad = pixaCreate(n); |
662 | 0 | for (i = 0; i < n; i++) { |
663 | 0 | pix1 = pixaGetPix(pixas, i, L_CLONE); |
664 | 0 | if (na) |
665 | 0 | numaGetIValue(na, i, &index); |
666 | 0 | else |
667 | 0 | index = i + 1; |
668 | 0 | snprintf(textstr, sizeof(textstr), "%d", index); |
669 | 0 | pix2 = pixAddTextlines(pix1, bmf, textstr, val, location); |
670 | 0 | pixaAddPix(pixad, pix2, L_INSERT); |
671 | 0 | pixDestroy(&pix1); |
672 | 0 | } |
673 | |
|
674 | 0 | return pixad; |
675 | 0 | } |
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 | 0 | { |
715 | 0 | char *textstr; |
716 | 0 | l_int32 i, n, nstr; |
717 | 0 | PIX *pix1, *pix2; |
718 | 0 | PIXA *pixad; |
719 | |
|
720 | 0 | if (!pixas) |
721 | 0 | return (PIXA *)ERROR_PTR("pixas not defined", __func__, NULL); |
722 | 0 | if (!bmf) |
723 | 0 | return (PIXA *)ERROR_PTR("bmf not defined", __func__, NULL); |
724 | 0 | if (location != L_ADD_ABOVE && location != L_ADD_BELOW && |
725 | 0 | location != L_ADD_LEFT && location != L_ADD_RIGHT) |
726 | 0 | return (PIXA *)ERROR_PTR("invalid location", __func__, NULL); |
727 | | |
728 | 0 | n = pixaGetCount(pixas); |
729 | 0 | pixad = pixaCreate(n); |
730 | 0 | nstr = (sa) ? sarrayGetCount(sa) : 0; |
731 | 0 | if (nstr > 0 && nstr < n) |
732 | 0 | L_WARNING("There are %d strings and %d pix\n", __func__, nstr, n); |
733 | 0 | for (i = 0; i < n; i++) { |
734 | 0 | pix1 = pixaGetPix(pixas, i, L_CLONE); |
735 | 0 | if (i < nstr) |
736 | 0 | textstr = sarrayGetString(sa, i, L_NOCOPY); |
737 | 0 | else |
738 | 0 | textstr = pixGetText(pix1); |
739 | 0 | pix2 = pixAddTextlines(pix1, bmf, textstr, val, location); |
740 | 0 | pixaAddPix(pixad, pix2, L_INSERT); |
741 | 0 | pixDestroy(&pix1); |
742 | 0 | } |
743 | |
|
744 | 0 | return pixad; |
745 | 0 | } |
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 | 0 | { |
785 | 0 | l_int32 d; |
786 | 0 | L_BMF *bmf8; |
787 | 0 | PIX *pix1, *pix2, *pix3; |
788 | 0 | PIXCMAP *cmap; |
789 | |
|
790 | 0 | if (!pixa) |
791 | 0 | return ERROR_INT("pixa not defined", __func__, 1); |
792 | 0 | if (!pixs) |
793 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
794 | 0 | if (location != L_ADD_ABOVE && location != L_ADD_BELOW && |
795 | 0 | location != L_ADD_LEFT && location != L_ADD_RIGHT) |
796 | 0 | return ERROR_INT("invalid location", __func__, 1); |
797 | | |
798 | 0 | if (!textstr) { |
799 | 0 | textstr = pixGetText(pixs); |
800 | 0 | if (!textstr) { |
801 | 0 | L_WARNING("no textstring defined; inserting copy", __func__); |
802 | 0 | pixaAddPix(pixa, pixs, L_COPY); |
803 | 0 | return 0; |
804 | 0 | } |
805 | 0 | } |
806 | | |
807 | | /* Default font size is 8. */ |
808 | 0 | bmf8 = (bmf) ? bmf : bmfCreate(NULL, 8); |
809 | |
|
810 | 0 | if (reduction != 1) |
811 | 0 | pix1 = pixScaleByIntSampling(pixs, reduction); |
812 | 0 | else |
813 | 0 | 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 | 0 | cmap = pixGetColormap(pix1); |
819 | 0 | d = pixGetDepth(pix1); |
820 | 0 | if (!cmap && d != 32) |
821 | 0 | pix2 = pixConvertTo32(pix1); |
822 | 0 | else |
823 | 0 | pix2 = pixClone(pix1); |
824 | |
|
825 | 0 | pix3 = pixAddTextlines(pix2, bmf, textstr, val, location); |
826 | 0 | pixDestroy(&pix1); |
827 | 0 | pixDestroy(&pix2); |
828 | 0 | if (!bmf) bmfDestroy(&bmf8); |
829 | 0 | if (!pix3) |
830 | 0 | return ERROR_INT("pix3 not made", __func__, 1); |
831 | | |
832 | 0 | pixaAddPix(pixa, pix3, L_INSERT); |
833 | 0 | return 0; |
834 | 0 | } |
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 | 0 | { |
863 | 0 | char *linestr; |
864 | 0 | l_int32 i, ifirst, sumw, newsum, w, nwords, nlines, len, xwidth; |
865 | 0 | NUMA *na; |
866 | 0 | SARRAY *sa, *sawords; |
867 | |
|
868 | 0 | if (!bmf) |
869 | 0 | return (SARRAY *)ERROR_PTR("bmf not defined", __func__, NULL); |
870 | 0 | if (!textstr) |
871 | 0 | return (SARRAY *)ERROR_PTR("teststr not defined", __func__, NULL); |
872 | | |
873 | 0 | if ((sawords = sarrayCreateWordsFromString(textstr)) == NULL) |
874 | 0 | return (SARRAY *)ERROR_PTR("sawords not made", __func__, NULL); |
875 | | |
876 | 0 | if ((na = bmfGetWordWidths(bmf, textstr, sawords)) == NULL) { |
877 | 0 | sarrayDestroy(&sawords); |
878 | 0 | return (SARRAY *)ERROR_PTR("na not made", __func__, NULL); |
879 | 0 | } |
880 | 0 | nwords = numaGetCount(na); |
881 | 0 | if (nwords == 0) { |
882 | 0 | sarrayDestroy(&sawords); |
883 | 0 | numaDestroy(&na); |
884 | 0 | return (SARRAY *)ERROR_PTR("no words in textstr", __func__, NULL); |
885 | 0 | } |
886 | 0 | bmfGetWidth(bmf, 'x', &xwidth); |
887 | |
|
888 | 0 | sa = sarrayCreate(0); |
889 | 0 | ifirst = 0; |
890 | 0 | numaGetIValue(na, 0, &w); |
891 | 0 | sumw = firstindent * xwidth + w; |
892 | 0 | for (i = 1; i < nwords; i++) { |
893 | 0 | numaGetIValue(na, i, &w); |
894 | 0 | newsum = sumw + bmf->spacewidth + w; |
895 | 0 | if (newsum > maxw) { |
896 | 0 | linestr = sarrayToStringRange(sawords, ifirst, i - ifirst, 2); |
897 | 0 | if (!linestr) |
898 | 0 | continue; |
899 | 0 | len = strlen(linestr); |
900 | 0 | if (len > 0) /* it should always be */ |
901 | 0 | linestr[len - 1] = '\0'; /* remove the last space */ |
902 | 0 | sarrayAddString(sa, linestr, L_INSERT); |
903 | 0 | ifirst = i; |
904 | 0 | sumw = w; |
905 | 0 | } |
906 | 0 | else |
907 | 0 | sumw += bmf->spacewidth + w; |
908 | 0 | } |
909 | 0 | linestr = sarrayToStringRange(sawords, ifirst, nwords - ifirst, 2); |
910 | 0 | if (linestr) |
911 | 0 | sarrayAddString(sa, linestr, L_INSERT); |
912 | 0 | nlines = sarrayGetCount(sa); |
913 | 0 | *ph = nlines * bmf->lineheight + (nlines - 1) * bmf->vertlinesep; |
914 | |
|
915 | 0 | sarrayDestroy(&sawords); |
916 | 0 | numaDestroy(&na); |
917 | 0 | return sa; |
918 | 0 | } |
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 | 0 | { |
935 | 0 | char *wordstr; |
936 | 0 | l_int32 i, nwords, width; |
937 | 0 | NUMA *na; |
938 | |
|
939 | 0 | if (!bmf) |
940 | 0 | return (NUMA *)ERROR_PTR("bmf not defined", __func__, NULL); |
941 | 0 | if (!textstr) |
942 | 0 | return (NUMA *)ERROR_PTR("teststr not defined", __func__, NULL); |
943 | 0 | if (!sa) |
944 | 0 | return (NUMA *)ERROR_PTR("sa not defined", __func__, NULL); |
945 | | |
946 | 0 | nwords = sarrayGetCount(sa); |
947 | 0 | if ((na = numaCreate(nwords)) == NULL) |
948 | 0 | return (NUMA *)ERROR_PTR("na not made", __func__, NULL); |
949 | | |
950 | 0 | for (i = 0; i < nwords; i++) { |
951 | 0 | wordstr = sarrayGetString(sa, i, L_NOCOPY); |
952 | 0 | bmfGetStringWidth(bmf, wordstr, &width); |
953 | 0 | numaAddNumber(na, width); |
954 | 0 | } |
955 | |
|
956 | 0 | return na; |
957 | 0 | } |
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 | 0 | { |
974 | 0 | char chr; |
975 | 0 | l_int32 i, w, width, nchar; |
976 | |
|
977 | 0 | if (!bmf) |
978 | 0 | return ERROR_INT("bmf not defined", __func__, 1); |
979 | 0 | if (!textstr) |
980 | 0 | return ERROR_INT("teststr not defined", __func__, 1); |
981 | 0 | if (!pw) |
982 | 0 | return ERROR_INT("&w not defined", __func__, 1); |
983 | | |
984 | 0 | nchar = strlen(textstr); |
985 | 0 | w = 0; |
986 | 0 | for (i = 0; i < nchar; i++) { |
987 | 0 | chr = textstr[i]; |
988 | 0 | bmfGetWidth(bmf, chr, &width); |
989 | 0 | if (width != UNDEF) |
990 | 0 | w += width + bmf->kernwidth; |
991 | 0 | } |
992 | 0 | w -= bmf->kernwidth; /* remove last one */ |
993 | |
|
994 | 0 | *pw = w; |
995 | 0 | return 0; |
996 | 0 | } |
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 | 0 | { |
1015 | 0 | char *linestr, *parastring; |
1016 | 0 | l_int32 nlines, i, allwhite, leadwhite; |
1017 | 0 | SARRAY *salines, *satemp, *saout; |
1018 | |
|
1019 | 0 | if (!textstr) |
1020 | 0 | return (SARRAY *)ERROR_PTR("textstr not defined", __func__, NULL); |
1021 | | |
1022 | 0 | if ((salines = sarrayCreateLinesFromString(textstr, 1)) == NULL) |
1023 | 0 | return (SARRAY *)ERROR_PTR("salines not made", __func__, NULL); |
1024 | 0 | nlines = sarrayGetCount(salines); |
1025 | 0 | saout = sarrayCreate(0); |
1026 | 0 | satemp = sarrayCreate(0); |
1027 | |
|
1028 | 0 | linestr = sarrayGetString(salines, 0, L_NOCOPY); |
1029 | 0 | sarrayAddString(satemp, linestr, L_COPY); |
1030 | 0 | for (i = 1; i < nlines; i++) { |
1031 | 0 | linestr = sarrayGetString(salines, i, L_NOCOPY); |
1032 | 0 | stringAllWhitespace(linestr, &allwhite); |
1033 | 0 | stringLeadingWhitespace(linestr, &leadwhite); |
1034 | 0 | if ((splitflag == SPLIT_ON_LEADING_WHITE && leadwhite) || |
1035 | 0 | (splitflag == SPLIT_ON_BLANK_LINE && allwhite) || |
1036 | 0 | (splitflag == SPLIT_ON_BOTH && (allwhite || leadwhite))) { |
1037 | 0 | parastring = sarrayToString(satemp, 1); /* add nl to each line */ |
1038 | 0 | sarrayAddString(saout, parastring, L_INSERT); |
1039 | 0 | sarrayDestroy(&satemp); |
1040 | 0 | satemp = sarrayCreate(0); |
1041 | 0 | } |
1042 | 0 | sarrayAddString(satemp, linestr, L_COPY); |
1043 | 0 | } |
1044 | 0 | parastring = sarrayToString(satemp, 1); /* add nl to each line */ |
1045 | 0 | sarrayAddString(saout, parastring, L_INSERT); |
1046 | 0 | sarrayDestroy(&satemp); |
1047 | 0 | sarrayDestroy(&salines); |
1048 | 0 | return saout; |
1049 | 0 | } |
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 | 0 | { |
1063 | 0 | l_int32 len, i; |
1064 | |
|
1065 | 0 | if (!textstr) |
1066 | 0 | return ERROR_INT("textstr not defined", __func__, 1); |
1067 | 0 | if (!pval) |
1068 | 0 | return ERROR_INT("&va not defined", __func__, 1); |
1069 | | |
1070 | 0 | len = strlen(textstr); |
1071 | 0 | *pval = 1; |
1072 | 0 | for (i = 0; i < len; i++) { |
1073 | 0 | if (textstr[i] != ' ' && textstr[i] != '\t' && textstr[i] != '\n') { |
1074 | 0 | *pval = 0; |
1075 | 0 | return 0; |
1076 | 0 | } |
1077 | 0 | } |
1078 | 0 | return 0; |
1079 | 0 | } |
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 | 0 | { |
1093 | 0 | if (!textstr) |
1094 | 0 | return ERROR_INT("textstr not defined", __func__, 1); |
1095 | 0 | if (!pval) |
1096 | 0 | return ERROR_INT("&va not defined", __func__, 1); |
1097 | | |
1098 | 0 | *pval = 0; |
1099 | 0 | if (textstr[0] == ' ' || textstr[0] == '\t') |
1100 | 0 | *pval = 1; |
1101 | |
|
1102 | 0 | return 0; |
1103 | 0 | } |