Layout-Flex

 view release on metacpan or  search on metacpan

Flex.xs  view on Meta::CPAN

    PUTBACK;
    FREETMPS; LEAVE;
}

/* ── Font::Metrics measure helpers (direct C calls, no Perl dispatch) ── */

static void
lf_measure_fm(const fm_font_t *fm, const char *text, int is_utf8, float size, float line_h,
              float *w, float *h)
{
    *w = is_utf8 ? fm_string_width_utf8(fm, text, size) : fm_string_width(fm, text, size);
    *h = line_h;
}

static void
lf_measure_fm_wrapped(const fm_font_t *fm, const char *text, int is_utf8, float size,
                      float line_h, float avail_w, float *w, float *h)
{
    size_t tlen = strlen(text);
    char *buf = (char *)malloc(tlen + 2);
    const char *p, *ws; size_t wlen, llen; int lines; float cw;
    buf[0] = '\0'; llen = 0; lines = 0;
    p = text;
    while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
    while (*p) {
        ws = p;
        while (*p && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++;
        wlen = (size_t)(p - ws);
        if (llen > 0) {
            buf[llen] = ' ';
            memcpy(buf + llen + 1, ws, wlen);
            buf[llen + 1 + wlen] = '\0';
            cw = is_utf8 ? fm_string_width_utf8(fm, buf, size)
                         : fm_string_width(fm, buf, size);
            if (cw > avail_w) {
                lines++;
                memcpy(buf, ws, wlen);
                buf[wlen] = '\0'; llen = wlen;
            } else { llen += 1 + wlen; }
        } else {
            memcpy(buf, ws, wlen);
            buf[wlen] = '\0'; llen = wlen; lines = 1;
        }
        while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
    }
    free(buf);
    *w = avail_w;
    *h = lines > 0 ? (float)lines * line_h : 0.0f;
}

MODULE = Layout::Flex  PACKAGE = Layout::Flex

PROTOTYPES: DISABLE

BOOT:
    fm_std14_init();

void
compute(class, ...)
    SV * class
    PPCODE:
    {
        lf_ctx_t  ctx;
        lf_item_t *lf_items = NULL;
        lf_rect_t *rects = NULL;
        AV        *items_av = NULL;
        SV        *measure_sv = NULL;
        SV        *fm_measure_sv = NULL;
        fm_font_t *fm_ptr = NULL;
        float      fm_size = 12.0f, fm_line_h = 14.4f;
        int        i, n;

        PERL_UNUSED_VAR(class);

        /* ── defaults ── */
        ctx.main_size     = 0.0f;
        ctx.cross_size    = 0.0f;
        ctx.direction     = LF_DIR_ROW;
        ctx.justify       = LF_JUST_START;
        ctx.align_items   = LF_ALIGN_STRETCH;
        ctx.wrap          = LF_WRAP_NOWRAP;
        ctx.align_content = LF_ACONT_STRETCH;
        ctx.main_gap      = 0.0f;
        ctx.cross_gap     = 0.0f;
        ctx.items         = NULL;
        ctx.n_items       = 0;

        /* ── parse named args ── */
        for (i = 1; i + 1 < items; i += 2) {
            const char *key = SvPV_nolen(ST(i));
            SV         *val = ST(i + 1);

            if (strEQ(key, "main_size")) {
                ctx.main_size = (float)SvNV(val);
            } else if (strEQ(key, "cross_size")) {
                ctx.cross_size = (float)SvNV(val);
            } else if (strEQ(key, "direction")) {
                const char *d = SvPV_nolen(val);
                ctx.direction = strEQ(d, "column") ? LF_DIR_COLUMN : LF_DIR_ROW;
            } else if (strEQ(key, "justify")) {
                const char *j = SvPV_nolen(val);
                if      (strEQ(j, "end"))           ctx.justify = LF_JUST_END;
                else if (strEQ(j, "center"))        ctx.justify = LF_JUST_CENTER;
                else if (strEQ(j, "space-between")) ctx.justify = LF_JUST_SPACE_BETWEEN;
                else if (strEQ(j, "space-around"))  ctx.justify = LF_JUST_SPACE_AROUND;
                else if (strEQ(j, "space-evenly"))  ctx.justify = LF_JUST_SPACE_EVENLY;
                else                                ctx.justify = LF_JUST_START;
            } else if (strEQ(key, "align")) {
                int a = lf_parse_align(SvPV_nolen(val));
                ctx.align_items = (a >= 0) ? a : LF_ALIGN_STRETCH;
            } else if (strEQ(key, "wrap")) {
                const char *w = SvPV_nolen(val);
                if      (strEQ(w, "wrap"))         ctx.wrap = LF_WRAP_WRAP;
                else if (strEQ(w, "wrap-reverse")) ctx.wrap = LF_WRAP_REVERSE;
                else                               ctx.wrap = LF_WRAP_NOWRAP;
            } else if (strEQ(key, "align_content")) {
                const char *ac = SvPV_nolen(val);
                if      (strEQ(ac, "start"))          ctx.align_content = LF_ACONT_START;
                else if (strEQ(ac, "end"))            ctx.align_content = LF_ACONT_END;
                else if (strEQ(ac, "center"))         ctx.align_content = LF_ACONT_CENTER;
                else if (strEQ(ac, "space-between"))  ctx.align_content = LF_ACONT_SPACE_BETWEEN;

Flex.xs  view on Meta::CPAN

        if (measure_sv || fm_measure_sv) {
            int needs_recompute = 0;
            for (i = 0; i < n; i++) {
                SV **elem2 = av_fetch(items_av, (SSize_t)i, 0);
                HV  *hv2;
                SV **wt_sv, **text_sv2;
                float avail, tw, th, new_cross;

                if (!elem2 || !SvOK(*elem2) || !SvROK(*elem2)
                    || SvTYPE(SvRV(*elem2)) != SVt_PVHV) continue;
                hv2 = (HV *)SvRV(*elem2);

                wt_sv = hv_fetch(hv2, "wrap_text", 9, 0);
                if (!wt_sv || !SvOK(*wt_sv) || !SvTRUE(*wt_sv)) continue;

                text_sv2 = hv_fetch(hv2, "text", 4, 0);
                if (!text_sv2 || !SvOK(*text_sv2)) continue;

                avail = (ctx.direction == LF_DIR_ROW) ? rects[i].w : rects[i].h;
                tw = 0.0f; th = 0.0f;

                if (fm_measure_sv) {
                    lf_measure_fm_wrapped(fm_ptr, SvPV_nolen(*text_sv2),
                                          SvUTF8(*text_sv2),
                                          fm_size, fm_line_h, avail, &tw, &th);
                } else if (SvROK(measure_sv)) {
                    lf_measure_cb_wrapped(aTHX_ measure_sv, hv2, avail, &tw, &th);
                } else {
                    float font_size = 12.0f;
                    lf_hv_float(aTHX_ hv2, "font_size", 9, &font_size);
                    lf_measure_simple_wrapped(SvPV_nolen(*text_sv2),
                                              font_size, avail, &tw, &th);
                }

                new_cross = (ctx.direction == LF_DIR_ROW) ? th : tw;
                if (new_cross != lf_items[i].cross) {
                    lf_items[i].cross = new_cross;
                    needs_recompute   = 1;
                }
            }
            if (needs_recompute) lf_compute(&ctx, rects);
        }

        EXTEND(SP, n);
        for (i = 0; i < n; i++) {
            AV *rect = newAV();
            av_push(rect, newSVnv((NV)rects[i].x));
            av_push(rect, newSVnv((NV)rects[i].y));
            av_push(rect, newSVnv((NV)rects[i].w));
            av_push(rect, newSVnv((NV)rects[i].h));
            PUSHs(sv_2mortal(newRV_noinc((SV *)rect)));
        }

        free(lf_items);
        free(rects);
    }

void
font_metrics_measure(class, ...)
    SV *class
    PPCODE:
    {
        SV  *fm_sv  = NULL;
        NV   size   = 12.0;
        NV   line_h = 0.0;
        HV  *hv;
        SV  *obj;
        int  i;

        PERL_UNUSED_VAR(class);

        for (i = 1; i + 1 < items; i += 2) {
            const char *k = SvPV_nolen(ST(i));
            SV         *v = ST(i + 1);
            if      (strEQ(k, "fm"))          { fm_sv  = v; }
            else if (strEQ(k, "size"))        { size   = SvNV(v); }
            else if (strEQ(k, "line_height")) { line_h = SvNV(v); }
        }

        if (!fm_sv || !SvOK(fm_sv) || !SvROK(fm_sv))
            croak("font_metrics_measure: fm must be a Font::Metrics object");
        if (line_h <= 0.0) line_h = size * 1.2;

        hv = newHV();
        hv_store(hv, "fm",     2, SvREFCNT_inc(fm_sv),                              0);
        hv_store(hv, "fmp",    3, newSVuv(PTR2UV(INT2PTR(fm_font_t *,
                                      SvIV(SvRV(fm_sv))))),                          0);
        hv_store(hv, "size",   4, newSVnv(size),                                     0);
        hv_store(hv, "line_h", 6, newSVnv(line_h),                                  0);

        obj = sv_bless(newRV_noinc((SV *)hv),
                       gv_stashpv("Layout::Flex::FMeasure", GV_ADD));
        EXTEND(SP, 1);
        PUSHs(sv_2mortal(obj));
        XSRETURN(1);
    }



( run in 1.281 second using v1.01-cache-2.11-cpan-4e7a2411597 )