Initial commit

This commit is contained in:
2026-05-30 15:05:58 +10:00
commit 2bc38d7587
11 changed files with 5722 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
libmarl.so

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright 2026 Maxwell Jeffress
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

29
Makefile Normal file
View File

@@ -0,0 +1,29 @@
HEADER = src/marl.h
CC = cc
TARGET = libmarl.so
FLAGS = -shared -fPIC
SRCS_X11 = src/x11/marl.c
FLAGS_X11 = -lxcb -lm
PREFIX ?= /usr/local
DESTDIR ?=
.PHONY: default
default: x11
.PHONY: x11
x11: $(SRCS_X11)
$(CC) $(SRCS_X11) $(FLAGS_X11) $(FLAGS) -o $(TARGET)
.PHONY: clean
clean:
rm $(TARGET)
.PHONY: install
install: $(TARGET)
mkdir -p $(DESTDIR)$(PREFIX)/include $(DESTDIR)$(PREFIX)/lib
cp $(HEADER) $(DESTDIR)$(PREFIX)/include/
cp $(TARGET) $(DESTDIR)$(PREFIX)/lib/
echo "$(DESTDIR)$(PREFIX)/lib" | tee $(DESTDIR)/etc/ld.so.conf.d/marl.conf
ldconfig

82
README.md Normal file
View File

@@ -0,0 +1,82 @@
# Max's Awesome Rendering Library
```c
#include <marl.h>
#include <stdio.h>
int main() {
Marl_Window* window = Marl.createWindow("dingus", 800, 600);
if (window == NULL) {
printf("Window didn't create lmao\n");
return 1;
}
Marl_Font* font = Marl.Font.load("/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 24.0f);
int counter = 0;
while (1) {
Marl_Event e = Marl.poll(window);
if (e.type == Marl_Quit) {
break;
}
if (e.type == Marl_MouseDown) {
counter++;
}
char buf[256];
if (counter == 0) {
snprintf(buf, sizeof(buf), "Click the window!");
} else {
snprintf(buf, sizeof(buf), "You clicked %d times!", counter);
}
Marl.clearScreen(window, 0xFF000000);
Marl.drawRect(window, 100, 100, 200, 150, 0xFF0000FF);
Marl.drawText(window, font, buf, 100, 80, 0xFFFFFFFF);
Marl.render(window);
}
Marl.Font.destroy(&font);
Marl.destroyWindow(&window);
}
```
## Building
```shell
# make <backend>
# Example
make x11
```
Currently supported backends:
* `x11`
## Installing
Build your preferred backend, then:
```shell
sudo make install
```
## Usage
```shell
gcc myprogram.c -lmarl -o myprogram
```
## Creating your own backend
Create a new folder in `src` with the name of your backend, and copy the `template/marl.c` file into the folder.
## License
Marl is licensed to you under the MIT license.
## Credits
* stb_truetype.h: Font rendering on x11
* XCB: x11 backend

5079
src/include/stb_truetype.h Normal file

File diff suppressed because it is too large Load Diff

88
src/marl.h Normal file
View File

@@ -0,0 +1,88 @@
#ifndef MARL_H
#define MARL_H
/*
* Marl
* Copyright 2026 Maxwell Jeffress
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdint.h>
typedef struct Marl_Window Marl_Window;
typedef struct Marl_Font Marl_Font;
typedef enum Marl_EventType {
Marl_None,
Marl_Quit,
Marl_MouseMove,
Marl_MouseDown,
Marl_MouseUp,
} Marl_EventType;
typedef enum Marl_MouseButton {
Marl_MouseLeft = 1,
Marl_MouseMiddle = 2,
Marl_MouseRight = 3,
} Marl_MouseButton;
typedef struct {
Marl_EventType type;
int x, y;
Marl_MouseButton button;
} Marl_Event;
struct _Marl {
/*
* Creates a heap-allocated Marl window.
* Marl owns this memory, you do not need to free it,
* only destroy it with Marl.destroyWindow when required.
*/
Marl_Window* (*createWindow)(const char* title, int width, int height);
/*
* Polls the backend for events.
*/
Marl_Event (*poll)(Marl_Window* window);
/*
* Clears the screen using your colour of choice.
*/
void (*clearScreen)(Marl_Window* window, uint32_t colour);
/*
* Draws the specified rectangle in the window.
*/
void (*drawRect)(Marl_Window* window, int x, int y, int width, int height, uint32_t colour);
/*
* Draws the specified text in the window.
*/
void (*drawText)(Marl_Window* window, Marl_Font* font, const char* text, int x, int y, uint32_t colour);
/*
* Renders a Marl_Window frame.
*/
int (*render)(Marl_Window* window);
/*
* Destroys a Marl_Window.
* You do not need to free the pointer afterwards.
*/
void (*destroyWindow)(Marl_Window** window);
struct {
Marl_Font* (*load)(const char* path, float size);
void (*destroy)(Marl_Font** font);
int (*textWidth)(Marl_Font* font, const char* text);
} Font;
};
extern struct _Marl Marl;
#endif // MARL_H

