feat: S3 sleep, PDF viewer graphics rendering, live user environment
This commit is contained in:
@@ -320,7 +320,7 @@ void desktop_draw_net_popup(DesktopState* ds) {
|
||||
|
||||
rows[0].label = "IP";
|
||||
if (connected) format_ip(rows[0].value, nc.ipAddress);
|
||||
else montauk::strcpy(rows[0].value, "\xE2\x80\x94"); // em dash
|
||||
else montauk::strcpy(rows[0].value, "0.0.0.0");
|
||||
|
||||
rows[1].label = "Subnet";
|
||||
format_ip(rows[1].value, nc.subnetMask);
|
||||
|
||||
@@ -238,8 +238,11 @@ static bool copy_recursive(const char* src_dir, const char* dst_dir,
|
||||
} else {
|
||||
// Skip ramdisk and limine.conf — installed system boots from
|
||||
// disk and gets a fresh config without the ramdisk module.
|
||||
// Skip setup.toml — live/setup environment config that should
|
||||
// not be present on the installed system.
|
||||
if (strcmp(basename, "ramdisk.tar") == 0) continue;
|
||||
if (strcmp(basename, "limine.conf") == 0) continue;
|
||||
if (strcmp(basename, "setup.toml") == 0) continue;
|
||||
|
||||
// It's a file — copy it
|
||||
char src_path[256];
|
||||
|
||||
@@ -751,6 +751,38 @@ extern "C" void _start() {
|
||||
// Load wallpaper from system desktop config
|
||||
load_login_wallpaper(ls);
|
||||
|
||||
// Check for setup environment (installation medium / ramdisk boot).
|
||||
// If setup.toml exists with environment.mode = "setup", skip login
|
||||
// entirely and launch the desktop in a passwordless live session.
|
||||
{
|
||||
auto doc = montauk::config::load("setup");
|
||||
const char* mode = doc.get_string("environment.mode", "");
|
||||
if (montauk::streq(mode, "setup")) {
|
||||
const char* user = doc.get_string("session.username", "liveuser");
|
||||
const char* display = doc.get_string("session.display_name", "Live User");
|
||||
const char* role = doc.get_string("session.role", "admin");
|
||||
|
||||
// Create a temporary user entry so the desktop and apps can
|
||||
// query session info normally.
|
||||
montauk::fmkdir("0:/users");
|
||||
montauk::user::create_user(user, display, "", role);
|
||||
montauk::user::set_session(user);
|
||||
doc.destroy();
|
||||
|
||||
// Launch desktop directly -- no login required
|
||||
int pid = montauk::spawn("0:/os/desktop.elf", user);
|
||||
if (pid >= 0) {
|
||||
montauk::waitpid(pid);
|
||||
}
|
||||
// Desktop exited (reboot/shutdown expected in setup mode).
|
||||
// Clear session and fall through to normal login in case
|
||||
// setup.toml was removed during installation.
|
||||
montauk::user::clear_session();
|
||||
} else {
|
||||
doc.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if users.toml exists (first boot detection)
|
||||
int fh = montauk::open("0:/config/users.toml");
|
||||
if (fh < 0) {
|
||||
|
||||
@@ -84,6 +84,46 @@ void px_fill_rounded(uint32_t* px, int bw, int bh,
|
||||
}
|
||||
}
|
||||
|
||||
void px_line(uint32_t* px, int bw, int bh,
|
||||
int x0, int y0, int x1, int y1, int thick, Color c) {
|
||||
if (thick < 1) thick = 1;
|
||||
int half = thick / 2;
|
||||
|
||||
// Horizontal line
|
||||
if (y0 == y1) {
|
||||
int lx = x0 < x1 ? x0 : x1;
|
||||
int rx = x0 > x1 ? x0 : x1;
|
||||
px_fill(px, bw, bh, lx, y0 - half, rx - lx + 1, thick, c);
|
||||
return;
|
||||
}
|
||||
// Vertical line
|
||||
if (x0 == x1) {
|
||||
int ty = y0 < y1 ? y0 : y1;
|
||||
int by = y0 > y1 ? y0 : y1;
|
||||
px_fill(px, bw, bh, x0 - half, ty, thick, by - ty + 1, c);
|
||||
return;
|
||||
}
|
||||
// General case: Bresenham with thickness
|
||||
uint32_t v = c.to_pixel();
|
||||
int dx = x1 - x0; int sx = dx > 0 ? 1 : -1; if (dx < 0) dx = -dx;
|
||||
int dy = y1 - y0; int sy = dy > 0 ? 1 : -1; if (dy < 0) dy = -dy;
|
||||
int err = dx - dy;
|
||||
int cx = x0, cy = y0;
|
||||
while (true) {
|
||||
for (int oy = -half; oy <= half; oy++) {
|
||||
for (int ox = -half; ox <= half; ox++) {
|
||||
int px_x = cx + ox, py_y = cy + oy;
|
||||
if (px_x >= 0 && px_x < bw && py_y >= 0 && py_y < bh)
|
||||
px[py_y * bw + px_x] = v;
|
||||
}
|
||||
}
|
||||
if (cx == x1 && cy == y1) break;
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) { err -= dy; cx += sx; }
|
||||
if (e2 < dx) { err += dx; cy += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
int str_len(const char* s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
|
||||
@@ -36,6 +36,32 @@ static int next_token(const uint8_t* d, int len, int p, Token* tok) {
|
||||
|
||||
if (p >= len) { tok->type = TOK_EOF; return p; }
|
||||
|
||||
// Dictionary << ... >> (e.g., marked-content properties in tagged PDFs)
|
||||
if (d[p] == '<' && p + 1 < len && d[p + 1] == '<') {
|
||||
p += 2;
|
||||
int depth = 1;
|
||||
while (p + 1 < len && depth > 0) {
|
||||
if (d[p] == '<' && d[p + 1] == '<') { depth++; p += 2; }
|
||||
else if (d[p] == '>' && d[p + 1] == '>') { depth--; p += 2; }
|
||||
else p++;
|
||||
}
|
||||
if (p < len && depth > 0) p++; // skip stray last char on malformed input
|
||||
tok->type = TOK_NAME;
|
||||
tok->str[0] = '\0';
|
||||
tok->str_len = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Stray > or >> (skip gracefully)
|
||||
if (d[p] == '>') {
|
||||
p++;
|
||||
if (p < len && d[p] == '>') p++;
|
||||
tok->type = TOK_NAME;
|
||||
tok->str[0] = '\0';
|
||||
tok->str_len = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Literal string
|
||||
if (d[p] == '(') {
|
||||
p++;
|
||||
@@ -249,6 +275,65 @@ static void add_text_item(PdfPage* page, float x, float y, float size,
|
||||
item->text[copy] = '\0';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Graphics State
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MAX_PATH_SEGS = 512;
|
||||
static constexpr int MAX_PATH_RECTS = 128;
|
||||
static constexpr int MAX_GFX_STACK = 16;
|
||||
|
||||
struct PathSeg { float x1, y1, x2, y2; };
|
||||
struct PathRect { float x, y, w, h; };
|
||||
|
||||
struct GfxState {
|
||||
float ctm[6]; // current transformation matrix
|
||||
float line_width;
|
||||
uint8_t stroke_r, stroke_g, stroke_b;
|
||||
uint8_t fill_r, fill_g, fill_b;
|
||||
};
|
||||
|
||||
static void ctm_transform(const float* ctm, float x, float y, float* ox, float* oy) {
|
||||
*ox = ctm[0] * x + ctm[2] * y + ctm[4];
|
||||
*oy = ctm[1] * x + ctm[3] * y + ctm[5];
|
||||
}
|
||||
|
||||
static float ctm_scale(const float* ctm) {
|
||||
// Approximate scale factor (geometric mean of axis scales)
|
||||
float sx = ctm[0] * ctm[0] + ctm[1] * ctm[1];
|
||||
float sy = ctm[2] * ctm[2] + ctm[3] * ctm[3];
|
||||
// sqrt approximation: just use max for simplicity
|
||||
if (sx < sy) sx = sy;
|
||||
// Manual sqrt via Newton's method (2 iterations)
|
||||
if (sx <= 0) return 1.0f;
|
||||
float g = sx;
|
||||
g = 0.5f * (g + sx / g);
|
||||
g = 0.5f * (g + sx / g);
|
||||
return g;
|
||||
}
|
||||
|
||||
static void add_gfx_item(PdfPage* page, GfxType type,
|
||||
float x1, float y1, float x2, float y2,
|
||||
float lw, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (page->gfx_count >= page->gfx_cap) {
|
||||
int new_cap = page->gfx_cap ? page->gfx_cap * 2 : 64;
|
||||
GraphicsItem* ni = (GraphicsItem*)montauk::malloc(new_cap * sizeof(GraphicsItem));
|
||||
if (!ni) return;
|
||||
if (page->gfx_items) {
|
||||
montauk::memcpy(ni, page->gfx_items, page->gfx_count * sizeof(GraphicsItem));
|
||||
montauk::mfree(page->gfx_items);
|
||||
}
|
||||
page->gfx_items = ni;
|
||||
page->gfx_cap = new_cap;
|
||||
}
|
||||
GraphicsItem* item = &page->gfx_items[page->gfx_count++];
|
||||
item->type = type;
|
||||
item->x1 = x1; item->y1 = y1;
|
||||
item->x2 = x2; item->y2 = y2;
|
||||
item->line_width = lw;
|
||||
item->r = r; item->g = g; item->b = b;
|
||||
}
|
||||
|
||||
static void matrix_multiply(float* result, const float* a, const float* b) {
|
||||
// Multiply two 3x3 matrices represented as [a b c d e f]
|
||||
// where the matrix is: [a b 0]
|
||||
@@ -368,6 +453,24 @@ void parse_page(int page_idx, int page_obj_num) {
|
||||
ts.tounicode = nullptr;
|
||||
ts.embedded_font = nullptr;
|
||||
|
||||
// Graphics state
|
||||
GfxState gs;
|
||||
gs.ctm[0] = 1; gs.ctm[1] = 0; gs.ctm[2] = 0; gs.ctm[3] = 1;
|
||||
gs.ctm[4] = 0; gs.ctm[5] = 0;
|
||||
gs.line_width = 1.0f;
|
||||
gs.stroke_r = 0; gs.stroke_g = 0; gs.stroke_b = 0;
|
||||
gs.fill_r = 0; gs.fill_g = 0; gs.fill_b = 0;
|
||||
|
||||
GfxState gs_stack[MAX_GFX_STACK];
|
||||
int gs_depth = 0;
|
||||
|
||||
// Path accumulation
|
||||
PathSeg* path_segs = (PathSeg*)montauk::malloc(MAX_PATH_SEGS * sizeof(PathSeg));
|
||||
PathRect* path_rects = (PathRect*)montauk::malloc(MAX_PATH_RECTS * sizeof(PathRect));
|
||||
int seg_count = 0, rect_count = 0;
|
||||
float path_cx = 0, path_cy = 0; // current point
|
||||
float path_sx = 0, path_sy = 0; // subpath start
|
||||
|
||||
bool in_text = false;
|
||||
Token tok;
|
||||
int pos = 0;
|
||||
@@ -523,7 +626,9 @@ void parse_page(int page_idx, int page_obj_num) {
|
||||
if (sy < 0) sy = -sy;
|
||||
if (sy > 0.01f) eff_size *= sy;
|
||||
|
||||
add_text_item(page, ts.tm[4], ts.tm[5], eff_size,
|
||||
float gx, gy;
|
||||
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
|
||||
add_text_item(page, gx, gy, eff_size,
|
||||
ops[0].str, ops[0].str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
|
||||
|
||||
// Advance text position using font metrics when available
|
||||
@@ -555,7 +660,9 @@ void parse_page(int page_idx, int page_obj_num) {
|
||||
if (sy < 0) sy = -sy;
|
||||
if (sy > 0.01f) eff_size *= sy;
|
||||
|
||||
add_text_item(page, ts.tm[4], ts.tm[5], eff_size,
|
||||
float gx, gy;
|
||||
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
|
||||
add_text_item(page, gx, gy, eff_size,
|
||||
sop->str, sop->str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
|
||||
|
||||
TrueTypeFont* adv_font = ts.embedded_font;
|
||||
@@ -592,7 +699,9 @@ void parse_page(int page_idx, int page_obj_num) {
|
||||
if (sy < 0) sy = -sy;
|
||||
if (sy > 0.01f) eff_size *= sy;
|
||||
|
||||
add_text_item(page, ts.tm[4], ts.tm[5], eff_size,
|
||||
float gx, gy;
|
||||
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
|
||||
add_text_item(page, gx, gy, eff_size,
|
||||
ops[0].str, ops[0].str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
|
||||
}
|
||||
}
|
||||
@@ -611,15 +720,250 @@ void parse_page(int page_idx, int page_obj_num) {
|
||||
if (sy < 0) sy = -sy;
|
||||
if (sy > 0.01f) eff_size *= sy;
|
||||
|
||||
add_text_item(page, ts.tm[4], ts.tm[5], eff_size,
|
||||
float gx, gy;
|
||||
ctm_transform(gs.ctm, ts.tm[4], ts.tm[5], &gx, &gy);
|
||||
add_text_item(page, gx, gy, eff_size,
|
||||
ops[2].str, ops[2].str_len, ts.font_flags, ts.tounicode, ts.embedded_font);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Graphics state operators ----
|
||||
// q - save graphics state
|
||||
else if (op[0] == 'q' && op[1] == '\0') {
|
||||
if (gs_depth < MAX_GFX_STACK)
|
||||
gs_stack[gs_depth++] = gs;
|
||||
}
|
||||
// Q - restore graphics state
|
||||
else if (op[0] == 'Q' && op[1] == '\0') {
|
||||
if (gs_depth > 0)
|
||||
gs = gs_stack[--gs_depth];
|
||||
}
|
||||
// cm - concat matrix
|
||||
else if (op[0] == 'c' && op[1] == 'm' && op[2] == '\0') {
|
||||
if (op_count >= 6) {
|
||||
float m[6] = { ops[0].num, ops[1].num, ops[2].num,
|
||||
ops[3].num, ops[4].num, ops[5].num };
|
||||
float r[6];
|
||||
// new_ctm = m * old_ctm
|
||||
r[0] = m[0]*gs.ctm[0] + m[1]*gs.ctm[2];
|
||||
r[1] = m[0]*gs.ctm[1] + m[1]*gs.ctm[3];
|
||||
r[2] = m[2]*gs.ctm[0] + m[3]*gs.ctm[2];
|
||||
r[3] = m[2]*gs.ctm[1] + m[3]*gs.ctm[3];
|
||||
r[4] = m[4]*gs.ctm[0] + m[5]*gs.ctm[2] + gs.ctm[4];
|
||||
r[5] = m[4]*gs.ctm[1] + m[5]*gs.ctm[3] + gs.ctm[5];
|
||||
for (int i = 0; i < 6; i++) gs.ctm[i] = r[i];
|
||||
}
|
||||
}
|
||||
// w - set line width
|
||||
else if (op[0] == 'w' && op[1] == '\0' && !in_text) {
|
||||
if (op_count >= 1) gs.line_width = ops[0].num;
|
||||
}
|
||||
// ---- Color operators ----
|
||||
// g - set fill gray
|
||||
else if (op[0] == 'g' && op[1] == '\0') {
|
||||
if (op_count >= 1) {
|
||||
uint8_t v = (uint8_t)(ops[0].num * 255);
|
||||
gs.fill_r = v; gs.fill_g = v; gs.fill_b = v;
|
||||
}
|
||||
}
|
||||
// G - set stroke gray
|
||||
else if (op[0] == 'G' && op[1] == '\0') {
|
||||
if (op_count >= 1) {
|
||||
uint8_t v = (uint8_t)(ops[0].num * 255);
|
||||
gs.stroke_r = v; gs.stroke_g = v; gs.stroke_b = v;
|
||||
}
|
||||
}
|
||||
// rg - set fill RGB
|
||||
else if (op[0] == 'r' && op[1] == 'g' && op[2] == '\0') {
|
||||
if (op_count >= 3) {
|
||||
gs.fill_r = (uint8_t)(ops[0].num * 255);
|
||||
gs.fill_g = (uint8_t)(ops[1].num * 255);
|
||||
gs.fill_b = (uint8_t)(ops[2].num * 255);
|
||||
}
|
||||
}
|
||||
// RG - set stroke RGB
|
||||
else if (op[0] == 'R' && op[1] == 'G' && op[2] == '\0') {
|
||||
if (op_count >= 3) {
|
||||
gs.stroke_r = (uint8_t)(ops[0].num * 255);
|
||||
gs.stroke_g = (uint8_t)(ops[1].num * 255);
|
||||
gs.stroke_b = (uint8_t)(ops[2].num * 255);
|
||||
}
|
||||
}
|
||||
// k - set fill CMYK (approximate as RGB)
|
||||
else if (op[0] == 'k' && op[1] == '\0') {
|
||||
if (op_count >= 4) {
|
||||
float c_ = ops[0].num, m_ = ops[1].num, y_ = ops[2].num, k_ = ops[3].num;
|
||||
gs.fill_r = (uint8_t)((1 - c_) * (1 - k_) * 255);
|
||||
gs.fill_g = (uint8_t)((1 - m_) * (1 - k_) * 255);
|
||||
gs.fill_b = (uint8_t)((1 - y_) * (1 - k_) * 255);
|
||||
}
|
||||
}
|
||||
// K - set stroke CMYK
|
||||
else if (op[0] == 'K' && op[1] == '\0') {
|
||||
if (op_count >= 4) {
|
||||
float c_ = ops[0].num, m_ = ops[1].num, y_ = ops[2].num, k_ = ops[3].num;
|
||||
gs.stroke_r = (uint8_t)((1 - c_) * (1 - k_) * 255);
|
||||
gs.stroke_g = (uint8_t)((1 - m_) * (1 - k_) * 255);
|
||||
gs.stroke_b = (uint8_t)((1 - y_) * (1 - k_) * 255);
|
||||
}
|
||||
}
|
||||
// ---- Path construction operators ----
|
||||
// m - moveto
|
||||
else if (op[0] == 'm' && op[1] == '\0' && !in_text) {
|
||||
if (op_count >= 2) {
|
||||
ctm_transform(gs.ctm, ops[0].num, ops[1].num, &path_cx, &path_cy);
|
||||
path_sx = path_cx;
|
||||
path_sy = path_cy;
|
||||
}
|
||||
}
|
||||
// l - lineto
|
||||
else if (op[0] == 'l' && op[1] == '\0' && !in_text) {
|
||||
if (op_count >= 2 && seg_count < MAX_PATH_SEGS) {
|
||||
float nx, ny;
|
||||
ctm_transform(gs.ctm, ops[0].num, ops[1].num, &nx, &ny);
|
||||
path_segs[seg_count].x1 = path_cx;
|
||||
path_segs[seg_count].y1 = path_cy;
|
||||
path_segs[seg_count].x2 = nx;
|
||||
path_segs[seg_count].y2 = ny;
|
||||
seg_count++;
|
||||
path_cx = nx;
|
||||
path_cy = ny;
|
||||
}
|
||||
}
|
||||
// re - rectangle (x y w h)
|
||||
else if (op[0] == 'r' && op[1] == 'e' && op[2] == '\0') {
|
||||
if (op_count >= 4 && rect_count < MAX_PATH_RECTS) {
|
||||
float rx = ops[0].num, ry = ops[1].num;
|
||||
float rw = ops[2].num, rh = ops[3].num;
|
||||
// Transform corners through CTM
|
||||
float tx, ty;
|
||||
ctm_transform(gs.ctm, rx, ry, &tx, &ty);
|
||||
float tw = rw * ctm_scale(gs.ctm);
|
||||
float th = rh * ctm_scale(gs.ctm);
|
||||
path_rects[rect_count].x = tx;
|
||||
path_rects[rect_count].y = ty;
|
||||
path_rects[rect_count].w = tw;
|
||||
path_rects[rect_count].h = th;
|
||||
rect_count++;
|
||||
// re also adds 4 line segments and sets current point
|
||||
if (seg_count + 4 <= MAX_PATH_SEGS) {
|
||||
float x0, y0, x1, y1, x2, y2, x3, y3;
|
||||
ctm_transform(gs.ctm, rx, ry, &x0, &y0);
|
||||
ctm_transform(gs.ctm, rx + rw, ry, &x1, &y1);
|
||||
ctm_transform(gs.ctm, rx + rw, ry + rh, &x2, &y2);
|
||||
ctm_transform(gs.ctm, rx, ry + rh, &x3, &y3);
|
||||
path_segs[seg_count++] = {x0, y0, x1, y1};
|
||||
path_segs[seg_count++] = {x1, y1, x2, y2};
|
||||
path_segs[seg_count++] = {x2, y2, x3, y3};
|
||||
path_segs[seg_count++] = {x3, y3, x0, y0};
|
||||
}
|
||||
path_cx = tx;
|
||||
path_cy = ty;
|
||||
path_sx = tx;
|
||||
path_sy = ty;
|
||||
}
|
||||
}
|
||||
// h - closepath
|
||||
else if (op[0] == 'h' && op[1] == '\0') {
|
||||
if (seg_count < MAX_PATH_SEGS &&
|
||||
(path_cx != path_sx || path_cy != path_sy)) {
|
||||
path_segs[seg_count].x1 = path_cx;
|
||||
path_segs[seg_count].y1 = path_cy;
|
||||
path_segs[seg_count].x2 = path_sx;
|
||||
path_segs[seg_count].y2 = path_sy;
|
||||
seg_count++;
|
||||
path_cx = path_sx;
|
||||
path_cy = path_sy;
|
||||
}
|
||||
}
|
||||
// ---- Path painting operators ----
|
||||
// S - stroke
|
||||
else if (op[0] == 'S' && op[1] == '\0' && !in_text) {
|
||||
float lw = gs.line_width * ctm_scale(gs.ctm);
|
||||
for (int si = 0; si < seg_count; si++)
|
||||
add_gfx_item(page, GFX_LINE,
|
||||
path_segs[si].x1, path_segs[si].y1,
|
||||
path_segs[si].x2, path_segs[si].y2,
|
||||
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
|
||||
seg_count = 0;
|
||||
rect_count = 0;
|
||||
}
|
||||
// s - close and stroke
|
||||
else if (op[0] == 's' && op[1] == '\0' && !in_text) {
|
||||
// Close first
|
||||
if (seg_count < MAX_PATH_SEGS &&
|
||||
(path_cx != path_sx || path_cy != path_sy)) {
|
||||
path_segs[seg_count++] = {path_cx, path_cy, path_sx, path_sy};
|
||||
}
|
||||
float lw = gs.line_width * ctm_scale(gs.ctm);
|
||||
for (int si = 0; si < seg_count; si++)
|
||||
add_gfx_item(page, GFX_LINE,
|
||||
path_segs[si].x1, path_segs[si].y1,
|
||||
path_segs[si].x2, path_segs[si].y2,
|
||||
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
|
||||
seg_count = 0;
|
||||
rect_count = 0;
|
||||
}
|
||||
// f or F - fill
|
||||
else if ((op[0] == 'f' || op[0] == 'F') &&
|
||||
(op[1] == '\0' || (op[1] == '*' && op[2] == '\0')) && !in_text) {
|
||||
for (int ri = 0; ri < rect_count; ri++)
|
||||
add_gfx_item(page, GFX_RECT_FILL,
|
||||
path_rects[ri].x, path_rects[ri].y,
|
||||
path_rects[ri].w, path_rects[ri].h,
|
||||
0, gs.fill_r, gs.fill_g, gs.fill_b);
|
||||
seg_count = 0;
|
||||
rect_count = 0;
|
||||
}
|
||||
// B or B* - fill then stroke
|
||||
else if (op[0] == 'B' && (op[1] == '\0' || (op[1] == '*' && op[2] == '\0'))
|
||||
&& !in_text) {
|
||||
for (int ri = 0; ri < rect_count; ri++)
|
||||
add_gfx_item(page, GFX_RECT_FILL,
|
||||
path_rects[ri].x, path_rects[ri].y,
|
||||
path_rects[ri].w, path_rects[ri].h,
|
||||
0, gs.fill_r, gs.fill_g, gs.fill_b);
|
||||
float lw = gs.line_width * ctm_scale(gs.ctm);
|
||||
for (int si = 0; si < seg_count; si++)
|
||||
add_gfx_item(page, GFX_LINE,
|
||||
path_segs[si].x1, path_segs[si].y1,
|
||||
path_segs[si].x2, path_segs[si].y2,
|
||||
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
|
||||
seg_count = 0;
|
||||
rect_count = 0;
|
||||
}
|
||||
// b or b* - close, fill then stroke
|
||||
else if (op[0] == 'b' && (op[1] == '\0' || (op[1] == '*' && op[2] == '\0'))
|
||||
&& !in_text) {
|
||||
if (seg_count < MAX_PATH_SEGS &&
|
||||
(path_cx != path_sx || path_cy != path_sy))
|
||||
path_segs[seg_count++] = {path_cx, path_cy, path_sx, path_sy};
|
||||
for (int ri = 0; ri < rect_count; ri++)
|
||||
add_gfx_item(page, GFX_RECT_FILL,
|
||||
path_rects[ri].x, path_rects[ri].y,
|
||||
path_rects[ri].w, path_rects[ri].h,
|
||||
0, gs.fill_r, gs.fill_g, gs.fill_b);
|
||||
float lw = gs.line_width * ctm_scale(gs.ctm);
|
||||
for (int si = 0; si < seg_count; si++)
|
||||
add_gfx_item(page, GFX_LINE,
|
||||
path_segs[si].x1, path_segs[si].y1,
|
||||
path_segs[si].x2, path_segs[si].y2,
|
||||
lw, gs.stroke_r, gs.stroke_g, gs.stroke_b);
|
||||
seg_count = 0;
|
||||
rect_count = 0;
|
||||
}
|
||||
// n - end path without painting (clipping only)
|
||||
else if (op[0] == 'n' && op[1] == '\0' && !in_text) {
|
||||
seg_count = 0;
|
||||
rect_count = 0;
|
||||
}
|
||||
|
||||
op_count = 0; // reset operand stack after operator
|
||||
}
|
||||
|
||||
montauk::mfree(ops);
|
||||
if (path_segs) montauk::mfree(path_segs);
|
||||
if (path_rects) montauk::mfree(path_rects);
|
||||
montauk::mfree(stream_data);
|
||||
}
|
||||
|
||||
|
||||
@@ -887,6 +887,9 @@ static void add_page(int obj_num) {
|
||||
g_doc.pages[g_doc.page_count].items = nullptr;
|
||||
g_doc.pages[g_doc.page_count].item_count = 0;
|
||||
g_doc.pages[g_doc.page_count].item_cap = 0;
|
||||
g_doc.pages[g_doc.page_count].gfx_items = nullptr;
|
||||
g_doc.pages[g_doc.page_count].gfx_count = 0;
|
||||
g_doc.pages[g_doc.page_count].gfx_cap = 0;
|
||||
g_doc.pages[g_doc.page_count].width = 612;
|
||||
g_doc.pages[g_doc.page_count].height = 792;
|
||||
g_doc.page_count++;
|
||||
@@ -1457,6 +1460,7 @@ void free_pdf() {
|
||||
if (g_doc.pages) {
|
||||
for (int i = 0; i < g_doc.page_count; i++) {
|
||||
if (g_doc.pages[i].items) montauk::mfree(g_doc.pages[i].items);
|
||||
if (g_doc.pages[i].gfx_items) montauk::mfree(g_doc.pages[i].gfx_items);
|
||||
}
|
||||
montauk::mfree(g_doc.pages);
|
||||
g_doc.pages = nullptr;
|
||||
|
||||
@@ -66,10 +66,22 @@ struct TextItem {
|
||||
TrueTypeFont* font; // embedded font, or nullptr for system font
|
||||
};
|
||||
|
||||
enum GfxType { GFX_LINE, GFX_RECT_FILL, GFX_RECT_STROKE };
|
||||
|
||||
struct GraphicsItem {
|
||||
GfxType type;
|
||||
float x1, y1, x2, y2; // LINE: endpoints; RECT: x,y,w,h (PDF coords)
|
||||
float line_width;
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
struct PdfPage {
|
||||
TextItem* items;
|
||||
int item_count;
|
||||
int item_cap;
|
||||
GraphicsItem* gfx_items;
|
||||
int gfx_count;
|
||||
int gfx_cap;
|
||||
float width, height; // page dimensions in points (from MediaBox)
|
||||
};
|
||||
|
||||
@@ -141,6 +153,7 @@ void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c);
|
||||
void px_vline(uint32_t* px, int bw, int bh, int x, int y, int h, Color c);
|
||||
void px_rect(uint32_t* px, int bw, int bh, int x, int y, int w, int h, Color c);
|
||||
void px_fill_rounded(uint32_t* px, int bw, int bh, int x, int y, int w, int h, int r, Color c);
|
||||
void px_line(uint32_t* px, int bw, int bh, int x0, int y0, int x1, int y1, int thick, Color c);
|
||||
int str_len(const char* s);
|
||||
void str_cpy(char* dst, const char* src, int max);
|
||||
|
||||
|
||||
@@ -48,6 +48,38 @@ void render(uint32_t* pixels) {
|
||||
pixels[row * g_win_w + col] = white;
|
||||
}
|
||||
|
||||
// Draw graphics items (lines, filled rectangles)
|
||||
for (int i = 0; i < page->gfx_count; i++) {
|
||||
GraphicsItem* gi = &page->gfx_items[i];
|
||||
Color gfx_color = Color::from_rgb(gi->r, gi->g, gi->b);
|
||||
|
||||
if (gi->type == GFX_RECT_FILL) {
|
||||
// gi->x1,y1 = rect origin (PDF coords), gi->x2,y2 = width,height
|
||||
int rx = page_x + (int)(gi->x1 * g_zoom);
|
||||
int ry = page_y + (int)((page->height - gi->y1 - gi->y2) * g_zoom);
|
||||
int rw = (int)(gi->x2 * g_zoom);
|
||||
int rh = (int)(gi->y2 * g_zoom);
|
||||
if (rw < 0) { rx += rw; rw = -rw; }
|
||||
if (rh < 0) { ry += rh; rh = -rh; }
|
||||
// Clip to content area
|
||||
if (ry + rh > clip_y0 && ry < clip_y1)
|
||||
px_fill(pixels, g_win_w, g_win_h, rx, ry, rw, rh, gfx_color);
|
||||
} else {
|
||||
// GFX_LINE or GFX_RECT_STROKE
|
||||
int lx0 = page_x + (int)(gi->x1 * g_zoom);
|
||||
int ly0 = page_y + (int)((page->height - gi->y1) * g_zoom);
|
||||
int lx1 = page_x + (int)(gi->x2 * g_zoom);
|
||||
int ly1 = page_y + (int)((page->height - gi->y2) * g_zoom);
|
||||
int lw = (int)(gi->line_width * g_zoom + 0.5f);
|
||||
if (lw < 1) lw = 1;
|
||||
// Basic clip check
|
||||
int min_y = ly0 < ly1 ? ly0 : ly1;
|
||||
int max_y = ly0 > ly1 ? ly0 : ly1;
|
||||
if (max_y + lw >= clip_y0 && min_y - lw < clip_y1)
|
||||
px_line(pixels, g_win_w, g_win_h, lx0, ly0, lx1, ly1, lw, gfx_color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw text items
|
||||
for (int i = 0; i < page->item_count; i++) {
|
||||
TextItem* item = &page->items[i];
|
||||
@@ -75,11 +107,18 @@ void render(uint32_t* pixels) {
|
||||
if (px_size < 4) px_size = 4;
|
||||
if (px_size > 120) px_size = 120;
|
||||
|
||||
// draw_to_buffer treats y as top of text box, but PDF
|
||||
// specifies the baseline. Subtract ascent so the rendered
|
||||
// baseline lands at the correct position.
|
||||
GlyphCache* gc = font->get_cache(px_size);
|
||||
int baseline_adj = gc ? gc->ascent : (int)(px_size * 0.8f);
|
||||
int ty = sy - baseline_adj;
|
||||
|
||||
if (item->font) {
|
||||
// Embedded font: pass raw character codes directly
|
||||
// (subset fonts use codes 0-N that map through the font's cmap)
|
||||
font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
sx, sy, item->text, TEXT_COLOR, px_size);
|
||||
sx, ty, item->text, TEXT_COLOR, px_size);
|
||||
} else {
|
||||
// System font: filter out non-printable characters
|
||||
char render_text[MAX_TEXT_LEN];
|
||||
@@ -96,7 +135,7 @@ void render(uint32_t* pixels) {
|
||||
|
||||
if (ri > 0) {
|
||||
font->draw_to_buffer(pixels, g_win_w, g_win_h,
|
||||
sx, sy, render_text, TEXT_COLOR, px_size);
|
||||
sx, ty, render_text, TEXT_COLOR, px_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user