std1.0.0-beta-7 SafeC docs

Source gui/gui_canvas_text.sc

View in the repository

// SafeC Standard Library — GUI: text on the vector canvas (see
// gui_canvas.h / gui_text.h).
//
// Under a transform that is a translation plus a uniform positive scale
// (the usual case — UI text), each glyph is rasterized once per (size,
// quarter-pixel horizontal offset) into an anti-aliased coverage bitmap
// cached on its typeface, and drawn by compositing that bitmap with the
// fill paint at a whole-pixel baseline. Under any other transform
// (rotation, skew, mirroring) glyph outlines are filled as paths.
#pragma once
#include <std/gui/gui.h>
#include <std/gui/gui_canvas.h>
#include <std/gui/gui_text.h>
#include <std/mem.h>
#include <std/math.h>
#include <std/collections/vec.h>

namespace std {

void __gui_canvas_flatten(const &GuiPath p, struct GuiMatrix m, float tol, &Vec<float> pts, &Vec<int> cont);
void __gui_canvas_edges(&GuiCanvas c, &Vec<float> pts, &Vec<int> cont, struct GuiMatrix m, int transform);
void __gui_canvas_fill_edges(&GuiCanvas c, int rule, const &GuiPaint paint);
void __gui_canvas_coverage_edges(&GuiCanvas c, int rule, &unsigned char out, int w, int h, int offX, int offY);
void __gui_canvas_blit_coverage(&GuiCanvas c, const &unsigned char covBytes, int w, int h, int dx, int dy,
                                const &GuiPaint paint);

void GuiCanvas::set_font(const &GuiTypeface face, float sizePx) {
    self.st.font = face;
    self.st.fontSize = sizePx > 0.0f ? sizePx : 1.0f;
}

void GuiCanvas::set_font_size(float sizePx) { self.st.fontSize = sizePx > 0.0f ? sizePx : 1.0f; }
void GuiCanvas::set_text_align(int align) { self.st.textAlign = align; }
void GuiCanvas::set_text_baseline(int baseline) { self.st.textBaseline = baseline; }

static &GuiTypeface __gui_canvas_face(const &GuiCanvas c) {
    match (c.st.font) {
        case some(f): {
            unsafe { return (struct GuiTypeface*)f; }
        }
        case null: { }
    }
    return GuiTypeface::fallback();
}

// 1 if glyphs can come from the bitmap cache under the current transform
// (translation + uniform positive scale); *scale gets the scale.
static int __gui_canvas_text_cacheable(const &GuiCanvas c, &float scale) {
    struct GuiMatrix m = c.st.m;
    if (m.b != 0.0f || m.c != 0.0f || m.a <= 0.0f) { return 0; }
    float d = m.a - m.d;
    if (d < -1e-4f || d > 1e-4f) { return 0; }
    *scale = m.a;
    return 1;
}

// Draws one glyph with its pen at user-space (ux, uy).
static void __gui_canvas_glyph(&GuiCanvas c, &GuiTypeface face, int glyph, float size, float ux, float uy,
                               const &GuiPaint paint) {
    float scale = 1.0f;
    if (!__gui_canvas_text_cacheable(c, &scale)) {
        c.scratchPath.clear();
        if (!face.glyph_path(glyph, &c.scratchPath, size, ux, uy)) { return; }
        __gui_canvas_flatten(&c.scratchPath, c.st.m, 0.05f, &c.fpts, &c.fcont);
        __gui_canvas_edges(c, &c.fpts, &c.fcont, GuiMatrix::identity(), 0);
        __gui_canvas_fill_edges(c, GUI_FILL_NONZERO, paint);
        return;
    }
    float dsize = size * scale;
    float dx = c.st.m.apply_x(ux, uy);
    float dy = c.st.m.apply_y(ux, uy);
    float fx = floor_f(dx);
    int ix = (int)fx;
    int sub = (int)((dx - fx) * 4.0f);
    if (sub > 3) { sub = 3; }
    if (sub < 0) { sub = 0; }
    int iy = (int)floor_f(dy + 0.5f);
    int sizeKey = (int)(dsize * 64.0f + 0.5f);
    ?&GuiGlyphBitmap hit = face.cache_find(glyph, sizeKey, sub);
    &GuiGlyphBitmap bm;
    match (hit) {
        case some(b): { bm = b; }
        case null: {
            struct GuiGlyphBitmap nb;
            nb.glyph = glyph;
            nb.sizeKey = sizeKey;
            nb.subpixel = sub;
            nb.width = 0; nb.height = 0; nb.left = 0; nb.top = 0;
            nb.coverage = null;
            c.scratchPath.clear();
            if (face.glyph_path(glyph, &c.scratchPath, (float)sizeKey / 64.0f, (float)sub * 0.25f, 0.0f)) {
                __gui_canvas_flatten(&c.scratchPath, GuiMatrix::identity(), 0.05f, &c.fpts, &c.fcont);
                __gui_canvas_edges(c, &c.fpts, &c.fcont, GuiMatrix::identity(), 0);
                if (c.edges.len > 0UL) {
                    float x0 = 0.0f; float y0 = 0.0f; float x1 = 0.0f; float y1 = 0.0f;
                    unsafe {
                        const struct GuiEdge* e = (const struct GuiEdge*)c.edges.data;
                        x0 = e[0].x0; x1 = e[0].x0; y0 = e[0].y0; y1 = e[0].y1;
                        unsigned long k = 0UL;
                        while (k < c.edges.len) {
                            if (e[k].x0 < x0) { x0 = e[k].x0; }
                            if (e[k].x1 < x0) { x0 = e[k].x1; }
                            if (e[k].x0 > x1) { x1 = e[k].x0; }
                            if (e[k].x1 > x1) { x1 = e[k].x1; }
                            if (e[k].y0 < y0) { y0 = e[k].y0; }
                            if (e[k].y1 > y1) { y1 = e[k].y1; }
                            k = k + 1UL;
                        }
                    }
                    int bx0 = (int)floor_f(x0);
                    int by0 = (int)floor_f(y0);
                    int bx1 = (int)ceil_f(x1) + 1;
                    int by1 = (int)ceil_f(y1);
                    int w = bx1 - bx0;
                    int h = by1 - by0;
                    if (w > 0 && h > 0 && w < 4096 && h < 4096) {
                        unsafe {
                            unsigned char* cov = (unsigned char*)alloc_zeroed_raw_((unsigned long)w * (unsigned long)h);
                            __gui_canvas_coverage_edges(c, GUI_FILL_NONZERO, (&unsigned char)cov, w, h, bx0, by0);
                            nb.coverage = (&heap unsigned char)cov;
                        }
                        nb.width = w;
                        nb.height = h;
                        nb.left = bx0;
                        nb.top = by0;
                    }
                }
            }
            bm = face.cache_insert(nb);
        }
    }
    if (bm.width <= 0 || bm.height <= 0) { return; }
    unsafe {
        __gui_canvas_blit_coverage(c, (const unsigned char*)bm.coverage, bm.width, bm.height,
                                   ix + bm.left, iy + bm.top, paint);
    }
}

float GuiCanvas::measure_text(const &char text) const {
    &GuiTypeface face = __gui_canvas_face(self);
    return face.measure(text, self.st.fontSize);
}

float GuiCanvas::fill_text_n_n_(const &char text, unsigned long len, float x, float y) {
    &GuiTypeface face = __gui_canvas_face(self);
    &GuiTypeface fb = GuiTypeface::fallback();
    float size = self.st.fontSize;
    float width = face.measure_n_n_(text, len, size);
    if (self.st.textAlign == GUI_TEXT_CENTER) { x = x - width * 0.5f; }
    else if (self.st.textAlign == GUI_TEXT_RIGHT) { x = x - width; }
    float asc = face.ascent(size);
    float desc = face.descent(size);
    if (self.st.textBaseline == GUI_BASELINE_TOP) { y = y + asc; }
    else if (self.st.textBaseline == GUI_BASELINE_MIDDLE) { y = y + (asc - desc) * 0.5f; }
    else if (self.st.textBaseline == GUI_BASELINE_BOTTOM) { y = y - desc; }
    struct GuiPaint paint = self.st.fill;
    float pen = 0.0f;
    int prev = 0;
    int selfFb;
    unsafe { selfFb = ((const unsigned char*)face == (const unsigned char*)fb) ? 1 : 0; }
    unsigned long i = 0UL;
    while (i < len) {
        unsigned int cp = gui_utf8_next_n_(text, len, &i);
        if (cp == 0U) { break; }
        int g = face.glyph_index(cp);
        if (g == 0 && !selfFb && fb.glyph_index(cp) != 0) {
            int g2 = fb.glyph_index(cp);
            __gui_canvas_glyph(self, fb, g2, size, x + pen, y, &paint);
            pen = pen + fb.advance(g2, size);
            prev = 0;
            continue;
        }
        if (prev != 0) { pen = pen + face.kerning(prev, g, size); }
        if (cp != 32U && cp != 9U) { __gui_canvas_glyph(self, face, g, size, x + pen, y, &paint); }
        pen = pen + face.advance(g, size);
        prev = g;
    }
    return width;
}

float GuiCanvas::fill_text_n([]const char text_s, float x, float y) {
    unsigned long len = (unsigned long)text_s.len;
    char text_d_[1];
    const &char text = &text_d_[0];
    if (text_s.len > 0UL) { unsafe { text = (const &char)text_s.ptr; } }
    return self.fill_text_n_n_(text, len, x, y);
}

float GuiCanvas::fill_text(const &char text, float x, float y) {
    unsigned long n = 0UL;
    unsafe { while (text[n] != '\0') { n = n + 1UL; } }
    return self.fill_text_n_n_(text, n, x, y);
}

void GuiCanvas::fill_text_layout(const &GuiTextLayout layout, float x, float y) {
    struct GuiPaint paint = self.st.fill;
    unsigned long i = 0UL;
    while (i < layout.glyphs.len) {
        &GuiTextGlyph g = layout.glyphs.at(i);
        i = i + 1UL;
        if (g.space || g.glyph <= 0) { continue; }
        match (g.face) {
            case some(f): {
                unsafe { __gui_canvas_glyph(self, (struct GuiTypeface*)f, g.glyph, layout.size, x + g.x, y + g.y, &paint); }
            }
            case null: { }
        }
    }
}

} // namespace std