59
src/template/marl.c Normal file
View File

@@ -0,0 +1,59 @@
/*
* Template for a Marl implementation.
*/
#include "marl.h"
#include <stdint.h>
#include <stdlib.h>
struct Marl_Window {};
struct Marl_Font {};
Marl_Window* _marl_createWindow(const char* title, int width, int height) {
return malloc(sizeof(Marl_Window));
}
Marl_Event _marl_poll(Marl_Window* window) {
return (Marl_Event){Marl_None, 0, 0, Marl_MouseLeft};
}
void _marl_clearScreen(Marl_Window* window, uint32_t colour) {}
void _marl_drawRect(Marl_Window* window, int x, int y, int width, int height, uint32_t colour) {}
void _marl_drawText(Marl_Window* window, Marl_Font* font, const char* text, int x, int y, uint32_t colour) {}
int _marl_render(Marl_Window* window) {
return 0;
}
void _marl_destroyWindow(Marl_Window** windowptr) {
free(*windowptr);
*windowptr = NULL;
}
Marl_Font* _marl_font_load(const char* path, float size) {
return malloc(sizeof(Marl_Font));
}
void _marl_font_destroy(Marl_Font** font) {
free(*font);
*font = NULL;
}
int _marl_font_textWidth(Marl_Font* font, const char* text) {
return 0;
}
struct _Marl Marl = {
/*
* General rendering
*/
_marl_createWindow, _marl_poll, _marl_clearScreen, _marl_drawRect, _marl_drawText, _marl_render, _marl_destroyWindow,
/*
* Text and fonts
*/
{ _marl_font_load, _marl_font_destroy, _marl_font_textWidth }
};

336
src/x11/marl.c Normal file
View File

