Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_gui_md.c File Reference

Markdown layout and drawing. More...

#include "nilorea/n_gui_md.h"
#include "nilorea/n_wrap.h"
#include <allegro5/allegro_primitives.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Include dependency graph for n_gui_md.c:

Go to the source code of this file.

Data Structures

struct  md_atom
 One layout atom: a glyph (nbytes > 0) or a hard break. More...
struct  md_hit
 A hit-test threaded through the layout: when a link atom's cell contains the point, its url is copied out while the document is still live. More...
struct  md_line_ctx
 Everything a line needs, carried through n_wrap to the emit callback, which is handed only an item range, a line number and a width. More...

Macros

#define MD_COLS_MAX   32
 Widest table this lays out.

Enumerations

enum  { MD_AT_SPACE = 1 , MD_AT_NEWLINE = 2 }
 Atom flags. More...
enum  { MD_MEASURE = 0 , MD_DRAW , MD_HIT }
 What one pass is doing. More...
enum  {
  MD_COL_TEXT = 0 , MD_COL_LINK , MD_COL_CODE , MD_COL_HEADING ,
  MD_COL_QUOTE
}
 Which colour of the style an atom takes. More...

Functions

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.
static ALLEGRO_COLOR md_col (const N_GUI_MD_STYLE *st, unsigned char colid)
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.
static void md_emit_line (size_t first, size_t last, size_t line, double width, void *user)
static ALLEGRO_COLOR md_fade (ALLEGRO_COLOR c, float a)
 A colour at a fraction of its opacity, premultiplied.
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.
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, summed without allocating.
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 al_draw_text.
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 its text.
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.
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.
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.
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.
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.
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.
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
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
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
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

Detailed Description

Markdown layout and drawing.

See n_gui_md.h.

Author
Castagnier Mickael
Version
1.0

The text is parsed once per call, then each block is laid out as a flow of "atoms", one per codepoint, each carrying its own font and colour. A flow is word-wrapped by n_wrap, which never sees a glyph or a font: it is handed a width and a pair of flags per atom and hands back line ranges.

Measure, draw and hit-test are one function with a mode, so a height cannot disagree with what is painted. That is the whole reason for the shape of this file, and the reason the measure pass allocates and walks exactly as the draw does rather than taking a cheaper route to a number.

Definition in file n_gui_md.c.


Data Structure Documentation

◆ md_atom

struct md_atom

One layout atom: a glyph (nbytes > 0) or a hard break.

url is non-NULL for an atom belonging to a link, and points into the parsed document, which outlives every atom made from it.

Definition at line 59 of file n_gui_md.c.

Collaboration diagram for md_atom:
Data Fields
char bytes[4]
unsigned char colid
unsigned char flags
ALLEGRO_FONT * font
unsigned char nbytes
const char * url
float w

◆ md_hit

struct md_hit

A hit-test threaded through the layout: when a link atom's cell contains the point, its url is copied out while the document is still live.

Definition at line 78 of file n_gui_md.c.

Collaboration diagram for md_hit:
Data Fields
size_t cap
int found
char * out
float px
float py

◆ md_line_ctx

struct md_line_ctx

Everything a line needs, carried through n_wrap to the emit callback, which is handed only an item range, a line number and a width.

Definition at line 234 of file n_gui_md.c.

Collaboration diagram for md_line_ctx:
Data Fields
const md_atom * a
unsigned char align
float avail
md_hit * hit
float lh
int mode
const N_GUI_MD_STYLE * st
float x
float y

Macro Definition Documentation

◆ MD_COLS_MAX

#define MD_COLS_MAX   32

Widest table this lays out.

Past it the remaining columns are dropped, which is also n_markdown's own cap, so it cannot be reached from a parsed document; it bounds the stack arrays below.

Definition at line 50 of file n_gui_md.c.

Referenced by md_table(), and md_table_cols().

Enumeration Type Documentation

◆ anonymous enum

anonymous enum

Atom flags.

Enumerator
MD_AT_SPACE 
MD_AT_NEWLINE 

Definition at line 53 of file n_gui_md.c.

◆ anonymous enum

anonymous enum

What one pass is doing.

A measure is the draw walk with the per-line callback left off, which is what keeps the two in step.

