source: mainline/uspace/lib/gfxfont/src/text.c

Last change on this file was f2d4a46, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Abbreviation in text mode

  • Property mode set to 100644
File size: 11.0 KB
Line 
1/*
2 * Copyright (c) 2022 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libgfxfont
30 * @{
31 */
32/**
33 * @file Text rendering
34 */
35
36#include <errno.h>
37#include <gfx/bitmap.h>
38#include <gfx/color.h>
39#include <gfx/font.h>
40#include <gfx/glyph.h>
41#include <gfx/render.h>
42#include <gfx/text.h>
43#include <io/pixelmap.h>
44#include <mem.h>
45#include <str.h>
46#include "../private/font.h"
47#include "../private/typeface.h"
48
49/** Initialize text formatting structure.
50 *
51 * Text formatting structure must always be initialized using this function
52 * first.
53 *
54 * @param fmt Text formatting structure
55 */
56void gfx_text_fmt_init(gfx_text_fmt_t *fmt)
57{
58 memset(fmt, 0, sizeof(gfx_text_fmt_t));
59}
60
61/** Compute text width.
62 *
63 * @param font Font
64 * @param str String
65 * @return Text width
66 */
67gfx_coord_t gfx_text_width(gfx_font_t *font, const char *str)
68{
69 gfx_glyph_metrics_t gmetrics;
70 size_t stradv;
71 const char *cp;
72 gfx_glyph_t *glyph;
73 gfx_coord_t width;
74 errno_t rc;
75
76 if ((font->finfo->props.flags & gff_text_mode) != 0)
77 return str_width(str);
78
79 width = 0;
80 cp = str;
81 while (*cp != '\0') {
82 rc = gfx_font_search_glyph(font, cp, &glyph, &stradv);
83 if (rc != EOK) {
84 ++cp;
85 continue;
86 }
87
88 gfx_glyph_get_metrics(glyph, &gmetrics);
89
90 cp += stradv;
91 width += gmetrics.advance;
92 }
93
94 return width;
95}
96
97/** Print string using text characters in text mode.
98 *
99 * @param pos Position of top-left corner of text
100 * @param fmt Formatting
101 * @param str String
102 * @return EOK on success or an error code
103 */
104static errno_t gfx_puttext_textmode(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
105 const char *str)
106{
107 gfx_context_t *gc = fmt->font->typeface->gc;
108 gfx_bitmap_params_t params;
109 gfx_bitmap_t *bitmap;
110 gfx_bitmap_alloc_t alloc;
111 gfx_coord_t width;
112 uint8_t attr;
113 pixelmap_t pmap;
114 gfx_coord_t x;
115 gfx_coord_t rmargin;
116 pixel_t pixel;
117 char32_t c;
118 size_t off;
119 bool ellipsis;
120 errno_t rc;
121
122 width = str_width(str);
123 if (fmt->abbreviate && width > fmt->width) {
124 ellipsis = true;
125 width = fmt->width;
126 if (width > 3)
127 rmargin = width - 3;
128 else
129 rmargin = width;
130 } else {
131 ellipsis = false;
132 rmargin = width;
133 }
134
135 /*
136 * NOTE: Creating and destroying bitmap each time is not probably
137 * the most efficient way.
138 */
139
140 gfx_color_get_ega(fmt->color, &attr);
141
142 gfx_bitmap_params_init(&params);
143 params.rect.p0.x = 0;
144 params.rect.p0.y = 0;
145 params.rect.p1.x = width;
146 params.rect.p1.y = 1;
147
148 if (params.rect.p1.x == 0) {
149 /* Nothing to do. Avoid creating bitmap of zero width. */
150 return EOK;
151 }
152
153 rc = gfx_bitmap_create(gc, &params, NULL, &bitmap);
154 if (rc != EOK)
155 return rc;
156
157 rc = gfx_bitmap_get_alloc(bitmap, &alloc);
158 if (rc != EOK) {
159 gfx_bitmap_destroy(bitmap);
160 return rc;
161 }
162
163 pmap.width = params.rect.p1.x;
164 pmap.height = 1;
165 pmap.data = alloc.pixels;
166
167 off = 0;
168 for (x = 0; x < rmargin; x++) {
169 c = str_decode(str, &off, STR_NO_LIMIT);
170 pixel = PIXEL(attr,
171 (c >> 16) & 0xff,
172 (c >> 8) & 0xff,
173 c & 0xff);
174 pixelmap_put_pixel(&pmap, x, 0, pixel);
175 }
176
177 if (ellipsis) {
178 for (x = rmargin; x < params.rect.p1.x; x++) {
179 c = '.';
180 pixel = PIXEL(attr,
181 (c >> 16) & 0xff,
182 (c >> 8) & 0xff,
183 c & 0xff);
184 pixelmap_put_pixel(&pmap, x, 0, pixel);
185 }
186 }
187
188 rc = gfx_bitmap_render(bitmap, NULL, pos);
189
190 gfx_bitmap_destroy(bitmap);
191 return rc;
192}
193
194/** Get text starting position.
195 *
196 * @param pos Anchor position
197 * @param fmt Text formatting
198 * @param str String
199 * @param spos Place to store starting position
200 */
201void gfx_text_start_pos(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
202 const char *str, gfx_coord2_t *spos)
203{
204 gfx_font_metrics_t fmetrics;
205 gfx_coord_t width;
206
207 *spos = *pos;
208
209 /* Adjust position for horizontal alignment */
210 if (fmt->halign != gfx_halign_left) {
211 /* Compute text width */
212 width = gfx_text_width(fmt->font, str);
213 if (fmt->abbreviate && width > fmt->width)
214 width = fmt->width;
215
216 switch (fmt->halign) {
217 case gfx_halign_center:
218 spos->x -= width / 2;
219 break;
220 case gfx_halign_right:
221 spos->x -= width;
222 break;
223 default:
224 break;
225 }
226 }
227
228 /* Adjust position for vertical alignment */
229 gfx_font_get_metrics(fmt->font, &fmetrics);
230
231 if (fmt->valign != gfx_valign_baseline) {
232 switch (fmt->valign) {
233 case gfx_valign_top:
234 spos->y += fmetrics.ascent;
235 break;
236 case gfx_valign_center:
237 spos->y += fmetrics.ascent / 2;
238 break;
239 case gfx_valign_bottom:
240 spos->y -= fmetrics.descent + 1;
241 break;
242 default:
243 break;
244 }
245 }
246}
247
248/** Render text.
249 *
250 * @param pos Anchor position
251 * @param fmt Text formatting
252 * @param str String
253 * @return EOK on success or an error code
254 */
255errno_t gfx_puttext(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str)
256{
257 gfx_glyph_metrics_t gmetrics;
258 gfx_font_metrics_t fmetrics;
259 size_t stradv;
260 const char *cp;
261 gfx_glyph_t *glyph;
262 gfx_coord2_t cpos;
263 gfx_coord2_t spos;
264 gfx_rect_t rect;
265 gfx_coord_t width;
266 gfx_coord_t rmargin;
267 bool ellipsis;
268 errno_t rc;
269
270 gfx_text_start_pos(pos, fmt, str, &spos);
271
272 /* Text mode */
273 if ((fmt->font->finfo->props.flags & gff_text_mode) != 0)
274 return gfx_puttext_textmode(&spos, fmt, str);
275
276 rc = gfx_set_color(fmt->font->typeface->gc, fmt->color);
277 if (rc != EOK)
278 return rc;
279
280 width = gfx_text_width(fmt->font, str);
281
282 if (fmt->abbreviate && width > fmt->width) {
283 /* Need to append ellipsis */
284 ellipsis = true;
285 rmargin = spos.x + fmt->width - gfx_text_width(fmt->font, "...");
286 } else {
287 ellipsis = false;
288 rmargin = spos.x + width;
289 }
290
291 cpos = spos;
292 cp = str;
293 while (*cp != '\0') {
294 rc = gfx_font_search_glyph(fmt->font, cp, &glyph, &stradv);
295 if (rc != EOK) {
296 ++cp;
297 continue;
298 }
299
300 gfx_glyph_get_metrics(glyph, &gmetrics);
301
302 /* Stop if we would run over the right margin */
303 if (fmt->abbreviate && cpos.x + gmetrics.advance > rmargin)
304 break;
305
306 rc = gfx_glyph_render(glyph, &cpos);
307 if (rc != EOK)
308 return rc;
309
310 cp += stradv;
311 cpos.x += gmetrics.advance;
312 }
313
314 /* Text underlining */
315 if (fmt->underline) {
316 gfx_font_get_metrics(fmt->font, &fmetrics);
317
318 rect.p0.x = spos.x;
319 rect.p0.y = spos.y + fmetrics.underline_y0;
320 rect.p1.x = cpos.x;
321 rect.p1.y = spos.y + fmetrics.underline_y1;
322
323 rc = gfx_fill_rect(fmt->font->typeface->gc, &rect);
324 if (rc != EOK)
325 return rc;
326 }
327
328 /* Render ellipsis, if required */
329 if (ellipsis) {
330 rc = gfx_font_search_glyph(fmt->font, ".", &glyph, &stradv);
331 if (rc != EOK)
332 return EOK;
333
334 gfx_glyph_get_metrics(glyph, &gmetrics);
335
336 rc = gfx_glyph_render(glyph, &cpos);
337 if (rc != EOK)
338 return rc;
339
340 cpos.x += gmetrics.advance;
341
342 rc = gfx_glyph_render(glyph, &cpos);
343 if (rc != EOK)
344 return rc;
345
346 cpos.x += gmetrics.advance;
347
348 rc = gfx_glyph_render(glyph, &cpos);
349 if (rc != EOK)
350 return rc;
351 }
352
353 return EOK;
354}
355
356/** Find character position in string by X coordinate.
357 *
358 * @param pos Anchor position
359 * @param fmt Text formatting
360 * @param str String
361 * @param fpos Position for which we need to find offset
362 *
363 * @return Byte offset in @a str of character corresponding to position
364 * @a fpos. Note that the position is rounded, that is,
365 * if it is before the center of character A, it will return
366 * offset of A, if it is after the center of A, it will return
367 * offset of the following character.
368 */
369size_t gfx_text_find_pos(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
370 const char *str, gfx_coord2_t *fpos)
371{
372 gfx_glyph_metrics_t gmetrics;
373 size_t stradv;
374 const char *cp;
375 gfx_glyph_t *glyph;
376 gfx_coord2_t cpos;
377 size_t off;
378 size_t strsize;
379 errno_t rc;
380
381 gfx_text_start_pos(pos, fmt, str, &cpos);
382
383 /* Text mode */
384 if ((fmt->font->finfo->props.flags & gff_text_mode) != 0) {
385 off = 0;
386 strsize = str_size(str);
387 while (off < strsize) {
388 if (fpos->x <= cpos.x)
389 return off;
390 (void) str_decode(str, &off, strsize);
391 cpos.x++;
392 }
393
394 return off;
395 }
396
397 cp = str;
398 off = 0;
399 while (*cp != '\0') {
400 rc = gfx_font_search_glyph(fmt->font, cp, &glyph, &stradv);
401 if (rc != EOK) {
402 ++cp;
403 continue;
404 }
405
406 gfx_glyph_get_metrics(glyph, &gmetrics);
407
408 if (fpos->x < cpos.x + gmetrics.advance / 2)
409 return off;
410
411 cp += stradv;
412 off += stradv;
413 cpos.x += gmetrics.advance;
414 }
415
416 return off;
417}
418
419/** Get text continuation parameters.
420 *
421 * Return the anchor position and format needed to continue printing
422 * text after the specified string. It is allowed for the sources
423 * (@a pos, @a fmt) and destinations (@a cpos, @a cfmt) to point
424 * to the same objects, respectively.
425 *
426 * @param pos Anchor position
427 * @param fmt Text formatting
428 * @param str String
429 * @param cpos Place to store anchor position for continuation
430 * @param cfmt Place to store format for continuation
431 */
432void gfx_text_cont(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str,
433 gfx_coord2_t *cpos, gfx_text_fmt_t *cfmt)
434{
435 gfx_coord2_t spos;
436 gfx_text_fmt_t tfmt;
437
438 /* Continuation should start where the current string ends */
439 gfx_text_start_pos(pos, fmt, str, &spos);
440 cpos->x = spos.x + gfx_text_width(fmt->font, str);
441 cpos->y = spos.y;
442
443 /*
444 * Formatting is the same, except the text should be aligned
445 * so that it starts at the anchor point.
446 */
447 tfmt = *fmt;
448 tfmt.halign = gfx_halign_left;
449 tfmt.valign = gfx_valign_baseline;
450
451 /* Remaining available width */
452 tfmt.width = fmt->width - (cpos->x - spos.x);
453
454 *cfmt = tfmt;
455}
456
457/** Get text bounding rectangle.
458 *
459 * @param pos Anchor position
460 * @param fmt Text formatting
461 * @param str String
462 * @param rect Place to store bounding rectangle
463 */
464void gfx_text_rect(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str,
465 gfx_rect_t *rect)
466{
467 gfx_coord2_t spos;
468 gfx_coord_t width;
469
470 gfx_text_start_pos(pos, fmt, str, &spos);
471 width = gfx_text_width(fmt->font, str);
472 if (fmt->abbreviate && width > fmt->width)
473 width = fmt->width;
474
475 rect->p0.x = spos.x;
476 rect->p0.y = spos.y - fmt->font->metrics.ascent;
477 rect->p1.x = spos.x + width;
478 rect->p1.y = spos.y + fmt->font->metrics.descent + 1;
479}
480
481/** @}
482 */
Note: See TracBrowser for help on using the repository browser.