Browse Source

Initial commit

Léo Gaspard 5 years ago
commit
6eabb6ed72
8 changed files with 373 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 19 0
      LICENSE
  3. 19 0
      Makefile
  4. 7 0
      TODO
  5. 15 0
      config.mk
  6. 180 0
      dtext.c
  7. 38 0
      dtext.h
  8. 94 0
      test.c

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+example

+ 19 - 0
LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2016 Leo Gaspard <leo@gaspard.io>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.

+ 19 - 0
Makefile

@@ -0,0 +1,19 @@
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = dtext.c test.c
+HDR = dtext.h
+
+all: example
+
+test: example
+	./example
+
+example: config.mk Makefile ${SRC} ${HDR}
+	${CC} ${CFLAGS} ${LDFLAGS} ${SRC} -o example
+
+clean:
+	rm -f example
+
+.PHONY: all test clean

+ 7 - 0
TODO

@@ -0,0 +1,7 @@
+Still to do:
+ * Cleanly report Xlib errors
+ * Retrieval of bounding box of text without displaying it
+ * Support for wide chars (wchar_t)
+ * Support for bold and italics
+ * Support for fallback fonts
+ * Document

+ 15 - 0
config.mk

@@ -0,0 +1,15 @@
+# See LICENSE file for copyright and license details.
+
+WARN_CFLAGS = -Wall -Wextra -pedantic -D_XOPEN_SOURCE=700
+
+DBG_CFLAGS  = -g
+DBG_LDFLAGS = -g
+
+FT_CFLAGS  = $(shell freetype-config --cflags)
+FT_LDFLAGS = $(shell freetype-config --libs)
+
+XLIB_LDFLAGS = -lX11 -lXrender
+
+CFLAGS  += -std=c99 ${XLIB_CFLAGS} ${FT_CFLAGS} ${WARN_CFLAGS} ${DBG_CFLAGS}
+LDFLAGS += ${XLIB_LDFLAGS} ${FT_LDFLAGS} ${DBG_LDFLAGS}
+# CC = cc

+ 180 - 0
dtext.c

