41#include <allegro5/allegro_font.h>
42#include <allegro5/allegro_image.h>
43#include <allegro5/allegro_primitives.h>
44#include <allegro5/allegro_ttf.h>
51#define CHECK(cond, msg) \
54 n_log(LOG_ERR, "FAIL: %s", (msg)); \
63 "A paragraph with **bold**, *italic*, `code` and a\n"
64 "[link](https://example.com/page) in it, hard wrapped by whoever wrote it\n"
65 "and re-wrapped by the renderer.\n"
67 "## A smaller heading\n"
72 "a fenced code block\n"
73 "keeping its own line breaks\n"
77 "- one that carries on\n"
78 " onto a second line\n"
82 "| `G` | back to the grid |\n"
83 "| Escape | close a dialog, otherwise go back to the grid |\n";
87static ALLEGRO_FONT*
face(
const char* name,
int px) {
88 static const char*
const dirs[] = {
89 "/usr/share/fonts/truetype/dejavu/",
90 "/usr/share/fonts/TTF/",
91 "/usr/share/fonts/dejavu/",
95 for (i = 0; dirs[i]; i++) {
97 (void)snprintf(path,
sizeof(path),
"%s%s", dirs[i], name);
98 f = al_load_ttf_font(path, px, 0);
105int main(
int argc,
char** argv) {
109 ALLEGRO_COLOR bg = al_map_rgb(33, 37, 46);
110 ALLEGRO_COLOR fg = al_map_rgb(220, 223, 228);
111 const char* out_path = NULL;
112 float h_wide, h_narrow, drawn;
116 for (i = 1; i < argc; i++)
117 if (strcmp(argv[i],
"-o") == 0 && i + 1 < argc)
118 out_path = argv[++i];
124 al_init_font_addon();
126 (void)al_init_image_addon();
127 if (!al_init_primitives_addon()) {
128 n_log(
LOG_ERR,
"cannot initialise the primitives addon");
131 display = al_create_display(900, 600);
133 n_log(
LOG_ERR,
"cannot create a display; run this under Xvfb");
137 memset(&fonts, 0,
sizeof(fonts));
139 fonts.
bold =
face(
"DejaVuSans-Bold.ttf", 14);
140 fonts.
italic =
face(
"DejaVuSans-Oblique.ttf", 14);
142 fonts.
mono =
face(
"DejaVuSansMono.ttf", 14);
143 fonts.
h1 =
face(
"DejaVuSans-Bold.ttf", 21);
144 fonts.
h2 =
face(
"DejaVuSans-Bold.ttf", 17);
145 fonts.
h3 =
face(
"DejaVuSans-Bold.ttf", 15);
149 fonts.
regular = al_create_builtin_font();
162 CHECK(h_wide > 0.0f,
"a height for the sample");
163 al_clear_to_color(bg);
165 CHECK(drawn == h_wide,
"draw returns exactly what measure said");
170 CHECK(h_narrow > h_wide,
"a narrower column is taller");
179 for (y = 20; y < 20 + (int)h_wide && !found; y++) {
181 for (x = 20; x < 780 && !found; x += 4)
183 (
float)x, (
float)y, url,
sizeof(url));
185 CHECK(found,
"the link is somewhere in the laid-out page");
187 CHECK(strcmp(url,
"https://example.com/page") == 0,
"and it is the right url");
190 -100.0f, -100.0f, url,
sizeof(url)),
191 "no link outside the page");
192 CHECK(url[0] ==
'\0',
"a miss clears the url rather than leaving it stale");
215 CHECK(hard > soft,
"hard breaks keep the three lines the source has");
221 CHECK(
gui != NULL,
"a gui context");
226 CHECK(mv >= 0,
"a markdownview");
228 "the text starts empty rather than NULL");
231 "set_text round-trips");
236 "no content height before the first draw");
237 al_clear_to_color(bg);
240 "the sample overflows a 240px pane, so there is a bar");
246 "a scroll past the end is clamped");
251 CHECK(live != NULL,
"the live style");
253 live->
link = al_map_rgb(255, 0, 0);
255 CHECK(live->
link.r > 0.9f,
"a colour set by the caller survives a draw");
259 "NULL text clears rather than crashes");
267 CHECK(other >= 0,
"a label to test the type guard with");
269 "another widget type is not a markdownview");
271 "and has no markdown text");
274 CHECK(
gui == NULL,
"the context is nulled on destroy");
279 ALLEGRO_BITMAP* page = al_create_bitmap(800, (
int)h_wide + 40);
281 al_set_target_bitmap(page);
282 al_clear_to_color(bg);
284 if (al_save_bitmap(out_path, page))
286 al_set_target_backbuffer(
display);
287 al_destroy_bitmap(page);
292 al_destroy_font(fonts.
regular);
293 al_destroy_font(fonts.
bold);
294 al_destroy_font(fonts.
italic);
296 al_destroy_font(fonts.
mono);
297 al_destroy_font(fonts.
h1);
298 al_destroy_font(fonts.
h2);
299 al_destroy_font(fonts.
h3);
ALLEGRO_DISPLAY * display
static ALLEGRO_FONT * face(const char *name, int px)
Load a face, trying the usual places; NULL when there is none, which the renderer is documented to su...
N_GUI_MD_STYLE * n_gui_markdownview_style(N_GUI_CTX *ctx, int widget_id)
the view's style, to read or to change in place, or NULL if there is no such widget.
#define N_GUI_ALIGN_LEFT
left aligned text
int n_gui_add_markdownview(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, const N_GUI_MD_FONTS *fonts)
add a read-only scrolling Markdown view.
int n_gui_add_label(N_GUI_CTX *ctx, int window_id, const char *text, float x, float y, float w, float h, int align)
Add a static text label.
void n_gui_markdownview_scroll_to(N_GUI_CTX *ctx, int widget_id, float y)
scroll to y pixels down the content, clamped to what there is
void n_gui_draw(N_GUI_CTX *ctx)
draw all visible GUI windows and widgets
void n_gui_markdownview_set_text(N_GUI_CTX *ctx, int widget_id, const char *text)
set the Markdown source (copied; NULL clears it).
float n_gui_markdownview_get_scroll(N_GUI_CTX *ctx, int widget_id)
how far down the content the view is, in pixels
int n_gui_add_window(N_GUI_CTX *ctx, const char *title, float x, float y, float w, float h)
Add a new pseudo-window to the context.
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
const char * n_gui_markdownview_get_text(N_GUI_CTX *ctx, int widget_id)
the Markdown source, owned by the widget, or NULL
float n_gui_markdownview_content_height(N_GUI_CTX *ctx, int widget_id)
the height of the whole document at the width it was last drawn at, or 0 before the first draw
The top-level GUI context that holds all windows.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
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
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
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
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
Lay out and draw n_markdown's block model with Allegro fonts.
int hard_breaks
Non-zero to break a paragraph where its source breaks, which is what a chat message wants: somebody p...
ALLEGRO_COLOR link
link text
ALLEGRO_FONT * regular
body text, and the fallback for every other face.
ALLEGRO_FONT * h1
"# " headings, ideally the bold face at a larger size
ALLEGRO_FONT * bolditalic
both at once
ALLEGRO_FONT * h3
"### " and deeper
unsigned parse_flags
n_markdown parse flags.
ALLEGRO_FONT * mono
code and fenced code blocks
ALLEGRO_FONT * h2
"## " headings
ALLEGRO_FONT * italic
italic
#define N_MARKDOWN_ALL
every block kind this parser knows.