1// nonstd.h
  2// A collection of useful functions and macros.
  3// This library is licensed under the BSD 2-Clause License.
  4// 
  5// This file provides both the interface and the implementation.
  6// To instantiate the implementation,
  7//     #define NONSTD_IMPLEMENTATION
  8// before including this file.
  9
 10#ifdef NONSTD_IMPLEMENTATION
 11#ifndef _POSIX_C_SOURCE
 12#define _POSIX_C_SOURCE 200809L
 13#endif
 14#endif
 15
 16#ifndef NONSTD_H
 17#define NONSTD_H
 18
 19#include <stdarg.h>
 20#include <stddef.h>
 21#include <stdint.h>
 22#include <stdio.h>
 23#include <stdlib.h>
 24#include <string.h>
 25#include <sys/time.h>
 26#include <time.h>
 27#include <unistd.h>
 28
 29#ifndef NONSTD_DEF
 30#ifdef NONSTD_STATIC
 31#define NONSTD_DEF static
 32#else
 33#define NONSTD_DEF extern
 34#endif
 35#endif
 36
 37typedef int8_t i8;
 38typedef uint8_t u8;
 39typedef int16_t i16;
 40typedef uint16_t u16;
 41typedef int32_t i32;
 42typedef uint32_t u32;
 43typedef int64_t i64;
 44typedef uint64_t u64;
 45typedef unsigned int uint;
 46typedef unsigned long ulong;
 47typedef intptr_t isize;
 48typedef uintptr_t usize;
 49typedef char c8;
 50
 51#define countof(a) (sizeof(a) / sizeof((a)[0]))
 52
 53#define static_foreach(type, var, array)                 \
 54	for (size_t _i_##var = 0, _n_##var = countof(array); \
 55		 _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var)
 56
 57#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n)))
 58#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n)))
 59#define FREE(ptr)   \
 60	do {            \
 61		free(ptr);  \
 62		ptr = NULL; \
 63	} while (0)
 64
 65NONSTD_DEF void *safe_malloc(size_t item_size, size_t count);
 66NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count);
 67
 68#define MIN(a, b) ((a) < (b) ? (a) : (b))
 69#define MAX(a, b) ((a) > (b) ? (a) : (b))
 70#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x))))
 71
 72// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206.
 73#define UNUSED(value) (void)(value)
 74#define TODO(message)                                                      \
 75	do {                                                                   \
 76		fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \
 77		abort();                                                           \
 78	} while (0)
 79#define UNREACHABLE(message)                                                      \
 80	do {                                                                          \
 81		fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \
 82		abort();                                                                  \
 83	} while (0)
 84
 85#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
 86#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg)
 87#else
 88#define STATIC_ASSERT(expr, msg) \
 89	typedef char static_assertion_##msg[(expr) ? 1 : -1]
 90#endif
 91
 92// String view - read-only, non-owning reference to a string
 93typedef struct {
 94	const char *data;
 95	size_t length;
 96} stringv;
 97
 98NONSTD_DEF stringv sv_from_cstr(const char *s);
 99NONSTD_DEF stringv sv_from_parts(const char *data, size_t length);
100NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end);
101NONSTD_DEF int sv_equals(stringv a, stringv b);
102NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix);
103NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix);
104
105// String builder - owning, mutable, dynamically growing string buffer
106typedef struct {
107	char *data;
108	size_t length;
109	size_t capacity;
110} stringb;
111
112NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap);
113NONSTD_DEF void sb_free(stringb *sb);
114NONSTD_DEF void sb_ensure(stringb *sb, size_t additional);
115NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s);
116NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv);
117NONSTD_DEF void sb_append_char(stringb *sb, char c);
118NONSTD_DEF stringv sb_as_sv(const stringb *sb);
119
120// Slice - generic non-owning view into an array
121// Usage: SLICE_DEF(int); slice(int) view = ...;
122#define SLICE_DEF(T)   \
123	typedef struct {   \
124		T *data;       \
125		size_t length; \
126	} slice_##T
127
128#define slice(T) slice_##T
129
130#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)})
131
132#define array_as_slice(T, arr) \
133	((slice(T)){.data = (arr).data, .length = (arr).length})
134
135// Dynamic array - generic type-safe growable array using macros
136// Usage: array(int) numbers; array_init(numbers);
137#define array(T)         \
138	struct {             \
139		T *data;         \
140		size_t length;   \
141		size_t capacity; \
142	}
143
144#define array_init(arr)     \
145	do {                    \
146		(arr).capacity = 0; \
147		(arr).data = NULL;  \
148		(arr).length = 0;   \
149	} while (0)
150
151#define array_init_cap(arr, initial_cap)                             \
152	do {                                                             \
153		(arr).capacity = (initial_cap) ? (initial_cap) : 16;         \
154		(arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \
155		(arr).length = 0;                                            \
156	} while (0)
157
158#define array_free(arr)     \
159	do {                    \
160		FREE((arr).data);   \
161		(arr).length = 0;   \
162		(arr).capacity = 0; \
163	} while (0)
164
165#define array_ensure(arr, additional)                                    \
166	do {                                                                 \
167		size_t _needed = (arr).length + (additional);                    \
168		if (_needed > (arr).capacity) {                                  \
169			size_t _new_cap = (arr).capacity ? (arr).capacity : 16;      \
170			while (_new_cap < _needed) {                                 \
171				if (_new_cap > SIZE_MAX / 2) {                           \
172					_new_cap = SIZE_MAX;                                 \
173					break;                                               \
174				}                                                        \
175				_new_cap *= 2;                                           \
176			}                                                            \
177			if (_new_cap < _needed) { /* Overflow or OOM */              \
178				break;                                                   \
179			}                                                            \
180			void *_new_data =                                            \
181				safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \
182			if (_new_data) {                                             \
183				(arr).data = _new_data;                                  \
184				(arr).capacity = _new_cap;                               \
185			}                                                            \
186		}                                                                \
187	} while (0)
188
189#define array_push(arr, value)                    \
190	do {                                          \
191		array_ensure((arr), 1);                   \
192		if ((arr).length < (arr).capacity) {      \
193			(arr).data[(arr).length++] = (value); \
194		}                                         \
195	} while (0)
196
197#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0)
198
199#define array_get(arr, index) ((arr).data[index])
200
201#define array_set(arr, index, value)     \
202	do {                                 \
203		if ((index) < (arr).length) {    \
204			(arr).data[index] = (value); \
205		}                                \
206	} while (0)
207
208#define array_insert(arr, index, value)                              \
209	do {                                                             \
210		if ((index) <= (arr).length) {                               \
211			array_ensure((arr), 1);                                  \
212			if ((arr).length < (arr).capacity) {                     \
213				for (size_t _i = (arr).length; _i > (index); --_i) { \
214					(arr).data[_i] = (arr).data[_i - 1];             \
215				}                                                    \
216				(arr).data[index] = (value);                         \
217				(arr).length++;                                      \
218			}                                                        \
219		}                                                            \
220	} while (0)
221
222#define array_remove(arr, index)                                     \
223	do {                                                             \
224		if ((index) < (arr).length) {                                \
225			for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \
226				(arr).data[_i] = (arr).data[_i + 1];                 \
227			}                                                        \
228			(arr).length--;                                          \
229		}                                                            \
230	} while (0)
231
232#define array_clear(arr)  \
233	do {                  \
234		(arr).length = 0; \
235	} while (0)
236
237#define array_reserve(arr, new_capacity)                                       \
238	do {                                                                       \
239		if ((new_capacity) > (arr).capacity) {                                 \
240			void *_new_data =                                                  \
241				safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \
242			if (_new_data) {                                                   \
243				(arr).data = _new_data;                                        \
244				(arr).capacity = (new_capacity);                               \
245			}                                                                  \
246		}                                                                      \
247	} while (0)
248
249#define array_foreach(arr, var)                                        \
250	for (size_t _i_##var = 0;                                          \
251		 _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \
252		 ++_i_##var)
253
254#define array_foreach_idx(arr, var, index) \
255	for (size_t index = 0;                 \
256		 index < (arr).length && ((var) = (arr).data[index], 1); ++index)
257
258// Arena - block-based memory allocator
259typedef struct {
260	char *ptr;
261	char *end;
262	array(char *) blocks;
263} Arena;
264
265#define ARENA_DEFAULT_BLOCK_SIZE (4096)
266
267NONSTD_DEF Arena arena_make(void);
268NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
269NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
270NONSTD_DEF void arena_free(Arena *a);
271
272// Image - simple RGB image structure
273typedef struct {
274	u8 r, g, b;
275} Color;
276
277typedef struct {
278	u32 width;
279	u32 height;
280	Color *pixels;
281} Canvas;
282
283#define COLOR_RGB(r, g, b) ((Color){(u8)(r), (u8)(g), (u8)(b)})
284#define COLOR_HEX(hex) ((Color){(u8)(((hex) >> 16) & 0xFF), (u8)(((hex) >> 8) & 0xFF), (u8)((hex) & 0xFF)})
285
286#define COLOR_BLACK COLOR_RGB(0, 0, 0)
287#define COLOR_WHITE COLOR_RGB(255, 255, 255)
288#define COLOR_RED COLOR_RGB(255, 0, 0)
289#define COLOR_GREEN COLOR_RGB(0, 255, 0)
290#define COLOR_BLUE COLOR_RGB(0, 0, 255)
291#define COLOR_YELLOW COLOR_RGB(255, 255, 0)
292#define COLOR_MAGENTA COLOR_RGB(255, 0, 255)
293#define COLOR_CYAN COLOR_RGB(0, 255, 255)
294
295NONSTD_DEF Canvas ppm_init(u32 width, u32 height);
296NONSTD_DEF void ppm_free(Canvas *img);
297NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color);
298NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y);
299NONSTD_DEF int ppm_save(const Canvas *img, const char *filename);
300NONSTD_DEF Canvas ppm_read(const char *filename);
301NONSTD_DEF void ppm_fill(Canvas *canvas, Color color);
302NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color);
303NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color);
304NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 x, i32 y, i32 r, Color color);
305NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color);
306
307// File I/O helpers
308NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
309NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
310NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
311NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
312NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
313
314// Logging
315typedef enum {
316	LOG_ERROR,
317	LOG_WARN,
318	LOG_INFO,
319	LOG_DEBUG,
320} LogLevel;
321
322NONSTD_DEF void set_log_level(LogLevel level);
323NONSTD_DEF LogLevel get_log_level_from_env(void);
324NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
325
326#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
327#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
328#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
329#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
330
331#define COLOR_RESET "\033[0m"
332#define COLOR_INFO "\033[32m"
333#define COLOR_DEBUG "\033[36m"
334#define COLOR_WARNING "\033[33m"
335#define COLOR_ERROR "\033[31m"
336
337#ifdef NONSTD_IMPLEMENTATION
338
339NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
340	if (count != 0 && item_size > SIZE_MAX / count) {
341		return NULL;
342	}
343	return malloc(item_size * count);
344}
345
346NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) {
347	if (count != 0 && item_size > SIZE_MAX / count) {
348		return NULL;
349	}
350	return realloc(ptr, item_size * count);
351}
352
353// String View Implementation
354
355NONSTD_DEF stringv sv_from_cstr(const char *s) {
356	return (stringv){.data = s, .length = s ? strlen(s) : 0};
357}
358
359NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) {
360	return (stringv){.data = data, .length = length};
361}
362
363NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) {
364	if (start > sv.length) {
365		start = sv.length;
366	}
367	if (end > sv.length) {
368		end = sv.length;
369	}
370	if (start > end) {
371		start = end;
372	}
373	return (stringv){.data = sv.data + start, .length = end - start};
374}
375
376NONSTD_DEF int sv_equals(stringv a, stringv b) {
377	return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0);
378}
379
380NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) {
381	return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0;
382}
383
384NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
385	return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
386}
387
388// String Builder Implementation
389
390NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
391	sb->capacity = initial_cap ? initial_cap : 16;
392	sb->data = ALLOC(char, sb->capacity);
393	sb->length = 0;
394	if (sb->data) {
395		sb->data[0] = '\0';
396	}
397}
398
399NONSTD_DEF void sb_free(stringb *sb) {
400	FREE(sb->data);
401	sb->length = 0;
402	sb->capacity = 0;
403}
404
405NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) {
406	size_t needed = sb->length + additional + 1;
407	size_t new_cap = sb->capacity;
408
409	if (needed > new_cap) {
410		while (new_cap < needed) {
411			if (new_cap > SIZE_MAX / 2) {
412				new_cap = SIZE_MAX;
413				break;
414			}
415			new_cap *= 2;
416		}
417		if (new_cap < needed)
418			return; // Overflow
419
420		char *new_data = safe_realloc(sb->data, sizeof(char), new_cap);
421		if (new_data) {
422			sb->data = new_data;
423			sb->capacity = new_cap;
424		}
425	}
426}
427
428NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) {
429	if (!s) {
430		return;
431	}
432	size_t slength = strlen(s);
433	sb_ensure(sb, slength);
434	if (sb->length + slength + 1 <= sb->capacity) {
435		memcpy(sb->data + sb->length, s, slength);
436		sb->length += slength;
437		sb->data[sb->length] = '\0';
438	}
439}
440
441NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) {
442	if (!sv.data || sv.length == 0) {
443		return;
444	}
445	sb_ensure(sb, sv.length);
446	if (sb->length + sv.length + 1 <= sb->capacity) {
447		memcpy(sb->data + sb->length, sv.data, sv.length);
448		sb->length += sv.length;
449		sb->data[sb->length] = '\0';
450	}
451}
452
453NONSTD_DEF void sb_append_char(stringb *sb, char c) {
454	sb_ensure(sb, 1);
455	if (sb->length + 2 <= sb->capacity) {
456		sb->data[sb->length++] = c;
457		sb->data[sb->length] = '\0';
458	}
459}
460
461NONSTD_DEF stringv sb_as_sv(const stringb *sb) {
462	return (stringv){.data = sb->data, .length = sb->length};
463}
464
465NONSTD_DEF Arena arena_make(void) {
466	Arena a = {0};
467	array_init(a.blocks);
468	return a;
469}
470
471// Arena Implementation
472
473NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
474	size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
475	char *block = ALLOC(char, size);
476	a->ptr = block;
477	a->end = block + size;
478	array_push(a->blocks, block);
479}
480
481NONSTD_DEF void *arena_alloc(Arena *a, size_t size) {
482	// Align to 8 bytes basically
483	size_t align = sizeof(void *);
484	uintptr_t current = (uintptr_t)a->ptr;
485	uintptr_t aligned = (current + align - 1) & ~(align - 1);
486	uintptr_t end = (uintptr_t)a->end;
487
488	// Check for overflow (aligned wrapped around) or out of bounds (aligned >= end)
489	// or not enough space ((end - aligned) < size)
490	if (aligned < current || aligned >= end || (end - aligned) < size) {
491		arena_grow(a, size);
492		current = (uintptr_t)a->ptr;
493		aligned = (current + align - 1) & ~(align - 1);
494		end = (uintptr_t)a->end;
495	}
496
497	// Double check after grow (in case grow failed or size is just too huge)
498	if (aligned < current || aligned >= end || (end - aligned) < size) {
499		return NULL;
500	}
501
502	a->ptr = (char *)(aligned + size);
503	return (void *)aligned;
504}
505
506NONSTD_DEF void arena_free(Arena *a) {
507	char *block;
508	array_foreach(a->blocks, block) { FREE(block); }
509	array_free(a->blocks);
510	a->ptr = NULL;
511	a->end = NULL;
512}
513
514// File I/O Implementation
515
516NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
517	FILE *f = fopen(filepath, "rb");
518	if (!f) {
519		return NULL;
520	}
521
522	fseek(f, 0, SEEK_END);
523	long length = ftell(f);
524	fseek(f, 0, SEEK_SET);
525
526	if (length < 0) {
527		fclose(f);
528		return NULL;
529	}
530
531	size_t size = (size_t)length;
532	char *buffer = ALLOC(char, size + 1);
533	if (!buffer) {
534		fclose(f);
535		return NULL;
536	}
537
538	size_t read = fread(buffer, 1, size, f);
539	fclose(f);
540
541	if (read != size) {
542		FREE(buffer);
543		return NULL;
544	}
545
546	buffer[size] = '\0';
547	if (out_size) {
548		*out_size = size;
549	}
550
551	return buffer;
552}
553
554NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) {
555	FILE *f = fopen(filepath, "wb");
556	if (!f) {
557		return 0;
558	}
559
560	size_t written = fwrite(data, 1, size, f);
561	fclose(f);
562
563	return written == size;
564}
565
566NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
567	size_t size = 0;
568	char *data = read_entire_file(filepath, &size);
569	stringb sb = {0};
570	if (data) {
571		sb.data = data;
572		sb.length = size;
573		sb.capacity = size + 1;
574	}
575	return sb;
576}
577
578NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) {
579	return write_entire_file(filepath, sv.data, sv.length);
580}
581
582NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
583	return write_entire_file(filepath, sb->data, sb->length);
584}
585
586// Logging Implementation
587
588static LogLevel max_level = LOG_INFO;
589
590static const char *level_strings[] = {
591	"ERROR",
592	"WARN",
593	"INFO",
594	"DEBUG",
595};
596
597static const char *level_colors[] = {
598	COLOR_ERROR,
599	COLOR_WARNING,
600	COLOR_INFO,
601	COLOR_DEBUG,
602};
603
604NONSTD_DEF void set_log_level(LogLevel level) {
605	max_level = level;
606}
607
608NONSTD_DEF LogLevel get_log_level_from_env(void) {
609	const char *env = getenv("LOG_LEVEL");
610	if (env) {
611		int level = atoi(env);
612		if (level >= 0 && level <= 3) {
613			return (LogLevel)level;
614		}
615	}
616
617	return max_level;
618}
619
620NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
621	if (max_level < level)
622		return;
623
624	struct timeval tv;
625	gettimeofday(&tv, NULL);
626	struct tm *tm_info = localtime(&tv.tv_sec);
627
628	char time_str[24];
629	strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
630
631	const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
632	const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
633
634	const char *log_format = "%s[%s.%03d] [%-5s] ";
635	fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
636
637	va_list args;
638	va_start(args, format);
639	vfprintf(stream, format, args);
640	va_end(args);
641
642	fprintf(stream, "%s\n", reset);
643	fflush(stream);
644}
645
646// PPM Image Implementation
647
648NONSTD_DEF Canvas ppm_init(u32 width, u32 height) {
649	Canvas img = {0};
650	img.width = width;
651	img.height = height;
652	img.pixels = ALLOC(Color, width * height);
653	if (img.pixels) {
654		memset(img.pixels, 0, sizeof(Color) * width * height);
655	}
656	return img;
657}
658
659NONSTD_DEF void ppm_free(Canvas *img) {
660	if (img->pixels) {
661		FREE(img->pixels);
662	}
663	img->width = 0;
664	img->height = 0;
665}
666
667NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color) {
668	if (x < img->width && y < img->height) {
669		img->pixels[y * img->width + x] = color;
670	}
671}
672
673NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y) {
674	if (x < img->width && y < img->height) {
675		return img->pixels[y * img->width + x];
676	}
677	return (Color){0, 0, 0};
678}
679
680NONSTD_DEF int ppm_save(const Canvas *img, const char *filename) {
681	FILE *f = fopen(filename, "w");
682	if (!f) {
683		return 0;
684	}
685
686	fprintf(f, "P3\n%u %u\n255\n", img->width, img->height);
687	for (u32 y = 0; y < img->height; ++y) {
688		for (u32 x = 0; x < img->width; ++x) {
689			Color c = ppm_get_pixel(img, x, y);
690			fprintf(f, "%d %d %d ", c.r, c.g, c.b);
691		}
692		fprintf(f, "\n");
693	}
694
695	fclose(f);
696	return 1;
697}
698
699NONSTD_DEF Canvas ppm_read(const char *filename) {
700	Canvas img = {0};
701	FILE *f = fopen(filename, "r");
702	if (!f) {
703		return img;
704	}
705
706	char magic[3];
707	if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P3") != 0) {
708		fclose(f);
709		return img;
710	}
711
712	u32 w, h, max_val;
713	if (fscanf(f, "%u %u %u", &w, &h, &max_val) != 3) {
714		fclose(f);
715		return img;
716	}
717
718	img = ppm_init(w, h);
719	if (!img.pixels) {
720		fclose(f);
721		return img;
722	}
723
724	for (u32 i = 0; i < w * h; ++i) {
725		int r, g, b;
726		if (fscanf(f, "%d %d %d", &r, &g, &b) != 3) {
727			ppm_free(&img);
728			fclose(f);
729			return (Canvas){0};
730		}
731		img.pixels[i] = (Color){(u8)r, (u8)g, (u8)b};
732	}
733
734	fclose(f);
735	return img;
736}
737
738NONSTD_DEF void ppm_fill(Canvas *canvas, Color color) {
739	for (u32 i = 0; i < canvas->width * canvas->height; ++i) {
740		canvas->pixels[i] = color;
741	}
742}
743
744NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color) {
745	if (w == 0 || h == 0) {
746		return;
747	}
748	for (u32 i = x; i < x + w; ++i) {
749		ppm_set_pixel(canvas, i, y, color);
750		ppm_set_pixel(canvas, i, y + h - 1, color);
751	}
752	for (u32 j = y; j < y + h; ++j) {
753		ppm_set_pixel(canvas, x, j, color);
754		ppm_set_pixel(canvas, x + w - 1, j, color);
755	}
756}
757
758NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color) {
759	i32 dx = abs(x1 - x0);
760	i32 dy = -abs(y1 - y0);
761	i32 sx = x0 < x1 ? 1 : -1;
762	i32 sy = y0 < y1 ? 1 : -1;
763	i32 err = dx + dy;
764
765	while (1) {
766		ppm_set_pixel(canvas, (u32)x0, (u32)y0, color);
767		if (x0 == x1 && y0 == y1) {
768			break;
769		}
770
771		i32 e2 = 2 * err;
772		if (e2 >= dy) {
773			err += dy;
774			x0 += sx;
775		}
776		if (e2 <= dx) {
777			err += dx;
778			y0 += sy;
779		}
780	}
781}
782
783NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 xm, i32 ym, i32 r, Color color) {
784	i32 x = -r, y = 0, err = 2 - 2 * r;
785	do {
786		ppm_set_pixel(canvas, (u32)(xm - x), (u32)(ym + y), color);
787		ppm_set_pixel(canvas, (u32)(xm - y), (u32)(ym - x), color);
788		ppm_set_pixel(canvas, (u32)(xm + x), (u32)(ym - y), color);
789		ppm_set_pixel(canvas, (u32)(xm + y), (u32)(ym + x), color);
790		r = err;
791		if (r <= y) {
792			err += ++y * 2 + 1;
793		}
794		if (r > x || err > y) {
795			err += ++x * 2 + 1;
796		}
797	} while (x < 0);
798}
799
800NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color) {
801	ppm_draw_line(canvas, x0, y0, x1, y1, color);
802	ppm_draw_line(canvas, x1, y1, x2, y2, color);
803	ppm_draw_line(canvas, x2, y2, x0, y0, color);
804}
805
806#endif // NONSTD_IMPLEMENTATION
807
808#endif // NONSTD_H
809
810/*
811BSD 2-Clause License
812
813Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
814
815Redistribution and use in source and binary forms, with or without
816modification, are permitted provided that the following conditions are met:
817
8181. Redistributions of source code must retain the above copyright notice, this
819   list of conditions and the following disclaimer.
820
8212. Redistributions in binary form must reproduce the above copyright notice,
822   this list of conditions and the following disclaimer in the documentation
823   and/or other materials provided with the distribution.
824
825THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
826AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
827IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
828DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
829FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
830DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
831SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
832CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
833OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
834OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
835*/