λ

pastry

truetype font renderer

settings

demo.c

5102 bytes raw

#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>

#include <stdint.h>
#include "pastry.h"

#include <hb.h>
#include <hb-ot.h>

static int add_glyph(Display *dpy, GlyphSet glyphset, PASTRY *pastry, PASTRY_Glyph glyph)
{
#define ABORT(glyph, m) do { fprintf(stderr, "glyph %lu %s\n", (unsigned long)(glyph), m); return -1; } while (0)

    PASTRY_GMetrics mtx;
    if (pastry_gmetrics(pastry, glyph, &mtx) < 0)
        ABORT(glyph, "bad glyph metrics");

    PASTRY_Image img = {
        .width  = (mtx.minWidth + 3) & ~3,
        .height = mtx.minHeight,
    };
    char pixels[img.width * img.height];
    img.pixels = pixels;
    if (pastry_render(pastry, glyph, img) < 0)
        ABORT(glyph, "not rendered");

    XGlyphInfo info = {
        .x      = (short) -mtx.leftSideBearing,
        .y      = (short) -mtx.yOffset,
        .width  = (unsigned short) img.width,
        .height = (unsigned short) img.height,
        .xOff   = (short) mtx.advanceWidth,
        .yOff   = 0
    };
    Glyph g = (Glyph) glyph;
    XRenderAddGlyphs(dpy, glyphset, &g, &info, 1,
                     img.pixels, (int) (img.width * img.height));

    return 0;
}

int main(void)
{
#define END(m) do { fprintf(stderr, "%s\n", m); return 1; } while (0)

    Display *dpy = XOpenDisplay(NULL);
    if (dpy == NULL)
        END("Can't open X display");
    unsigned int s = 2, screen = (unsigned int)DefaultScreen(dpy);

    Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 600*s, 440*s, 0,
                               DefaultDepth(dpy, screen), InputOutput, CopyFromParent, 0, NULL);
    XSelectInput(dpy, win, ExposureMask);
    Atom wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(dpy, win, &wmDeleteWindow, 1);
    XMapRaised(dpy, win);

    XRenderPictFormat *fmt = XRenderFindVisualFormat(dpy, DefaultVisual(dpy, screen));
    Picture pic = XRenderCreatePicture(dpy, win, fmt, 0, NULL);
    XRenderColor fg = { 0, 0, 0, 0xFFFF }, bg = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF };

    Pixmap fgpix = XCreatePixmap(dpy, win, 1, 1, 24);
    XRenderPictureAttributes attr = { .repeat = True };
    fmt = XRenderFindStandardFormat(dpy, PictStandardRGB24);
    Picture fgpic = XRenderCreatePicture(dpy, fgpix, fmt, CPRepeat, &attr);
    XRenderFillRectangle(dpy, PictOpSrc, fgpic, &fg, 0, 0, 1, 1);

    fmt = XRenderFindStandardFormat(dpy, PictStandardA8);
    GlyphSet glyphset = XRenderCreateGlyphSet(dpy, fmt);

    PASTRY pastry = {
        .xScale = 16*s,
        .yScale = 16*s,
        .flags  = PASTRY_DOWNWARD_Y,
    };
    pastry.font = pastry_loadfile("firago-extended.ttf");
    if (pastry.font == NULL)
        END("failed to load font");

    // harfbuzz does the shaping; pastry just draws the glyph ids it hands us
    hb_blob_t *blob = hb_blob_create_from_file_or_fail("firago-extended.ttf");
    if (blob == NULL)
        END("harfbuzz failed to load font");
    hb_face_t *face = hb_face_create(blob, 0);
    hb_font_t *font = hb_font_create(face);
    hb_ot_font_set_funcs(font);
    hb_buffer_t *buffer = hb_buffer_create();

    XEvent event;
    while (!XNextEvent(dpy, &event)) {
        if (event.type == Expose) {
            XRenderFillRectangle(dpy, PictOpOver, pic, &bg, 0, 0, (unsigned int)event.xexpose.width, (unsigned int)event.xexpose.height);

            FILE *file = fopen("text.utf8", "r");
            if (file == NULL)
                END("cannot open input text");

            PASTRY_LMetrics lmtx;
            pastry_lmetrics(&pastry, &lmtx);
            int y = 20 + (int)lmtx.ascender + (int)lmtx.lineGap;

            char text[256];
            while (fgets(text, sizeof(text), file)) {
                size_t len = strlen(text);
                if (len > 0 && text[len - 1] == '\n')
                    text[--len] = 0;

                hb_buffer_clear_contents(buffer);
                hb_buffer_add_utf8(buffer, text, (int)len, 0, (int)len);
                hb_buffer_guess_segment_properties(buffer);
                hb_shape(font, buffer, NULL, 0);

                unsigned int count = 0;
                hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &count);
                for (unsigned int i = 0; i < count; i++)
                    add_glyph(dpy, glyphset, &pastry, (PASTRY_Glyph) info[i].codepoint);

                unsigned int glyphs[256];
                for (unsigned int i = 0; i < count && i < 256; i++)
                    glyphs[i] = info[i].codepoint;
                XRenderCompositeString32(dpy, PictOpOver, fgpic, pic, NULL, glyphset, 0, 0, 20, y, glyphs, (int)count);

                y += 2 * ((int)lmtx.ascender + (int)lmtx.descender + (int)lmtx.lineGap);
            }

            fclose(file);
        } else if (event.type == ClientMessage) {
            if ((Atom) event.xclient.data.l[0] == wmDeleteWindow)
                break;
        }
    }

    hb_buffer_destroy(buffer);
    hb_font_destroy(font);
    hb_face_destroy(face);
    hb_blob_destroy(blob);

    pastry_freefont(pastry.font);
    XCloseDisplay(dpy);
    return 0;
}