Enumerator
MD_MEASURE 
MD_DRAW 
MD_HIT 

Definition at line 87 of file n_gui_md.c.

◆ anonymous enum

anonymous enum

Which colour of the style an atom takes.

Enumerator
MD_COL_TEXT 
MD_COL_LINK 
MD_COL_CODE 
MD_COL_HEADING 
MD_COL_QUOTE 

Definition at line 70 of file n_gui_md.c.

Function Documentation

◆ md_build_seg()

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 )
static

Append atoms for one styled segment.

hard_nl decides what a newline in the text becomes: a line break, or a space for the wrap to break at if it needs to. Returns the new atom count.

Definition at line 181 of file n_gui_md.c.

References md_atom::bytes, md_atom::colid, md_atom::flags, font, md_atom::font, MD_AT_NEWLINE, MD_AT_SPACE, md_cp_len(), md_wrap_of(), md_atom::nbytes, md_atom::url, and md_atom::w.

Referenced by md_flow().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_col()

ALLEGRO_COLOR md_col ( const N_GUI_MD_STYLE * st,
unsigned char colid )
static

Definition at line 132 of file n_gui_md.c.

References N_GUI_MD_STYLE::code, N_GUI_MD_STYLE::heading, N_GUI_MD_STYLE::link, MD_COL_CODE, MD_COL_HEADING, MD_COL_LINK, MD_COL_QUOTE, N_GUI_MD_STYLE::quote, and N_GUI_MD_STYLE::text.

Referenced by md_flush_line().

Here is the caller graph for this function:

◆ md_cp_len()

int md_cp_len ( const char * s,
int i,
int len )
static

Bytes in the UTF-8 codepoint at s[i], clamped to what is left.

Definition at line 94 of file n_gui_md.c.

Referenced by md_build_seg(), and md_flow_width().

Here is the caller graph for this function:

◆ md_emit_line()

void md_emit_line ( size_t first,
size_t last,
size_t line,
double width,
void * user )
static

Definition at line 302 of file n_gui_md.c.

References last, md_line_ctx::lh, line(), md_flush_line(), and md_line_ctx::y.

Referenced by md_flow().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_fade()

ALLEGRO_COLOR md_fade ( ALLEGRO_COLOR c,
float a )
static

A colour at a fraction of its opacity, premultiplied.

Allegro's default blender adds the source and keeps (1 - alpha) of the destination, so the rgb has to come down with the alpha or the colour draws at very nearly full strength over a hole it has punched in what was there. Setting .a alone, which is the obvious thing to write, gives a rule that looks solid and a panel that looks white.

Definition at line 155 of file n_gui_md.c.

Referenced by n_gui_md_style_default().

Here is the caller graph for this function:

◆ md_flow()

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 )
static

One inline flow: an optional prefix, then the spans.

base_font, when given, is the face every atom takes whatever its span's style says, bar a code span which keeps the mono face: that is how a heading is drawn in the heading face rather than in a bold body face, and there is no bold-heading face to switch to inside one. NULL picks the face from each span's own style, which is what body text wants.

style_or is ORed into every span's style, which is how a table's header row is bold without the parser having to say so. colid is the colour every non-link, non-code atom takes. Returns the height.

Definition at line 318 of file n_gui_md.c.

References md_line_ctx::a, md_line_ctx::align, md_line_ctx::avail, font, md_line_ctx::hit, md_line_ctx::lh, md_build_seg(), MD_COL_CODE, MD_COL_LINK, md_emit_line(), md_font_for(), MD_MEASURE, md_line_ctx::mode, mode, N_GUI_MD_FONTS::mono, N_MARKDOWN_CODE, N_MARKDOWN_LINK, n_wrap_layout(), N_GUI_MD_FONTS::regular, md_line_ctx::st, n_markdown_span::style, md_line_ctx::x, and md_line_ctx::y.

Referenced by md_run(), and md_table().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_flow_width()

float md_flow_width ( const N_GUI_MD_FONTS * f,
const n_markdown_span * spans,
size_t nspans,
unsigned style_or )
static

The width one flow would take on a single line: the same per-codepoint widths the atoms carry, summed without allocating.

Used to size table columns, where a whole-string measure would disagree with the wrap.

Definition at line 383 of file n_gui_md.c.