@@ -0,0 +1,180 @@
+/* See LICENSE file for copyright and license details. */
+
+#include <errno.h>
+#include <stdint.h>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrender.h>
+
+#include "dtext.h"
+
+static dt_error load_char(dt_context *ctx, dt_font *fnt, char c);
+
+dt_error
+dt_init(dt_context **res, Display *dpy, Window win)
+{
+	dt_error err;
+	dt_context *ctx;
+	Visual *visual;
+	Pixmap pix;
+	XRenderPictureAttributes attrs;
+
+	if (!(ctx = malloc(sizeof(*ctx))))
+		return -ENOMEM;
+
+	if ((err = FT_Init_FreeType(&ctx->ft_lib)))
+		goto fail_init_ft;
+
+	visual = XDefaultVisual(dpy, XDefaultScreen(dpy));
+	ctx->win_format = XRenderFindVisualFormat(dpy, visual);
+	XFree(visual);
+	ctx->argb32_format = XRenderFindStandardFormat(dpy, PictStandardARGB32);
+
+	ctx->dpy = dpy;
+	ctx->pic = XRenderCreatePicture(dpy, win, ctx->win_format, 0, &attrs);
+
+	pix = XCreatePixmap(dpy, DefaultRootWindow(dpy), 1, 1, 32);
+	attrs.repeat = 1;
+	ctx->fill = XRenderCreatePicture(ctx->dpy, pix, ctx->argb32_format,
+	                                 CPRepeat, &attrs);
+	XFreePixmap(dpy, pix);
+
+	*res = ctx;
+	return 0;
+
+fail_init_ft:
+	free(ctx);
+	return err;
+}
+
+dt_error
+dt_quit(dt_context *ctx)
+{
+	dt_error err = 0;
+
+	XRenderFreePicture(ctx->dpy, ctx->fill);
+	XRenderFreePicture(ctx->dpy, ctx->pic);
+
+	XFree(ctx->argb32_format);
+	XFree(ctx->win_format);
+
+	err = FT_Done_FreeType(ctx->ft_lib);
+
+	free(ctx);
+
+	return err;
+}
+
+dt_error
+dt_load(dt_context *ctx, dt_font **res, uint8_t size, char const *name)
+{
+	dt_error err;
+	dt_font *fnt;
+
+	if (!(fnt = malloc(sizeof(*fnt))))
+		return -ENOMEM;
+
+	if ((err = FT_New_Face(ctx->ft_lib, name, 0, &fnt->face)))
+		goto fail_new_face;
+	if ((err = FT_Set_Char_Size(fnt->face, size << 6, 0, 0, 0)))
+		goto fail_char_size;
+
+	fnt->gs = XRenderCreateGlyphSet(ctx->dpy, ctx->argb32_format);
+	memset(fnt->loaded, 0, 256);
+
+	*res = fnt;
+	return 0;
+
+fail_char_size:
+	FT_Done_Face(fnt->face); // if this fails... just ignore
+fail_new_face:
+	free(fnt);
+	return err;
+}
+
+dt_error
+dt_free(dt_context *ctx, dt_font *fnt)
+{
+	dt_error err = 0;
+
+	XRenderFreeGlyphSet(ctx->dpy, fnt->gs);
+
+	err = FT_Done_Face(fnt->face);
+
+	free(fnt);
+
+	return err;
+}
+
+dt_error
+dt_draw(dt_context *ctx, dt_font *fnt, dt_style const *style,
+        uint32_t x, uint32_t y, char const *txt)
+{
+	dt_error err;
+	XRenderColor col;
+	size_t len;
+	size_t i;
+
+	col.red   = (style->red   << 8) + style->red;
+	col.green = (style->green << 8) + style->green;
+	col.blue  = (style->blue  << 8) + style->blue;
+	col.alpha = 0xFFFF - ((style->alpha << 8) + style->alpha);
+	XRenderFillRectangle(ctx->dpy, PictOpSrc, ctx->fill, &col, 0, 0, 1, 1);
+
+	len = strlen(txt);
+
+	for (i = 0; i < len; ++i)
+		if ((err = load_char(ctx, fnt, txt[i])))
+			return err;
+
+	XRenderCompositeString8(ctx->dpy, PictOpOver, ctx->fill, ctx->pic,
+	                        ctx->argb32_format, fnt->gs, 0, 0, x, y,
+	                        txt, len);
+	return 0;
+}
+
+static dt_error
+load_char(dt_context *ctx, dt_font *fnt, char c)
+{
+	dt_error err;
+	FT_GlyphSlot slot;
+	Glyph gid;
+	XGlyphInfo g;
+	char *img;
+	size_t x, y, i;
+
+	if (fnt->loaded[(uint8_t) c])
+		return 0;
+
+	if ((err = FT_Load_Char(fnt->face, c, FT_LOAD_RENDER)))
+		return err;
+	slot = fnt->face->glyph;
+
+	gid = c;
+
+	g.width  = slot->bitmap.width;
+	g.height = slot->bitmap.rows;
+	g.x = - slot->bitmap_left;
+	g.y =   slot->bitmap_top;
+	g.xOff = slot->advance.x >> 6;
+	g.yOff = slot->advance.y >> 6;
+
+	if (!(img = malloc(4 * g.width * g.height)))
+		return -ENOMEM;
+	for (y = 0; y < g.height; ++y)
+		for (x = 0; x < g.width; ++x)
+			for (i = 0; i < 4; ++i)
+				img[4 * (y * g.width + x) + i] =
+					slot->bitmap.buffer[y * g.width + x];
+
+	XRenderAddGlyphs(ctx->dpy, fnt->gs, &gid, &g, 1,
+	                 img, 4 * g.width * g.height);
+
+	free(img);
+
+	fnt->loaded[(uint8_t) c] = 1;
+	return 0;
+}

