source: mainline/uspace/lib/gfxfont/src/text.c@ 901b302

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 901b302 was a130983, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Implement text abbreviation

When rendering text, gfx_puttext can now abbreviate it with …
to fit within the specified width. Use on window captions.
Now Barber Pole can have a proper caption.

Note that now the caption is centered on the space between the left
edge and the buttons, instead on the entire title bar, so it
may look a little lopsided. It will look better once we put
something to the left side of the title bar.

  • Property mode set to 100644
File size: 10.6 KB
RevLine 
[8fa65af0]1/*
[5c27e77]2 * Copyright (c) 2022 Jiri Svoboda
[8fa65af0]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>
[6a87f28]37#include <gfx/bitmap.h>
[b433f68]38#include <gfx/color.h>
[8fa65af0]39#include <gfx/font.h>
40#include <gfx/glyph.h>
[b433f68]41#include <gfx/render.h>
[8fa65af0]42#include <gfx/text.h>
[6a87f28]43#include <io/pixelmap.h>
[8fa65af0]44#include <mem.h>
[6a87f28]45#include <str.h>
46#include "../private/font.h"
47#include "../private/typeface.h"
[8fa65af0]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
[6a87f28]76 if ((font->finfo->props.flags & gff_text_mode) != 0)
77 return str_width(str);
78
[8fa65af0]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
[6a87f28]97/** Print string using text characters in text mode.
98 *
99 * @param font Font
100 * @param pos Position of top-left corner of text
[b433f68]101 * @param color Text color
[6a87f28]102 * @param str String
103 * @return EOK on success or an error code
104 */
105static errno_t gfx_puttext_textmode(gfx_font_t *font, gfx_coord2_t *pos,
[b433f68]106 gfx_color_t *color, const char *str)
[6a87f28]107{
108 gfx_context_t *gc = font->typeface->gc;
109 gfx_bitmap_params_t params;
110 gfx_bitmap_t *bitmap;
111 gfx_bitmap_alloc_t alloc;
[bc52b5b]112 uint8_t attr;
[6a87f28]113 pixelmap_t pmap;
114 gfx_coord_t x;
115 pixel_t pixel;
[bc52b5b]116 char32_t c;
117 size_t off;
[6a87f28]118 errno_t rc;
119
120 /*
121 * NOTE: Creating and destroying bitmap each time is not probably
122 * the most efficient way.
123 */
124
[bc52b5b]125 gfx_color_get_ega(color, &attr);
[b433f68]126
[6a87f28]127 gfx_bitmap_params_init(&params);
128 params.rect.p0.x = 0;
129 params.rect.p0.y = 0;
130 params.rect.p1.x = str_width(str);
131 params.rect.p1.y = 1;
132
[17fac946]133 if (params.rect.p1.x == 0) {
134 /* Nothing to do. Avoid creating bitmap of zero width. */
135 return EOK;
136 }
137
[6a87f28]138 rc = gfx_bitmap_create(gc, &params, NULL, &bitmap);
139 if (rc != EOK)
140 return rc;
141
142 rc = gfx_bitmap_get_alloc(bitmap, &alloc);
143 if (rc != EOK) {
144 gfx_bitmap_destroy(bitmap);
145 return rc;
146 }
147
148 pmap.width = params.rect.p1.x;
149 pmap.height = 1;
150 pmap.data = alloc.pixels;
151
[bc52b5b]152 off = 0;
[6a87f28]153 for (x = 0; x < params.rect.p1.x; x++) {
[bc52b5b]154 c = str_decode(str, &off, STR_NO_LIMIT);
155 pixel = PIXEL(attr,
156 (c >> 16) & 0xff,
157 (c >> 8) & 0xff,
158 c & 0xff);
[6a87f28]159 pixelmap_put_pixel(&pmap, x, 0, pixel);
160 }
161
162 rc = gfx_bitmap_render(bitmap, NULL, pos);
163
164 gfx_bitmap_destroy(bitmap);
165 return rc;
166}
167
[d63623f]168/** Get text starting position.
[8fa65af0]169 *
170 * @param pos Anchor position
171 * @param fmt Text formatting
172 * @param str String
[d63623f]173 * @param spos Place to store starting position
[8fa65af0]174 */
[4583015]175void gfx_text_start_pos(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
176 const char *str, gfx_coord2_t *spos)
[8fa65af0]177{
178 gfx_font_metrics_t fmetrics;
179 gfx_coord_t width;
180
[d63623f]181 *spos = *pos;
[8fa65af0]182
183 /* Adjust position for horizontal alignment */
184 if (fmt->halign != gfx_halign_left) {
[a130983]185 /* Compute text width */
[4583015]186 width = gfx_text_width(fmt->font, str);
[a130983]187 if (fmt->abbreviate && width > fmt->width)
188 width = fmt->width;
189
[8fa65af0]190 switch (fmt->halign) {
191 case gfx_halign_center:
[d63623f]192 spos->x -= width / 2;
[8fa65af0]193 break;
194 case gfx_halign_right:
[400a16d]195 spos->x -= width;
[8fa65af0]196 break;
197 default:
198 break;
199 }
200 }
201
202 /* Adjust position for vertical alignment */
[4583015]203 gfx_font_get_metrics(fmt->font, &fmetrics);
[8fa65af0]204
[8bf9058]205 if (fmt->valign != gfx_valign_baseline) {
[8fa65af0]206 switch (fmt->valign) {
207 case gfx_valign_top:
[d63623f]208 spos->y += fmetrics.ascent;
[8fa65af0]209 break;
210 case gfx_valign_center:
[d63623f]211 spos->y += fmetrics.ascent / 2;
[8fa65af0]212 break;
[8bf9058]213 case gfx_valign_bottom:
[a0aeb8f]214 spos->y -= fmetrics.descent + 1;
[0d62c10]215 break;
[8fa65af0]216 default:
217 break;
218 }
219 }
[d63623f]220}
221
222/** Render text.
223 *
224 * @param pos Anchor position
225 * @param fmt Text formatting
226 * @param str String
227 * @return EOK on success or an error code
228 */
[4583015]229errno_t gfx_puttext(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str)
[d63623f]230{
231 gfx_glyph_metrics_t gmetrics;
[5c27e77]232 gfx_font_metrics_t fmetrics;
[d63623f]233 size_t stradv;
234 const char *cp;
235 gfx_glyph_t *glyph;
236 gfx_coord2_t cpos;
[5c27e77]237 gfx_coord2_t spos;
238 gfx_rect_t rect;
[a130983]239 gfx_coord_t width;
240 gfx_coord_t rmargin;
241 bool ellipsis;
[d63623f]242 errno_t rc;
243
[4583015]244 gfx_text_start_pos(pos, fmt, str, &spos);
[8fa65af0]245
[6a87f28]246 /* Text mode */
[4583015]247 if ((fmt->font->finfo->props.flags & gff_text_mode) != 0)
248 return gfx_puttext_textmode(fmt->font, &spos, fmt->color, str);
[b433f68]249
[4583015]250 rc = gfx_set_color(fmt->font->typeface->gc, fmt->color);
[b433f68]251 if (rc != EOK)
252 return rc;
[6a87f28]253
[a130983]254 width = gfx_text_width(fmt->font, str);
255
256 if (fmt->abbreviate && width > fmt->width) {
257 /* Need to append ellipsis */
258 ellipsis = true;
259 rmargin = spos.x + fmt->width - gfx_text_width(fmt->font, "...");
260 } else {
261 ellipsis = false;
262 rmargin = spos.x + width;
263 }
264
[5c27e77]265 cpos = spos;
[8fa65af0]266 cp = str;
267 while (*cp != '\0') {
[4583015]268 rc = gfx_font_search_glyph(fmt->font, cp, &glyph, &stradv);
[8fa65af0]269 if (rc != EOK) {
270 ++cp;
271 continue;
272 }
273
274 gfx_glyph_get_metrics(glyph, &gmetrics);
275
[a130983]276 /* Stop if we would run over the right margin */
277 if (fmt->abbreviate && cpos.x + gmetrics.advance > rmargin)
278 break;
279
[8fa65af0]280 rc = gfx_glyph_render(glyph, &cpos);
281 if (rc != EOK)
282 return rc;
283
284 cp += stradv;
285 cpos.x += gmetrics.advance;
286 }
287
[5c27e77]288 /* Text underlining */
289 if (fmt->underline) {
[4583015]290 gfx_font_get_metrics(fmt->font, &fmetrics);
[5c27e77]291
292 rect.p0.x = spos.x;
293 rect.p0.y = spos.y + fmetrics.underline_y0;
294 rect.p1.x = cpos.x;
295 rect.p1.y = spos.y + fmetrics.underline_y1;
296
[4583015]297 rc = gfx_fill_rect(fmt->font->typeface->gc, &rect);
[5c27e77]298 if (rc != EOK)
299 return rc;
300 }
301
[a130983]302 /* Render ellipsis, if required */
303 if (ellipsis) {
304 rc = gfx_font_search_glyph(fmt->font, ".", &glyph, &stradv);
305 if (rc != EOK)
306 return EOK;
307
308 gfx_glyph_get_metrics(glyph, &gmetrics);
309
310 rc = gfx_glyph_render(glyph, &cpos);
311 if (rc != EOK)
312 return rc;
313
314 cpos.x += gmetrics.advance;
315
316 rc = gfx_glyph_render(glyph, &cpos);
317 if (rc != EOK)
318 return rc;
319
320 cpos.x += gmetrics.advance;
321
322 rc = gfx_glyph_render(glyph, &cpos);
323 if (rc != EOK)
324 return rc;
325 }
326
[8fa65af0]327 return EOK;
328}
329
[d63623f]330/** Find character position in string by X coordinate.
331 *
332 * @param pos Anchor position
333 * @param fmt Text formatting
334 * @param str String
335 * @param fpos Position for which we need to find offset
336 *
337 * @return Byte offset in @a str of character corresponding to position
338 * @a fpos. Note that the position is rounded, that is,
339 * if it is before the center of character A, it will return
340 * offset of A, if it is after the center of A, it will return
341 * offset of the following character.
342 */
[4583015]343size_t gfx_text_find_pos(gfx_coord2_t *pos, gfx_text_fmt_t *fmt,
344 const char *str, gfx_coord2_t *fpos)
[d63623f]345{
346 gfx_glyph_metrics_t gmetrics;
347 size_t stradv;
348 const char *cp;
349 gfx_glyph_t *glyph;
350 gfx_coord2_t cpos;
351 size_t off;
352 size_t strsize;
353 errno_t rc;
354
[4583015]355 gfx_text_start_pos(pos, fmt, str, &cpos);
[d63623f]356
357 /* Text mode */
[4583015]358 if ((fmt->font->finfo->props.flags & gff_text_mode) != 0) {
[d63623f]359 off = 0;
360 strsize = str_size(str);
361 while (off < strsize) {
362 if (fpos->x <= cpos.x)
363 return off;
364 (void) str_decode(str, &off, strsize);
365 cpos.x++;
366 }
367
368 return off;
369 }
370
371 cp = str;
372 off = 0;
373 while (*cp != '\0') {
[4583015]374 rc = gfx_font_search_glyph(fmt->font, cp, &glyph, &stradv);
[d63623f]375 if (rc != EOK) {
376 ++cp;
377 continue;
378 }
379
380 gfx_glyph_get_metrics(glyph, &gmetrics);
381
382 if (fpos->x < cpos.x + gmetrics.advance / 2)
383 return off;
384
385 cp += stradv;
386 off += stradv;
387 cpos.x += gmetrics.advance;
388 }
389
390 return off;
391}
392
[9eb8d12]393/** Get text continuation parameters.
394 *
395 * Return the anchor position and format needed to continue printing
396 * text after the specified string. It is allowed for the sources
397 * (@a pos, @a fmt) and destinations (@a cpos, @a cfmt) to point
398 * to the same objects, respectively.
399 *
400 * @param pos Anchor position
401 * @param fmt Text formatting
402 * @param str String
403 * @param cpos Place to store anchor position for continuation
404 * @param cfmt Place to store format for continuation
405 */
[4583015]406void gfx_text_cont(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str,
407 gfx_coord2_t *cpos, gfx_text_fmt_t *cfmt)
[9eb8d12]408{
409 gfx_coord2_t spos;
410 gfx_text_fmt_t tfmt;
411
412 /* Continuation should start where the current string ends */
[4583015]413 gfx_text_start_pos(pos, fmt, str, &spos);
414 cpos->x = spos.x + gfx_text_width(fmt->font, str);
[9eb8d12]415 cpos->y = spos.y;
416
417 /*
418 * Formatting is the same, except the text should be aligned
419 * so that it starts at the anchor point.
420 */
421 tfmt = *fmt;
422 tfmt.halign = gfx_halign_left;
423 tfmt.valign = gfx_valign_baseline;
424
[a130983]425 /* Remaining available width */
426 tfmt.width = fmt->width - (cpos->x - spos.x);
427
[9eb8d12]428 *cfmt = tfmt;
429}
430
431/** Get text bounding rectangle.
432 *
433 * @param pos Anchor position
434 * @param fmt Text formatting
435 * @param str String
436 * @param rect Place to store bounding rectangle
437 */
[4583015]438void gfx_text_rect(gfx_coord2_t *pos, gfx_text_fmt_t *fmt, const char *str,
439 gfx_rect_t *rect)
[9eb8d12]440{
441 gfx_coord2_t spos;
[a130983]442 gfx_coord_t width;
[9eb8d12]443
[4583015]444 gfx_text_start_pos(pos, fmt, str, &spos);
[a130983]445 width = gfx_text_width(fmt->font, str);
446 if (fmt->abbreviate && width > fmt->width)
447 width = fmt->width;
[9eb8d12]448
449 rect->p0.x = spos.x;
[4583015]450 rect->p0.y = spos.y - fmt->font->metrics.ascent;
[a130983]451 rect->p1.x = spos.x + width;
[4583015]452 rect->p1.y = spos.y + fmt->font->metrics.descent + 1;
[9eb8d12]453}
454
[8fa65af0]455/** @}
456 */
Note: See TracBrowser for help on using the repository browser.