References font, md_cp_len(), md_font_for(), and n_markdown_span::text.

Referenced by md_table_cols().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_flush_line()

void md_flush_line ( const md_line_ctx * c,
size_t from,
size_t to,
float y,
float line_w )
static

Draw atoms [from, to) as one line: consecutive atoms sharing a font and a colour are batched into one al_draw_text.

On a hit-test pass nothing is drawn and a link atom under the point records its url instead.

Definition at line 246 of file n_gui_md.c.

References md_line_ctx::a, md_line_ctx::align, md_line_ctx::avail, md_atom::bytes, md_hit::cap, md_atom::colid, md_atom::font, md_hit::found, md_line_ctx::hit, md_line_ctx::lh, md_col(), MD_HIT, md_line_ctx::mode, N_MARKDOWN_ALIGN_CENTER, N_MARKDOWN_ALIGN_RIGHT, md_atom::nbytes, md_hit::out, md_hit::px, md_hit::py, md_line_ctx::st, md_atom::url, md_atom::w, and md_line_ctx::x.

Referenced by md_emit_line().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_font_for()

ALLEGRO_FONT * md_font_for ( const N_GUI_MD_FONTS * f,
unsigned style )
static

The face for an inline style, falling back towards regular so that a caller with one font still gets its text.

Definition at line 105 of file n_gui_md.c.

References N_GUI_MD_FONTS::bold, N_GUI_MD_FONTS::bolditalic, N_GUI_MD_FONTS::italic, N_GUI_MD_FONTS::mono, N_MARKDOWN_BOLD, N_MARKDOWN_CODE, N_MARKDOWN_ITALIC, and N_GUI_MD_FONTS::regular.

Referenced by md_flow(), and md_flow_width().

Here is the caller graph for this function:

◆ md_font_heading()

ALLEGRO_FONT * md_font_heading ( const N_GUI_MD_FONTS * f,
int level )
static

The face for a heading of this level, falling back to bold then regular.

Definition at line 119 of file n_gui_md.c.

References N_GUI_MD_FONTS::bold, N_GUI_MD_FONTS::h1, N_GUI_MD_FONTS::h2, N_GUI_MD_FONTS::h3, and N_GUI_MD_FONTS::regular.

Referenced by md_run().

Here is the caller graph for this function:

◆ md_lh()

float md_lh ( const N_GUI_MD_FONTS * f,
ALLEGRO_FONT * font )
static

A font's line height, or the regular face's when it is NULL.

Definition at line 164 of file n_gui_md.c.

References font, and N_GUI_MD_FONTS::regular.

Referenced by md_run(), and md_table().

Here is the caller graph for this function:

◆ md_run()

◆ md_table()

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 )
static

One table.

Cells wrap inside their column, a row is as tall as its tallest cell, and a rule goes under the header row. Returns the height.

Definition at line 460 of file n_gui_md.c.

References n_markdown_block::align, N_GUI_MD_STYLE::cell_pad, n_markdown_block::items, MD_COL_TEXT, MD_COLS_MAX, MD_DRAW, md_flow(), md_lh(), MD_MEASURE, md_table_cols(), mode, n_markdown_block::n_cols, n_markdown_block::n_items, N_MARKDOWN_ALIGN_LEFT, N_MARKDOWN_BOLD, n_markdown_inline::n_spans, N_GUI_MD_FONTS::regular, N_GUI_MD_STYLE::rule, and n_markdown_inline::spans.

Referenced by md_run().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_table_cols()

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 )
static

Column widths for a table, including the cell padding.

Every column asks for its widest cell on one line, and if the table then fits, that is what it gets: a two-column table of short keys does not stretch across the pane. When the asks add up to more than there is, they are scaled by the same factor with a floor of style->col_min, so a long neighbour cannot squeeze a column to nothing – and if even the floors do not fit, the width is shared equally, because at that point nothing will look right and equal is at least predictable.

Cells wrap to the width they are given; the alternative, panning the table sideways, hides the right-hand column of exactly the tables people read.

Definition at line 420 of file n_gui_md.c.

References N_GUI_MD_STYLE::cell_pad, N_GUI_MD_STYLE::col_min, n_markdown_block::items, MD_COLS_MAX, md_flow_width(), n_markdown_block::n_cols, n_markdown_block::n_items, N_MARKDOWN_BOLD, n_markdown_inline::n_spans, and n_markdown_inline::spans.