+ 38 - 0
dtext.h

@@ -0,0 +1,38 @@
+/* See LICENSE file for copyright and license details. */
+
+typedef int32_t dt_error;
+
+typedef struct {
+	FT_Library ft_lib;
+
+	XRenderPictFormat *win_format;
+	XRenderPictFormat *argb32_format;
+
+	Display *dpy;
+	Picture pic;
+	Picture fill;
+} dt_context;
+
+typedef struct {
+	FT_Face face;
+
+	GlyphSet gs;
+	uint8_t loaded[256];
+} dt_font;
+
+typedef struct {
+	uint8_t red;
+	uint8_t green;
+	uint8_t blue;
+	uint8_t alpha; // 0 means opaque
+} dt_style;
+
+dt_error dt_init(dt_context **ctx, Display *dpy, Window win);
+dt_error dt_quit(dt_context *ctx);
+
+dt_error dt_load(dt_context *ctx, dt_font **fnt,
+                 uint8_t size, char const *name);
+dt_error dt_free(dt_context *ctx, dt_font *fnt);
+
+dt_error dt_draw(dt_context *ctx, dt_font *fnt, dt_style const *style,
+                 uint32_t x, uint32_t y, char const *txt);

+ 94 - 0
test.c

@@ -0,0 +1,94 @@
+/* See LICENSE file for copyright and license details. */
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/extensions/Xrender.h>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+#include "dtext.h"
+
+Display *dpy;
+Window win;
+GC gc;
+
+dt_context *ctx;
+dt_font *fnt;
+dt_style style;
+dt_style style_inv;
+
+static void setup_x();
+static void setup_dt();
+static void draw();
+
+int main()
+{
+	XEvent evt;
+
+	_Xdebug = 1;
+
+	setup_x();
+	setup_dt();
+
+	draw();
+
+	XSelectInput(dpy, win, ExposureMask | KeyPressMask);
+	while (1) {
+		XNextEvent(dpy, &evt);
+		switch (evt.type) {
+		case Expose:
+			draw();
+			break;
+		case KeyPress:
+			if (XLookupKeysym(&evt.xkey, 0) == XK_Escape)
+				return 0;
+			break;
+		}
+	}
+}
+
+static void setup_x()
+{
+	unsigned long white, black;
+
+	dpy = XOpenDisplay(NULL);
+
+	white = XWhitePixel(dpy, DefaultScreen(dpy));
+	black = XBlackPixel(dpy, DefaultScreen(dpy));
+
+	win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0,
+	                          700, 500, 0, white, white);
+	XMapWindow(dpy, win);
+
+	gc = XDefaultGC(dpy, 0);
+	XSetForeground(dpy, gc, black);
+}
+
+static void setup_dt()
+{
+	assert(!dt_init(&ctx, dpy, win));
+
+	assert(!dt_load(ctx, &fnt, 16, "/usr/share/fonts/fantasque-sans-mono/FantasqueSansMono-Regular.otf"));
+	//assert(!dt_load(ctx, &fnt, 16, "/usr/share/fonts/libertine/LinLibertine_R.otf"));
+
+	memset(&style, 0, sizeof(style));
+
+	memset(&style_inv, 0, sizeof(style_inv));
+	style_inv.red = 0xFF;
+	style_inv.green = 0xFF;
+	style_inv.blue = 0xFF;
+}
+
+static void draw()
+{
+	assert(!dt_draw(ctx, fnt, &style, 10, 50, "The quick brown fox jumps over the lazy dog."));
+
+	XFillRectangle(dpy, win, gc, 5, 60, 400, 100);
+	assert(!dt_draw(ctx, fnt, &style_inv, 10, 90, "The quick brown fox jumps over the lazy dog."));
+
+	XFlush(dpy);
+}