pastry
truetype font renderer
demo.c
8030 bytes raw
1#include <stdio.h>
2#include <string.h>
3#include <X11/Xlib.h>
4#include <X11/extensions/Xrender.h>
5
6#include <stdint.h>
7#include "pastry.h"
8
9#include <hb.h>
10#include <hb-ot.h>
11
12#include <fribidi.h>
13
14#define MAX_CHARS 256
15
16static int add_glyph(Display *dpy, GlyphSet glyphset, PASTRY *pastry, PASTRY_Glyph glyph, int *advance)
17{
18#define ABORT(glyph, m) do { fprintf(stderr, "glyph %lu %s\n", (unsigned long)(glyph), m); return -1; } while (0)
19
20 PASTRY_GMetrics mtx;
21 if (pastry_gmetrics(pastry, glyph, &mtx) < 0)
22 ABORT(glyph, "bad glyph metrics");
23
24 PASTRY_Image img = {
25 .width = (mtx.minWidth + 3) & ~3,
26 .height = mtx.minHeight,
27 };
28 char pixels[img.width * img.height];
29 img.pixels = pixels;
30 if (pastry_render(pastry, glyph, img) < 0)
31 ABORT(glyph, "not rendered");
32
33 XGlyphInfo info = {
34 .x = (short) -mtx.leftSideBearing,
35 .y = (short) -mtx.yOffset,
36 .width = (unsigned short) img.width,
37 .height = (unsigned short) img.height,
38 .xOff = (short) mtx.advanceWidth,
39 .yOff = 0
40 };
41 Glyph g = (Glyph) glyph;
42 XRenderAddGlyphs(dpy, glyphset, &g, &info, 1,
43 img.pixels, (int) (img.width * img.height));
44
45 *advance += info.xOff;
46 return 0;
47}
48
49typedef struct {
50 FriBidiLevel level; // embedding level; odd means right-to-left
51 int advance; // total advance, used to place the next run
52 unsigned int count; // number of shaped glyphs
53 uint32_t glyphs[MAX_CHARS];
54} LineRun;
55
56static int draw_line(Display *dpy, GlyphSet glyphset, Picture fgpic, Picture pic,
57 PASTRY *pastry, hb_font_t *font, hb_buffer_t *buffer,
58 const char *text, int y)
59{
60 FriBidiChar u[MAX_CHARS];
61 FriBidiLevel levels[MAX_CHARS];
62
63 FriBidiStrIndex len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, text,
64 (FriBidiStrIndex) strlen(text), u);
65 if (len <= 0)
66 return 0;
67 if (len > MAX_CHARS)
68 len = MAX_CHARS;
69
70 for (FriBidiStrIndex i = 0; i < len; i++)
71 levels[i] = 0;
72
73 FriBidiParType baseDir = FRIBIDI_PAR_ON; // let fribidi pick the direction
74 fribidi_log2vis(u, len, &baseDir, NULL, NULL, NULL, levels);
75
76 // split the line into runs of equal embedding level, in logical order
77 LineRun runs[MAX_CHARS];
78 unsigned int nruns = 0;
79
80 for (FriBidiStrIndex i = 0; i < len; ) {
81 FriBidiStrIndex end = i + 1;
82 while (end < len && levels[end] == levels[i])
83 end++;
84
85 LineRun *run = &runs[nruns++];
86 run->level = levels[i];
87 run->advance = 0;
88
89 hb_buffer_clear_contents(buffer);
90 hb_buffer_add_utf32(buffer, (const uint32_t *) &u[i], (int) (end - i), 0, (int) (end - i));
91 hb_buffer_guess_segment_properties(buffer);
92 hb_buffer_set_direction(buffer, (levels[i] & 1) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
93 hb_shape(font, buffer, NULL, 0);
94
95 unsigned int count = 0;
96 hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &count);
97 if (count > MAX_CHARS)
98 count = MAX_CHARS;
99 run->count = count;
100 for (unsigned int k = 0; k < count; k++) {
101 run->glyphs[k] = info[k].codepoint;
102 if (add_glyph(dpy, glyphset, pastry, (PASTRY_Glyph) info[k].codepoint, &run->advance) < 0)
103 return -1;
104 }
105
106 i = end;
107 }
108
109 // reorder the runs into visual order (rule L2 of the bidi algorithm)
110 FriBidiLevel maxLevel = 0, lowestOdd = 0;
111 for (unsigned int i = 0; i < nruns; i++) {
112 if (runs[i].level > maxLevel)
113 maxLevel = runs[i].level;
114 if ((runs[i].level & 1) && (lowestOdd == 0 || runs[i].level < lowestOdd))
115 lowestOdd = runs[i].level;
116 }
117 for (FriBidiLevel level = maxLevel; lowestOdd && level >= lowestOdd; level--) {
118 for (unsigned int i = 0; i < nruns; ) {
119 if (runs[i].level < level) {
120 i++;
121 continue;
122 }
123 unsigned int j = i;
124 while (j < nruns && runs[j].level >= level)
125 j++;
126 for (unsigned int a = i, b = j - 1; a < b; a++, b--) {
127 LineRun tmp = runs[a];
128 runs[a] = runs[b];
129 runs[b] = tmp;
130 }
131 i = j;
132 }
133 }
134
135 int x = 20;
136 for (unsigned int i = 0; i < nruns; i++) {
137 if (runs[i].count == 0)
138 continue;
139 XRenderCompositeString32(dpy, PictOpOver, fgpic, pic, NULL, glyphset,
140 0, 0, x, y, runs[i].glyphs, (int) runs[i].count);
141 x += runs[i].advance;
142 }
143 return 0;
144}
145
146int main(void)
147{
148#define END(m) do { fprintf(stderr, "%s\n", m); return 1; } while (0)
149
150 Display *dpy = XOpenDisplay(NULL);
151 if (dpy == NULL)
152 END("Can't open X display");
153 unsigned int s = 2, screen = (unsigned int)DefaultScreen(dpy);
154
155 Window win = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, 600*s, 440*s, 0,
156 DefaultDepth(dpy, screen), InputOutput, CopyFromParent, 0, NULL);
157 XSelectInput(dpy, win, ExposureMask);
158 Atom wmDeleteWindow = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
159 XSetWMProtocols(dpy, win, &wmDeleteWindow, 1);
160 XMapRaised(dpy, win);
161
162 XRenderPictFormat *fmt = XRenderFindVisualFormat(dpy, DefaultVisual(dpy, screen));
163 Picture pic = XRenderCreatePicture(dpy, win, fmt, 0, NULL);
164 XRenderColor fg = { 0, 0, 0, 0xFFFF }, bg = { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF };
165
166 Pixmap fgpix = XCreatePixmap(dpy, win, 1, 1, 24);
167 XRenderPictureAttributes attr = { .repeat = True };
168 fmt = XRenderFindStandardFormat(dpy, PictStandardRGB24);
169 Picture fgpic = XRenderCreatePicture(dpy, fgpix, fmt, CPRepeat, &attr);
170 XRenderFillRectangle(dpy, PictOpSrc, fgpic, &fg, 0, 0, 1, 1);
171
172 fmt = XRenderFindStandardFormat(dpy, PictStandardA8);
173 GlyphSet glyphset = XRenderCreateGlyphSet(dpy, fmt);
174
175 PASTRY pastry = {
176 .xScale = 16*s,
177 .yScale = 16*s,
178 .flags = PASTRY_DOWNWARD_Y,
179 };
180 pastry.font = pastry_loadfile("firago-extended.ttf");
181 if (pastry.font == NULL)
182 END("failed to load font");
183
184 // harfbuzz does the shaping; pastry just draws the glyph ids it hands us
185 hb_blob_t *blob = hb_blob_create_from_file_or_fail("firago-extended.ttf");
186 if (blob == NULL)
187 END("harfbuzz failed to load font");
188 hb_face_t *face = hb_face_create(blob, 0);
189 hb_font_t *font = hb_font_create(face);
190 hb_ot_font_set_funcs(font);
191 hb_buffer_t *buffer = hb_buffer_create();
192
193 XEvent event;
194 while (!XNextEvent(dpy, &event)) {
195 if (event.type == Expose) {
196 XRenderFillRectangle(dpy, PictOpOver, pic, &bg, 0, 0, (unsigned int)event.xexpose.width, (unsigned int)event.xexpose.height);
197
198 FILE *file = fopen("text.utf8", "r");
199 if (file == NULL)
200 END("cannot open input text");
201
202 PASTRY_LMetrics lmtx;
203 pastry_lmetrics(&pastry, &lmtx);
204 int y = 20 + (int)lmtx.ascender + (int)lmtx.lineGap;
205
206 char text[MAX_CHARS];
207 while (fgets(text, sizeof(text), file)) {
208 size_t len = strlen(text);
209 if (len > 0 && text[len - 1] == '\n')
210 text[--len] = 0;
211
212 if (draw_line(dpy, glyphset, fgpic, pic, &pastry, font, buffer, text, y) < 0)
213 END("failed to draw line");
214
215 y += 2 * ((int)lmtx.ascender + (int)lmtx.descender + (int)lmtx.lineGap);
216 }
217
218 fclose(file);
219 } else if (event.type == ClientMessage) {
220 if ((Atom) event.xclient.data.l[0] == wmDeleteWindow)
221 break;
222 }
223 }
224
225 hb_buffer_destroy(buffer);
226 hb_font_destroy(font);
227 hb_face_destroy(face);
228 hb_blob_destroy(blob);
229
230 pastry_freefont(pastry.font);
231 XCloseDisplay(dpy);
232 return 0;
233}