@@ -0,0 +1,336 @@
/*
* Marl
* Copyright 2026 Maxwell Jeffress
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Marl implementation using X11.
*/
#include "../marl.h"
#include <string.h>
#include <xcb/xcb.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <xcb/xproto.h>
#define STB_TRUETYPE_IMPLEMENTATION
#include "../include/stb_truetype.h"
struct Marl_Window {
/*
* XCB variables and pointers
*/
xcb_connection_t* conn;
const xcb_setup_t* setup;
xcb_screen_t* screen;
xcb_window_t window;
xcb_gcontext_t gc;
xcb_atom_t delete_atom;
/*
* Marl pixel buffer
*/
int width, height;
uint32_t* pixels;
};
struct Marl_Font {
stbtt_fontinfo info;
unsigned char* data; // raw TTF file contents
float scale;
int ascent, descent, line_gap;
};
static inline uint32_t _marl_blend(uint32_t dst, uint32_t src, uint8_t alpha) {
uint8_t sr = (src >> 16) & 0xFF;
uint8_t sg = (src >> 8) & 0xFF;
uint8_t sb = (src >> 0) & 0xFF;
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = (dst >> 0) & 0xFF;
uint8_t rr = (sr * alpha + dr * (255 - alpha)) / 255;
uint8_t rg = (sg * alpha + dg * (255 - alpha)) / 255;
uint8_t rb = (sb * alpha + db * (255 - alpha)) / 255;
return 0xFF000000 | (rr << 16) | (rg << 8) | rb;
}
/*
* =================
* GENERAL FUNCTIONS
* =================
*/
Marl_Window* _marl_createWindow(const char* title, int width, int height) {
Marl_Window* window = malloc(sizeof(Marl_Window));
window->conn = xcb_connect(NULL, NULL);
if (xcb_connection_has_error(window->conn)) {
fprintf(stderr, "marl: couldn't connect to X server, returning\n");
return NULL;
}
window->setup = xcb_get_setup(window->conn);
window->screen = xcb_setup_roots_iterator(window->setup).data;
window->window = xcb_generate_id(window->conn);
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
uint32_t values[] = {
window->screen->black_pixel,
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION
};
xcb_create_window(
window->conn,
XCB_COPY_FROM_PARENT,
window->window,
window->screen->root,
0, 0, width, height,
0,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
window->screen->root_visual,
mask,
values
);
xcb_change_property(
window->conn,
XCB_PROP_MODE_REPLACE,
window->window,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
strlen(title),
title
);
xcb_intern_atom_reply_t *proto_reply = xcb_intern_atom_reply(window->conn,
xcb_intern_atom(window->conn, 1, 12, "WM_PROTOCOLS"), NULL);
xcb_intern_atom_reply_t *delete_reply = xcb_intern_atom_reply(window->conn,
xcb_intern_atom(window->conn, 0, 16, "WM_DELETE_WINDOW"), NULL);
xcb_change_property(window->conn, XCB_PROP_MODE_REPLACE, window->window,
proto_reply->atom, XCB_ATOM_ATOM, 32, 1, &delete_reply->atom);
window->delete_atom = delete_reply->atom;
free(proto_reply);
free(delete_reply);
window->gc = xcb_generate_id(window->conn);
xcb_create_gc(window->conn, window->gc, window->window, 0, NULL);
xcb_map_window(window->conn, window->window);
xcb_flush(window->conn);
window->width = width;
window->height = height;
window->pixels = malloc(width * height * sizeof(uint32_t));
for (int i = 0; i < width * height; i++) {
window->pixels[i] = 0xFFFF0000;
}
return window;
}
Marl_Event _marl_poll(Marl_Window* window) {
xcb_generic_event_t* event;
Marl_Event result = {Marl_None, 0, 0, 0};
while ((event = xcb_poll_for_event(window->conn)) != NULL) {
switch (event->response_type & ~0x80) {
case XCB_MOTION_NOTIFY: {
xcb_motion_notify_event_t* e = (xcb_motion_notify_event_t*)event;
result.type = Marl_MouseMove;
result.x = e->event_x;
result.y = e->event_y;
break;
}
case XCB_BUTTON_PRESS: {
xcb_button_press_event_t* e = (xcb_button_press_event_t*)event;
result.type = Marl_MouseDown;
result.x = e->event_x;
result.y = e->event_y;
result.button = e->detail;
free(event);
return result;
}
case XCB_BUTTON_RELEASE: {
xcb_button_press_event_t* e = (xcb_button_press_event_t*)event;
result.type = Marl_MouseUp;
result.x = e->event_x;
result.y = e->event_y;
result.button = e->detail;
free(event);
return result;
}
case XCB_CLIENT_MESSAGE: {
if (((xcb_client_message_event_t*)event)->data.data32[0] == window->delete_atom) {
free(event);
result.type = Marl_Quit;
return result;
}
break;
}
}
free(event);
}
return result;
}
void _marl_clearScreen(Marl_Window* window, uint32_t colour) {
for (int i = 0; i < window->width * window->height; i++) {
window->pixels[i] = colour;
}
}
void _marl_drawRect(Marl_Window* window, int x, int y, int w, int h, uint32_t colour) {
// clamp to window bounds
int x0 = x < 0 ? 0 : x;
int y0 = y < 0 ? 0 : y;
int x1 = (x + w) > window->width ? window->width : (x + w);
int y1 = (y + h) > window->height ? window->height : (y + h);
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
window->pixels[row * window->width + col] = colour;
}
int _marl_render(Marl_Window* window) {
xcb_put_image(
window->conn,
XCB_IMAGE_FORMAT_Z_PIXMAP,
window->window,
window->gc,
window->width,
window->height,
0, 0,
0,
window->screen->root_depth,
window->width * window->height * sizeof(uint32_t),
(uint8_t*) window->pixels
);
xcb_flush(window->conn);
return 0;
}
void _marl_drawText(Marl_Window* window, Marl_Font* font, const char* text, int x, int y, uint32_t colour) {
int cx = x; // current x cursor
for (const char* ch = text; *ch; ch++) {
int advance, lsb;
stbtt_GetCodepointHMetrics(&font->info, *ch, &advance, &lsb);
// get the bitmap for this glyph
int gx, gy, gw, gh;
unsigned char* bitmap = stbtt_GetCodepointBitmap(
&font->info, font->scale, font->scale,
*ch, &gw, &gh, &gx, &gy
);
// gy is the offset from the baseline
int draw_x = cx + gx;
int draw_y = y + font->ascent + gy;
for (int row = 0; row < gh; row++) {
for (int col = 0; col < gw; col++) {
int px = draw_x + col;
int py = draw_y + row;
if (px < 0 || px >= window->width || py < 0 || py >= window->height)
continue;
uint8_t alpha = bitmap[row * gw + col];
if (alpha == 0) continue;
uint32_t dst = window->pixels[py * window->width + px];
window->pixels[py * window->width + px] = _marl_blend(dst, colour, alpha);
}
}
stbtt_FreeBitmap(bitmap, NULL);
cx += (int)(advance * font->scale);
}
}
void _marl_destroyWindow(Marl_Window** windowptr) {
Marl_Window* window = *windowptr;
free(window->pixels);
xcb_disconnect(window->conn);
free(window);
*windowptr = NULL;
}
/*
* ===============
* FONT MANAGEMENT
* ===============
*/
Marl_Font* _marl_font_load(const char* path, float size) {
FILE* f = fopen(path, "rb");
if (!f) { fprintf(stderr, "marl: couldn't open font %s\n", path); return NULL; }
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
unsigned char* data = malloc(len);
fread(data, 1, len, f);
fclose(f);
Marl_Font* font = malloc(sizeof(Marl_Font));
font->data = data;
stbtt_InitFont(&font->info, data, stbtt_GetFontOffsetForIndex(data, 0));
font->scale = stbtt_ScaleForPixelHeight(&font->info, size);
int ascent, descent, line_gap;
stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap);
font->ascent = (int)(ascent * font->scale);
font->descent = (int)(descent * font->scale);
font->line_gap = (int)(line_gap * font->scale);
return font;
}
void _marl_font_destroy(Marl_Font** fontptr) {
Marl_Font* font = *fontptr;
free(font->data);
free(font);
*fontptr = NULL;
}
int _marl_font_textWidth(Marl_Font* font, const char* text) {
int width = 0;
for (const char* ch = text; *ch; ch++) {
int advance, lsb;
stbtt_GetCodepointHMetrics(&font->info, *ch, &advance, &lsb);
width += (int)(advance * font->scale);
}
return width;
}
struct _Marl Marl = {
/*
* General rendering
*/
_marl_createWindow, _marl_poll, _marl_clearScreen, _marl_drawRect, _marl_drawText, _marl_render, _marl_destroyWindow,
/*
* Text and fonts
*/
{ _marl_font_load, _marl_font_destroy, _marl_font_textWidth }
};

1
test/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
test

2
test/Makefile Normal file
View File

@@ -0,0 +1,2 @@
test: main.c
cc main.c -lmarl -o test

38
test/main.c Normal file
View File

@@ -0,0 +1,38 @@
#include <marl.h>
#include <stdio.h>
int main() {
Marl_Window* window = Marl.createWindow("Click Me!", 800, 600);
if (window == NULL) {
printf("Window didn't create lmao\n");
return 1;
}
Marl_Font* font = Marl.Font.load("/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 24.0f);
int counter = 0;
while (1) {
Marl_Event e = Marl.poll(window);
if (e.type == Marl_Quit) {
break;
}
if (e.type == Marl_MouseDown) {
counter++;
}
char buf[256];
if (counter == 0) {
snprintf(buf, sizeof(buf), "Click the window!");
} else {
snprintf(buf, sizeof(buf), "You clicked %d times!", counter);
}
Marl.clearScreen(window, 0xFF000000);
Marl.drawRect(window, 100, 100, 200, 150, 0xFF0000FF);
Marl.drawText(window, font, buf, 100, 80, 0xFFFFFFFF);
Marl.render(window);
}
Marl.Font.destroy(&font);
Marl.destroyWindow(&window);
}