Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_gui_md.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
36
37#include "nilorea/n_gui_md.h"
38
39#include "nilorea/n_wrap.h"
40
41#include <allegro5/allegro_primitives.h>
42
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46
50#define MD_COLS_MAX 32
51
53enum { MD_AT_SPACE = 1,
55
59typedef struct {
60 ALLEGRO_FONT* font;
61 const char* url;
62 float w;
63 unsigned char nbytes;
64 unsigned char colid;
65 unsigned char flags;
66 char bytes[4];
67} md_atom;
68
70enum { MD_COL_TEXT = 0,
75
78typedef struct {
79 float px, py;
80 char* out;
81 size_t cap;
82 int found;
83} md_hit;
84
87enum { MD_MEASURE = 0,
90
91/* ---- helpers ------------------------------------------------------------ */
92
94static int md_cp_len(const char* s, int i, int len) {
95 int n = 1;
96 if (i >= len)
97 return 0;
98 while (i + n < len && ((unsigned char)s[i + n] & 0xC0) == 0x80)
99 n++;
100 return n;
101}
102
105static ALLEGRO_FONT* md_font_for(const N_GUI_MD_FONTS* f, unsigned style) {
106 ALLEGRO_FONT* r = f->regular;
107 if (style & N_MARKDOWN_CODE)
108 return f->mono ? f->mono : r;
109 if ((style & N_MARKDOWN_BOLD) && (style & N_MARKDOWN_ITALIC))
110 return f->bolditalic ? f->bolditalic : (f->bold ? f->bold : (f->italic ? f->italic : r));
111 if (style & N_MARKDOWN_BOLD)
112 return f->bold ? f->bold : r;
113 if (style & N_MARKDOWN_ITALIC)
114 return f->italic ? f->italic : r;
115 return r;
116}
117
119static ALLEGRO_FONT* md_font_heading(const N_GUI_MD_FONTS* f, int level) {
120 ALLEGRO_FONT* want = NULL;
121 if (level <= 1)
122 want = f->h1;
123 else if (level == 2)
124 want = f->h2;
125 else
126 want = f->h3;
127 if (want)
128 return want;
129 return f->bold ? f->bold : f->regular;
130}
131
132static ALLEGRO_COLOR md_col(const N_GUI_MD_STYLE* st, unsigned char colid) {
133 switch (colid) {
134 case MD_COL_LINK:
135 return st->link;
136 case MD_COL_CODE:
137 return st->code;
138 case MD_COL_HEADING:
139 return st->heading;
140 case MD_COL_QUOTE:
141 return st->quote;
142 default:
143 break;
144 }
145 return st->text;
146}
147
155static ALLEGRO_COLOR md_fade(ALLEGRO_COLOR c, float a) {
156 c.r *= a;
157 c.g *= a;
158 c.b *= a;
159 c.a *= a;
160 return c;
161}
162
164static float md_lh(const N_GUI_MD_FONTS* f, ALLEGRO_FONT* font) {
165 return (float)al_get_font_line_height(font ? font : f->regular);
166}
167
168/* ---- one flow of inline spans -------------------------------------------- */
169
172static void md_wrap_of(const md_atom* a, n_wrap_item* w) {
173 w->width = (double)a->w;
174 w->flags = ((a->flags & MD_AT_SPACE) ? N_WRAP_SPACE : 0u) |
175 ((a->flags & MD_AT_NEWLINE) ? N_WRAP_NEWLINE : 0u);
176}
177
181static int md_build_seg(md_atom* out, n_wrap_item* wrap, int cap, int na, const char* text, int len, ALLEGRO_FONT* font, unsigned char colid, const char* url, int hard_nl) {
182 int i = 0;
183 while (i < len && na < cap) {
184 md_atom* a = &out[na];
185 a->font = font;
186 a->colid = colid;
187 a->url = url;
188 a->flags = 0;
189 a->nbytes = 0;
190 a->bytes[0] = '\0';
191 if (text[i] == '\n' && !hard_nl) {
192 /* A soft newline is a space: one the wrap may break at, and one
193 that is drawn if it does not. */
194 a->flags = MD_AT_SPACE;
195 a->nbytes = 1;
196 a->bytes[0] = ' ';
197 a->bytes[1] = '\0';
198 a->w = (float)al_get_text_width(font, " ");
199 md_wrap_of(a, &wrap[na]);
200 na++;
201 i++;
202 continue;
203 }
204 if (text[i] == '\n') {
205 a->flags = MD_AT_NEWLINE;
206 a->w = 0.0f;
207 md_wrap_of(a, &wrap[na]);
208 na++;
209 i++;
210 continue;
211 }
212 {
213 int bl = md_cp_len(text, i, len);
214 char g[8];
215 if (bl > 4)
216 bl = 4;
217 memcpy(g, text + i, (size_t)bl);
218 g[bl] = '\0';
219 a->w = (float)al_get_text_width(font, g);
220 a->nbytes = (unsigned char)bl;
221 memcpy(a->bytes, g, (size_t)bl);
222 if (text[i] == ' ' || text[i] == '\t')
223 a->flags = MD_AT_SPACE;
224 md_wrap_of(a, &wrap[na]);
225 na++;
226 i += bl;
227 }
228 }
229 return na;
230}
231
234typedef struct {
235 const md_atom* a;
237 float x, y, lh, avail;
238 int mode;
239 unsigned char align;
242
246static void md_flush_line(const md_line_ctx* c, size_t from, size_t to, float y, float line_w) {
247 char buf[1024];
248 size_t k;
249 int blen = 0;
250 ALLEGRO_FONT* bfont = NULL;
251 unsigned char bcol = 0;
252 float x0 = c->x, rx, bx;
253
254 /* Alignment is applied here rather than in the wrap, which reports the
255 * line's own width precisely so that this can. */
257 x0 += (c->avail - line_w) * 0.5f;
258 else if (c->align == N_MARKDOWN_ALIGN_RIGHT)
259 x0 += c->avail - line_w;
260 if (x0 < c->x)
261 x0 = c->x;
262 rx = x0;
263 bx = x0;
264
265 if (c->mode == MD_HIT) {
266 for (k = from; k < to; k++) {
267 const md_atom* at = &c->a[k];
268 if (at->url && c->hit->px >= rx && c->hit->px < rx + at->w &&
269 c->hit->py >= y && c->hit->py < y + c->lh) {
270 if (c->hit->cap)
271 (void)snprintf(c->hit->out, c->hit->cap, "%s", at->url);
272 c->hit->found = 1;
273 }
274 rx += at->w;
275 }
276 return;
277 }
278 for (k = from; k < to; k++) {
279 const md_atom* at = &c->a[k];
280 if (!at->nbytes)
281 continue;
282 if (blen && (at->font != bfont || at->colid != bcol ||
283 blen + at->nbytes >= (int)sizeof(buf))) {
284 al_draw_text(bfont, md_col(c->st, bcol), bx, y, 0, buf);
285 blen = 0;
286 }
287 if (blen == 0) {
288 bfont = at->font;
289 bcol = at->colid;
290 bx = rx;
291 }
292 memcpy(buf + blen, at->bytes, at->nbytes);
293 blen += at->nbytes;
294 buf[blen] = '\0';
295 rx += at->w;
296 }
297 if (blen)
298 al_draw_text(bfont, md_col(c->st, bcol), bx, y, 0, buf);
299}
300
301// cppcheck-suppress constParameterCallback ; callback signature must match n_wrap_emit
302static void md_emit_line(size_t first, size_t last, size_t line, double width, void* user) {
303 const md_line_ctx* c = (const md_line_ctx*)user;
304 md_flush_line(c, first, last, c->y + (float)line * c->lh, (float)width);
305}
306
318static float md_flow(const N_GUI_MD_FONTS* f, const N_GUI_MD_STYLE* st, const char* prefix, const n_markdown_span* spans, size_t nspans, float x, float y, float w, float lh, ALLEGRO_FONT* base_font, unsigned style_or, unsigned char colid, unsigned char align, int hard_nl, int mode, md_hit* hit) {
319 size_t total = prefix ? strlen(prefix) : 0, i;
320 md_atom* atoms;
321 n_wrap_item* wrap;
322 md_line_ctx ctx;
323 int na = 0;
324 size_t lines;
325
326 for (i = 0; i < nspans; i++)
327 total += spans[i].text ? strlen(spans[i].text) : 0;
328 if (total == 0)
329 return lh; /* an empty flow still occupies its line */
330 if (w < 1.0f)
331 w = 1.0f;
332 /* One atom per codepoint at most, so the byte count bounds both arrays;
333 * one allocation for the pair keeps the failure path single. */
334 atoms = malloc(total * (sizeof(*atoms) + sizeof(*wrap)));
335 if (!atoms)
336 return lh;
337 wrap = (n_wrap_item*)(void*)(atoms + total);
338 if (prefix)
339 na = md_build_seg(atoms, wrap, (int)total, na, prefix, (int)strlen(prefix),
340 base_font ? base_font : f->regular, colid, NULL, hard_nl);
341 for (i = 0; i < nspans; i++) {
342 unsigned sty = spans[i].style | style_or;
343 int is_link = (sty & N_MARKDOWN_LINK) ? 1 : 0;
344 int is_code = (sty & N_MARKDOWN_CODE) ? 1 : 0;
345 unsigned char cid = colid;
346 ALLEGRO_FONT* font;
347 if (is_link)
348 cid = MD_COL_LINK;
349 else if (is_code)
350 cid = MD_COL_CODE;
351 if (!spans[i].text)
352 continue;
353 if (base_font)
354 font = (is_code && f->mono) ? f->mono : base_font;
355 else
356 font = md_font_for(f, sty);
357 na = md_build_seg(atoms, wrap, (int)total, na, spans[i].text,
358 (int)strlen(spans[i].text), font, cid,
359 is_link ? spans[i].url : NULL,
360 /* a code span keeps its own breaks whatever the
361 document asks for: it is preformatted text */
362 hard_nl || (sty & N_MARKDOWN_CODE) ? 1 : 0);
363 }
364
365 ctx.a = atoms;
366 ctx.st = st;
367 ctx.x = x;
368 ctx.y = y;
369 ctx.lh = lh;
370 ctx.avail = w;
371 ctx.mode = mode;
372 ctx.align = align;
373 ctx.hit = hit;
374 lines = n_wrap_layout(wrap, (size_t)na, (double)w,
375 mode == MD_MEASURE ? NULL : md_emit_line, &ctx);
376 free(atoms);
377 return (float)lines * lh;
378}
379
383static float md_flow_width(const N_GUI_MD_FONTS* f, const n_markdown_span* spans, size_t nspans, unsigned style_or) {
384 float sum = 0.0f;
385 size_t i;
386 for (i = 0; i < nspans; i++) {
387 ALLEGRO_FONT* font = md_font_for(f, spans[i].style | style_or);
388 const char* t = spans[i].text;
389 int len = t ? (int)strlen(t) : 0;
390 int j = 0;
391 while (j < len) {
392 int bl = md_cp_len(t, j, len);
393 char g[8];
394 if (bl > 4)
395 bl = 4;
396 memcpy(g, t + j, (size_t)bl);
397 g[bl] = '\0';
398 sum += (float)al_get_text_width(font, g);
399 j += bl;
400 }
401 }
402 return sum;
403}
404
405/* ---- tables -------------------------------------------------------------- */
406
420static void md_table_cols(const N_GUI_MD_FONTS* f, const N_GUI_MD_STYLE* st, const n_markdown_block* b, float avail, float* out) {
421 size_t cols = b->n_cols, rows = b->n_cols ? b->n_items / b->n_cols : 0;
422 size_t c, r;
423 float pad2 = st->cell_pad * 2.0f, sum = 0.0f;
424
425 if (cols > MD_COLS_MAX)
426 cols = MD_COLS_MAX;
427 for (c = 0; c < cols; c++) {
428 float want = 0.0f;
429 for (r = 0; r < rows; r++) {
430 const n_markdown_inline* cell = &b->items[r * b->n_cols + c];
431 float cw = md_flow_width(f, cell->spans, cell->n_spans,
432 r == 0 ? N_MARKDOWN_BOLD : 0u);
433 if (cw > want)
434 want = cw;
435 }
436 out[c] = want + pad2;
437 sum += out[c];
438 }
439 if (sum <= avail || sum <= 0.0f)
440 return;
441 {
442 float scaled = 0.0f;
443 float k = avail / sum;
444 for (c = 0; c < cols; c++) {
445 out[c] *= k;
446 if (out[c] < st->col_min)
447 out[c] = st->col_min;
448 scaled += out[c];
449 }
450 if (scaled > avail) {
451 float each = avail / (float)cols;
452 for (c = 0; c < cols; c++)
453 out[c] = each;
454 }
455 }
456}
457
460static float md_table(const N_GUI_MD_FONTS* f, const N_GUI_MD_STYLE* st, const n_markdown_block* b, float x, float y, float w, int mode, md_hit* hit) {
461 float widths[MD_COLS_MAX];
462 size_t cols = b->n_cols, rows = b->n_cols ? b->n_items / b->n_cols : 0;
463 size_t c, r;
464 float used = 0.0f;
465 float lh = md_lh(f, f->regular);
466
467 if (!cols || !rows)
468 return 0.0f;
469 if (cols > MD_COLS_MAX)
470 cols = MD_COLS_MAX;
471 md_table_cols(f, st, b, w, widths);
472
473 for (r = 0; r < rows; r++) {
474 float cx = x, row_h = 0.0f;
475 unsigned style_or = (r == 0) ? N_MARKDOWN_BOLD : 0u;
476
477 /* The row's height comes from a measure pass over every cell, because
478 * a cell cannot be drawn until the row's height is known and a cell's
479 * own height is not known until it is laid out. */
480 for (c = 0; c < cols; c++) {
481 const n_markdown_inline* cell = &b->items[r * b->n_cols + c];
482 float h = md_flow(f, st, NULL, cell->spans, cell->n_spans, 0.0f, 0.0f,
483 widths[c] - st->cell_pad * 2.0f, lh, NULL, style_or,
485 if (h > row_h)
486 row_h = h;
487 }
488 if (mode != MD_MEASURE) {
489 for (c = 0; c < cols; c++) {
490 const n_markdown_inline* cell = &b->items[r * b->n_cols + c];
491 unsigned char al = b->align && c < b->n_cols
492 ? b->align[c]
493 : (unsigned char)N_MARKDOWN_ALIGN_LEFT;
494 (void)md_flow(f, st, NULL, cell->spans, cell->n_spans,
495 cx + st->cell_pad, y + used,
496 widths[c] - st->cell_pad * 2.0f, lh, NULL, style_or,
497 MD_COL_TEXT, al, 0, mode, hit);
498 cx += widths[c];
499 }
500 }
501 used += row_h;
502 if (r == 0) {
503 /* the rule under the header, which is what makes the rest read as
504 * rows of a table rather than as three columns of loose words */
505 float total = 0.0f;
506 for (c = 0; c < cols; c++)
507 total += widths[c];
508 if (mode == MD_DRAW)
509 al_draw_line(x, y + used + 1.0f, x + total, y + used + 1.0f, st->rule, 1.0f);
510 used += 3.0f;
511 }
512 }
513 return used;
514}
515
516/* ---- the document ------------------------------------------------------- */
517
521static float md_run(const N_GUI_MD_FONTS* f, const N_GUI_MD_STYLE* st, float x, float y, float w, const char* text, int mode, md_hit* hit) {
522 n_markdown_doc doc;
523 float lh = md_lh(f, f->regular);
524 float used = 0.0f;
525 size_t bi, ii;
526 int any = 0;
527
528 if (!text)
529 text = "";
530 n_markdown_parse_ex(text, strlen(text), st->parse_flags, &doc);
531 for (bi = 0; bi < doc.n_blocks; bi++) {
532 const n_markdown_block* blk = &doc.blocks[bi];
533 float by = y + used;
534 any = 1;
535 switch (blk->kind) {
536 case N_MARKDOWN_HEADING: {
537 ALLEGRO_FONT* hf = md_font_heading(f, blk->level);
538 float hlh = md_lh(f, hf);
539 float h;
540 /* The heading face is imposed on every atom: md_font_heading
541 * has already fallen back to bold, or to regular, so what
542 * comes out is the most heading-like face there is. */
543 h = md_flow(f, st, NULL, blk->inl.spans, blk->inl.n_spans, x, by, w, hlh,
544 hf, 0u, MD_COL_HEADING, N_MARKDOWN_ALIGN_LEFT, 0, mode, hit);
545 used += h;
546 if (blk->level <= 2) {
547 if (mode == MD_DRAW)
548 al_draw_line(x, by + h + 1.0f, x + w, by + h + 1.0f, st->rule, 1.0f);
549 used += 3.0f;
550 }
551 break;
552 }
553 case N_MARKDOWN_TABLE:
554 used += md_table(f, st, blk, x, by, w, mode, hit);
555 break;
556 case N_MARKDOWN_QUOTE: {
557 float h = md_flow(f, st, NULL, blk->inl.spans, blk->inl.n_spans,
558 x + st->indent, by, w - st->indent, lh, NULL, 0u,
560 st->hard_breaks, MD_MEASURE, NULL);
561 if (mode == MD_DRAW)
562 al_draw_filled_rectangle(x, by, x + 2.0f, by + h, st->quote);
563 if (mode != MD_MEASURE)
564 (void)md_flow(f, st, NULL, blk->inl.spans, blk->inl.n_spans,
565 x + st->indent, by, w - st->indent, lh, NULL, 0u,
567 st->hard_breaks, mode, hit);
568 used += h;
569 break;
570 }
573 float mlh = md_lh(f, f->mono ? f->mono : f->regular);
574 float h;
575 cs.text = blk->code ? blk->code : (char*)"";
576 cs.url = NULL;
578 h = md_flow(f, st, NULL, &cs, 1, x + st->indent, by,
579 w - st->indent * 2.0f, mlh, NULL, 0u, MD_COL_CODE,
581 if (mode == MD_DRAW)
582 al_draw_filled_rectangle(x, by - st->cell_pad, x + w,
583 by + h + st->cell_pad, st->code_bg);
584 if (mode != MD_MEASURE)
585 (void)md_flow(f, st, NULL, &cs, 1, x + st->indent, by,
586 w - st->indent * 2.0f, mlh, NULL, 0u, MD_COL_CODE,
587 N_MARKDOWN_ALIGN_LEFT, 1, mode, hit);
588 used += h + st->cell_pad * 2.0f;
589 break;
590 }
591 case N_MARKDOWN_UL:
592 case N_MARKDOWN_OL:
593 for (ii = 0; ii < blk->n_items; ii++) {
594 char bullet[32];
595 if (blk->kind == N_MARKDOWN_UL)
596 (void)snprintf(bullet, sizeof(bullet), "- ");
597 else
598 (void)snprintf(bullet, sizeof(bullet), "%u. ", (unsigned)(ii + 1));
599 used += md_flow(f, st, bullet, blk->items[ii].spans,
600 blk->items[ii].n_spans, x + st->indent, y + used,
601 w - st->indent, lh, NULL, 0u, MD_COL_TEXT,
603 }
604 break;
605 case N_MARKDOWN_PARA:
606 default:
607 used += md_flow(f, st, NULL, blk->inl.spans, blk->inl.n_spans, x, by, w,
609 hit);
610 break;
611 }
612 used += st->block_gap;
613 }
614 n_markdown_free(&doc);
615 if (!any)
616 return 0.0f;
617 return used - st->block_gap; /* trim the gap after the last block */
618}
619
620/* ---- public ------------------------------------------------------------- */
621
622void n_gui_md_style_default(N_GUI_MD_STYLE* out, ALLEGRO_COLOR text, ALLEGRO_COLOR background, float line_height) {
623 float level;
624 if (!out)
625 return;
626 if (line_height < 1.0f)
627 line_height = 16.0f;
628 out->text = text;
629 out->heading = text;
630 out->link = al_map_rgb(120, 170, 245);
631 out->code = text;
632 out->quote = text;
633 out->rule = text;
634 /* The code panel is lifted off a dark background and dropped into a light
635 * one, so it reads as a panel either way rather than vanishing into one of
636 * the two. A faint wash rather than a mixed colour, because what it sits
637 * on is the caller's business. */
638 level = (background.r + background.g + background.b) / 3.0f;
639 out->code_bg = level > 0.5f ? al_premul_rgba_f(0.0f, 0.0f, 0.0f, 0.06f)
640 : al_premul_rgba_f(1.0f, 1.0f, 1.0f, 0.10f);
641 out->rule = md_fade(out->rule, 0.35f);
642 out->quote = md_fade(out->quote, 0.80f);
643 out->block_gap = line_height * 0.45f;
644 out->indent = line_height * 0.8f;
645 out->cell_pad = line_height * 0.35f;
646 out->col_min = line_height * 2.5f;
648 out->hard_breaks = 0;
649}
650
651float n_gui_md_height(const N_GUI_MD_FONTS* fonts, const N_GUI_MD_STYLE* style, float width, const char* text) {
652 if (!fonts || !fonts->regular || !style)
653 return 0.0f;
654 return md_run(fonts, style, 0.0f, 0.0f, width, text, MD_MEASURE, NULL);
655}
656
657float n_gui_md_draw(const N_GUI_MD_FONTS* fonts, const N_GUI_MD_STYLE* style, float x, float y, float width, const char* text) {
658 if (!fonts || !fonts->regular || !style)
659 return 0.0f;
660 return md_run(fonts, style, x, y, width, text, MD_DRAW, NULL);
661}
662
663int n_gui_md_link_at(const N_GUI_MD_FONTS* fonts, const N_GUI_MD_STYLE* style, float x, float y, float width, const char* text, float px, float py, char* url, size_t url_cap) {
664 md_hit hit;
665 if (!fonts || !fonts->regular || !style)
666 return 0;
667 hit.px = px;
668 hit.py = py;
669 hit.out = url;
670 hit.cap = url_cap;
671 hit.found = 0;
672 if (url && url_cap)
673 url[0] = '\0';
674 (void)md_run(fonts, style, x, y, width, text, MD_HIT, &hit);
675 return hit.found;
676}
static void background(float x, float y, float w, float h, void *user_data)
a background
static N_GUI_MOUSE_EVENT last
the last event the widget was told of, and how many
static int mode
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
FONT * font
the default 8x8 font, valid after allegro_init()
static float md_flow(const N_GUI_MD_FONTS *f, const N_GUI_MD_STYLE *st, const char *prefix, const n_markdown_span *spans, size_t nspans, float x, float y, float w, float lh, ALLEGRO_FONT *base_font, unsigned style_or, unsigned char colid, unsigned char align, int hard_nl, int mode, md_hit *hit)
One inline flow: an optional prefix, then the spans.
Definition n_gui_md.c:318
static float md_table(const N_GUI_MD_FONTS *f, const N_GUI_MD_STYLE *st, const n_markdown_block *b, float x, float y, float w, int mode, md_hit *hit)
One table.
Definition n_gui_md.c:460
static float md_lh(const N_GUI_MD_FONTS *f, ALLEGRO_FONT *font)
A font's line height, or the regular face's when it is NULL.
Definition n_gui_md.c:164
float avail
Definition n_gui_md.c:237
static int md_cp_len(const char *s, int i, int len)
Bytes in the UTF-8 codepoint at s[i], clamped to what is left.
Definition n_gui_md.c:94
@ MD_MEASURE
Definition n_gui_md.c:87
@ MD_HIT
Definition n_gui_md.c:89
@ MD_DRAW
Definition n_gui_md.c:88
static float md_run(const N_GUI_MD_FONTS *f, const N_GUI_MD_STYLE *st, float x, float y, float w, const char *text, int mode, md_hit *hit)
Parse and lay out the whole text.
Definition n_gui_md.c:521
md_hit * hit
Definition n_gui_md.c:240
static ALLEGRO_FONT * md_font_heading(const N_GUI_MD_FONTS *f, int level)
The face for a heading of this level, falling back to bold then regular.
Definition n_gui_md.c:119
float py
Definition n_gui_md.c:79
static void md_wrap_of(const md_atom *a, n_wrap_item *w)
Project an atom onto the generic wrap's view of it: a width, and whether it is a break point.
Definition n_gui_md.c:172
size_t cap
Definition n_gui_md.c:81
ALLEGRO_FONT * font
Definition n_gui_md.c:60
static ALLEGRO_COLOR md_fade(ALLEGRO_COLOR c, float a)
A colour at a fraction of its opacity, premultiplied.
Definition n_gui_md.c:155
int found
Definition n_gui_md.c:82
static void md_emit_line(size_t first, size_t last, size_t line, double width, void *user)
Definition n_gui_md.c:302
unsigned char nbytes
Definition n_gui_md.c:63
static ALLEGRO_FONT * md_font_for(const N_GUI_MD_FONTS *f, unsigned style)
The face for an inline style, falling back towards regular so that a caller with one font still gets ...
Definition n_gui_md.c:105
static void md_table_cols(const N_GUI_MD_FONTS *f, const N_GUI_MD_STYLE *st, const n_markdown_block *b, float avail, float *out)
Column widths for a table, including the cell padding.
Definition n_gui_md.c:420
const char * url
Definition n_gui_md.c:61
float w
Definition n_gui_md.c:62
int n_gui_md_link_at(const N_GUI_MD_FONTS *fonts, const N_GUI_MD_STYLE *style, float x, float y, float width, const char *text, float px, float py, char *url, size_t url_cap)
the url of the link under (px, py), if there is one
Definition n_gui_md.c:663
unsigned char flags
Definition n_gui_md.c:65
char bytes[4]
Definition n_gui_md.c:66
static void md_flush_line(const md_line_ctx *c, size_t from, size_t to, float y, float line_w)
Draw atoms [from, to) as one line: consecutive atoms sharing a font and a colour are batched into one...
Definition n_gui_md.c:246
@ MD_AT_NEWLINE
Definition n_gui_md.c:54
@ MD_AT_SPACE
Definition n_gui_md.c:53
static float md_flow_width(const N_GUI_MD_FONTS *f, const n_markdown_span *spans, size_t nspans, unsigned style_or)
The width one flow would take on a single line: the same per-codepoint widths the atoms carry,...
Definition n_gui_md.c:383
unsigned char align
Definition n_gui_md.c:239
const md_atom * a
Definition n_gui_md.c:235
float n_gui_md_draw(const N_GUI_MD_FONTS *fonts, const N_GUI_MD_STYLE *style, float x, float y, float width, const char *text)
draw text as markdown at (x, y), wrapped to width
Definition n_gui_md.c:657
static ALLEGRO_COLOR md_col(const N_GUI_MD_STYLE *st, unsigned char colid)
Definition n_gui_md.c:132
const N_GUI_MD_STYLE * st
Definition n_gui_md.c:236
#define MD_COLS_MAX
Widest table this lays out.
Definition n_gui_md.c:50
@ MD_COL_TEXT
Definition n_gui_md.c:70
@ MD_COL_LINK
Definition n_gui_md.c:71
@ MD_COL_CODE
Definition n_gui_md.c:72
@ MD_COL_HEADING
Definition n_gui_md.c:73
@ MD_COL_QUOTE
Definition n_gui_md.c:74
static int md_build_seg(md_atom *out, n_wrap_item *wrap, int cap, int na, const char *text, int len, ALLEGRO_FONT *font, unsigned char colid, const char *url, int hard_nl)
Append atoms for one styled segment.
Definition n_gui_md.c:181
char * out
Definition n_gui_md.c:80
unsigned char colid
Definition n_gui_md.c:64
void n_gui_md_style_default(N_GUI_MD_STYLE *out, ALLEGRO_COLOR text, ALLEGRO_COLOR background, float line_height)
fill a style from two colours and a line height
Definition n_gui_md.c:622
float n_gui_md_height(const N_GUI_MD_FONTS *fonts, const N_GUI_MD_STYLE *style, float width, const char *text)
height of text laid out as markdown at width, in pixels
Definition n_gui_md.c:651
float px
Definition n_gui_md.c:79
One layout atom: a glyph (nbytes > 0) or a hard break.
Definition n_gui_md.c:59
A hit-test threaded through the layout: when a link atom's cell contains the point,...
Definition n_gui_md.c:78
Everything a line needs, carried through n_wrap to the emit callback, which is handed only an item ra...
Definition n_gui_md.c:234
Lay out and draw n_markdown's block model with Allegro fonts.
ALLEGRO_COLOR heading
heading text
Definition n_gui_md.h:91
int hard_breaks
Non-zero to break a paragraph where its source breaks, which is what a chat message wants: somebody p...
Definition n_gui_md.h:122
float col_min
narrowest a table column may be squeezed to, in pixels
Definition n_gui_md.h:109
ALLEGRO_FONT * bold
bold
Definition n_gui_md.h:69
float cell_pad
padding inside a table cell, left and right
Definition n_gui_md.h:107
ALLEGRO_COLOR text
body text
Definition n_gui_md.h:89
ALLEGRO_COLOR quote
a blockquote's text, and its bar
Definition n_gui_md.h:99
ALLEGRO_COLOR link
link text
Definition n_gui_md.h:93
ALLEGRO_FONT * regular
body text, and the fallback for every other face.
Definition n_gui_md.h:67
ALLEGRO_COLOR code_bg
the panel behind a fenced code block, and behind inline code
Definition n_gui_md.h:97
ALLEGRO_FONT * h1
"# " headings, ideally the bold face at a larger size
Definition n_gui_md.h:77
ALLEGRO_COLOR rule
the rule under a heading and under a table's header row
Definition n_gui_md.h:101
ALLEGRO_FONT * bolditalic
both at once
Definition n_gui_md.h:73
ALLEGRO_FONT * h3
"### " and deeper
Definition n_gui_md.h:81
unsigned parse_flags
n_markdown parse flags.
Definition n_gui_md.h:113
ALLEGRO_FONT * mono
code and fenced code blocks
Definition n_gui_md.h:75
ALLEGRO_FONT * h2
"## " headings
Definition n_gui_md.h:79
ALLEGRO_FONT * italic
italic
Definition n_gui_md.h:71
ALLEGRO_COLOR code
code and code block text
Definition n_gui_md.h:95
float indent
how far a quote, a list item's text and a code block are indented
Definition n_gui_md.h:105
float block_gap
vertical space between two blocks, in pixels
Definition n_gui_md.h:103
The faces a render uses.
Definition n_gui_md.h:65
Colours and metrics.
Definition n_gui_md.h:87
void n_markdown_free(n_markdown_doc *doc)
free everything a n_markdown_doc owns and zero it
Definition n_markdown.c:918
void n_markdown_parse_ex(const char *text, size_t len, unsigned flags, n_markdown_doc *out)
parse text into out, choosing which optional block kinds to recognise
Definition n_markdown.c:710
#define N_MARKDOWN_CODE
inline code style flag (carries no emphasis)
Definition n_markdown.h:67
#define N_MARKDOWN_ALIGN_CENTER
what "|:---:|" asks for.
Definition n_markdown.h:98
#define N_MARKDOWN_ALIGN_RIGHT
what "|---:|" asks for.
Definition n_markdown.h:100
#define N_MARKDOWN_ITALIC
italic inline style flag
Definition n_markdown.h:65
#define N_MARKDOWN_ALL
every block kind this parser knows.
Definition n_markdown.h:90
#define N_MARKDOWN_LINK
[text](http(s)://url) style flag; url is set only when this is present
Definition n_markdown.h:69
#define N_MARKDOWN_BOLD
bold inline style flag
Definition n_markdown.h:63
n_markdown_span * spans
Definition n_markdown.h:125
n_markdown_inline * items
Definition n_markdown.h:142
n_markdown_inline inl
Definition n_markdown.h:140
@ N_MARKDOWN_HEADING
"## " heading (inl, level)
Definition n_markdown.h:110
@ N_MARKDOWN_OL
ordered list (items)
Definition n_markdown.h:109
@ N_MARKDOWN_QUOTE
Definition n_markdown.h:106
@ N_MARKDOWN_TABLE
pipe table (items, n_cols)
Definition n_markdown.h:111
@ N_MARKDOWN_UL
unordered list (items)
Definition n_markdown.h:108
@ N_MARKDOWN_CODE_BLOCK
``` fenced code (raw code)
Definition n_markdown.h:107
@ N_MARKDOWN_PARA
paragraph
Definition n_markdown.h:105
int kind
enum n_markdown_block_kind
Definition n_markdown.h:139
unsigned char * align
TABLE: N_MARKDOWN_ALIGN_* per column, n_cols entries, owned.
Definition n_markdown.h:150
#define N_MARKDOWN_ALIGN_LEFT
table column alignment: the default, and what "|:---|" asks for.
Definition n_markdown.h:96
n_markdown_block * blocks
Definition n_markdown.h:155
size_t n_cols
TABLE: cells per row, always at least 1.
Definition n_markdown.h:147
int level
HEADING: 1 to 6.
Definition n_markdown.h:145
A parsed document: an owned array of blocks.
Definition n_markdown.h:154
One flow of inline runs (a paragraph, a quote, or one list item).
Definition n_markdown.h:124
One inline run: visible text, a style bitmask, and (for a link) an already-http(s)-validated url.
Definition n_markdown.h:117
size_t n_wrap_layout(const n_wrap_item *items, size_t count, double max_width, n_wrap_emit emit, void *user)
Break items into lines no wider than max_width.
Definition n_wrap.c:50
Greedy word-wrap over pre-measured, styled items.
#define N_WRAP_SPACE
the item is a legal break point (typically a space); it is DROPPED when a line breaks there,...
Definition n_wrap.h:70
#define N_WRAP_NEWLINE
the item is a hard line break; it ends the line and occupies no cell
Definition n_wrap.h:72
double width
advance width in the caller's units (pixels, columns, ...)
Definition n_wrap.h:78
unsigned int flags
N_WRAP_SPACE / N_WRAP_NEWLINE, or 0
Definition n_wrap.h:80
One pre-measured layout item: a glyph, an emoji cell, an inline image, or a whole word,...
Definition n_wrap.h:76