Referenced by md_table().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ md_wrap_of()

void md_wrap_of ( const md_atom * a,
n_wrap_item * w )
static

Project an atom onto the generic wrap's view of it: a width, and whether it is a break point.

n_wrap never sees a font, a glyph or a colour.

Definition at line 172 of file n_gui_md.c.

References md_atom::flags, n_wrap_item::flags, MD_AT_NEWLINE, MD_AT_SPACE, N_WRAP_NEWLINE, N_WRAP_SPACE, md_atom::w, and n_wrap_item::width.

Referenced by md_build_seg().

Here is the caller graph for this function:

◆ n_gui_md_draw()

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

Draws into the current Allegro target with the current transform and clipping rectangle; a caller wanting the overflow clipped sets one.

Parameters
fontsthe faces. Must not be NULL, nor fonts->regular.
stylecolours and metrics. Must not be NULL.
xleft edge
ytop edge
widththe width to wrap to, in pixels
textthe markdown source. NULL is treated as empty.
Returns
the height used, equal to n_gui_md_height() for the same inputs
Examples
ex_gui_md.c.

Definition at line 657 of file n_gui_md.c.

References MD_DRAW, md_run(), and N_GUI_MD_FONTS::regular.

Referenced by _draw_markdownview(), and main().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ n_gui_md_height()

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

The same walk n_gui_md_draw() makes, with nothing drawn, so the two agree to the pixel. Independent of any y: measuring at the origin gives the height a draw anywhere will use.

Parameters
fontsthe faces. Must not be NULL, nor fonts->regular.
stylecolours and metrics. Must not be NULL.
widththe width to wrap to, in pixels
textthe markdown source. NULL is treated as empty.
Returns
the height in pixels, never negative
Examples
ex_gui_md.c.

Definition at line 651 of file n_gui_md.c.

References MD_MEASURE, md_run(), and N_GUI_MD_FONTS::regular.

Referenced by _markdownview_measure(), and main().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ n_gui_md_link_at()

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

Lays the text out exactly as n_gui_md_draw() would at the same position and width, and reports the link whose glyph cell the point falls in.

Parameters
fontsthe faces. Must not be NULL, nor fonts->regular.
stylecolours and metrics. Must not be NULL.
xleft edge the text would be drawn at
ytop edge
widththe width it would wrap to
textthe markdown source. NULL is treated as empty.
pxthe point to test
pythe point to test
urlreceives the url, NUL-terminated and bounded by url_cap
url_capsize of url
Returns
1 when a link was under the point and url was written, else 0
Examples
ex_gui_md.c.

Definition at line 663 of file n_gui_md.c.

References md_hit::cap, md_hit::found, MD_HIT, md_run(), md_hit::out, md_hit::px, md_hit::py, and N_GUI_MD_FONTS::regular.

Referenced by main(), and n_gui_process_event().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ n_gui_md_style_default()

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

The derived colours are the obvious ones: a heading and body text share a colour, a link is blue, code sits on a panel lifted or dropped from the background depending on which is darker. The metrics are fractions of line_height, so they follow the font.

Parameters
outthe style to fill. Must not be NULL.
textbody text colour
backgroundwhat the text is drawn on, used to derive the code panel
line_heightthe regular face's line height, in pixels
Examples
ex_gui_md.c.

Definition at line 622 of file n_gui_md.c.

References background(), N_GUI_MD_STYLE::block_gap, N_GUI_MD_STYLE::cell_pad, N_GUI_MD_STYLE::code, N_GUI_MD_STYLE::code_bg, N_GUI_MD_STYLE::col_min, N_GUI_MD_STYLE::hard_breaks, N_GUI_MD_STYLE::heading, N_GUI_MD_STYLE::indent, N_GUI_MD_STYLE::link, md_fade(), N_MARKDOWN_ALL, N_GUI_MD_STYLE::parse_flags, N_GUI_MD_STYLE::quote, N_GUI_MD_STYLE::rule, and N_GUI_MD_STYLE::text.

Referenced by _markdownview_follow_theme(), and main().

Here is the call graph for this function:
Here is the caller graph for this function: