1
0
mirror of https://github.com/tomolt/libschrift synced 2025-05-29 20:09:34 -04:00

Initial commit.

This commit is contained in:
Thomas Oltmann 2020-04-03 16:25:13 +02:00
commit d1ea25fcb5
10 changed files with 452 additions and 0 deletions

3
.gitignore vendored Normal file

@ -0,0 +1,3 @@
*.o
libschrift.a
sftdemo

16
LICENSE Normal file

@ -0,0 +1,16 @@
ISC License
© 2019, 2020 Thomas Oltmann
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

32
Makefile Normal file

@ -0,0 +1,32 @@
# See LICENSE file for copyright and license details.
.POSIX:
include config.mk
.PHONY: all clean install uninstall
all: libschrift.a sftdemo
libschrift.a: schrift.o
$(AR) rcs $@ $^
sftdemo: sftdemo.o libschrift.a
$(LD) $(LDFLAGS) $< -o $@ -L. -lschrift
sftdemo.o schrift.o: schrift.h
sftdemo.o: arg.h
clean:
rm -f *.o
rm -f libschrift.a
rm -f sftdemo
install: all
mkdir -p "$(DESTDIR)$(PREFIX)/lib"
cp -f libschrift.a "$(DESTDIR)$(PREFIX)/lib"
mkdir -p "$(DESTDIR)$(PREFIX)/include"
cp -f schrift.h "$(DESTDIR)$(PREFIX)/include"
uninstall:
rm -f "$(DESTDIR)$(PREFIX)/lib/libschrift.a"
rm -f "$(DESTDIR)$(PREFIX)/include/schrift.h"

52
README.md Normal file

@ -0,0 +1,52 @@
libschrift
==========
*libschrift* is a novel TTF font rendering library with focus
on simplicity, correctness, and security.
Goals
-----
- Be as suckless as possible.
See: <https://www.suckless.org/philosophy/>
- Make correct (as in artifact-free, UTF-8 handling etc.)
font rendering easy-to-use.
- Be reasonably secure, which especially means to not crash,
leak memory / resources or expose major security
vulnerabilities on corrupted / malicious / random input.
Rationale
---------
Besides *libschrift*, there really are only two cross-platform
open-source font rendering libraries available: *FreeType2* and
*stb\_truetype*. *FreeType2* is a pretty stable implementation
that supports most state-of-the-art features, but that also makes
it incredibly bloated and frustrating to use. *stb\_truetype* is
a lot better in this regard by virtue of keeping a much smaller
scope, but it suffers from some annoying problems that can lead to
visual artifacts. On top of that, it doesn't seem to be designed
to be particularly secure, as its main use-case is working on
trusted input files.
Limitations
-----------
- Only Latin, Cyrillic and Greek writing systems will be fully
supported for now, as others would require significantly more
support for OpenType-specific features.
- The only available text encoding is UTF-8.
See: <http://www.utf8everywhere.org>
- Support for most TrueType (.ttf) and OpenType (.otf) fonts.
No bitmap or PostScript fonts (for now).
- No hinting. Especially no auto-hinting like *FreeType2*.
Progress
--------
Although most logic is already in-place and working, *libschrift*
is not quite usable yet. Still missing are:
- Better documentation. At least some more complete man pages.
- Compound glyph support.
- Correct handling of grapheme clusters?
- Removal of TTF "parsing" stage.
- No more bounds checking in big-endian 'get' functions - use
'pop' or ranges instead.
- More rigorous bounds-checking everywhere.

50
arg.h Normal file

@ -0,0 +1,50 @@
/*
* ISC-License
*
* Copyright 2004-2017 Christoph Lohmann <20h@r-36.net>
* Copyright 2017-2018 Laslo Hunhold <dev@frign.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef ARG_H
#define ARG_H
extern char *argv0;
/* int main(int argc, char *argv[]) */
#define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \
*argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \
int i_, argused_; \
if ((*argv)[1] == '-' && !(*argv)[2]) { \
argc--, argv++; \
break; \
} \
for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \
switch((*argv)[i_])
#define ARGEND if (argused_) { \
if ((*argv)[i_ + 1]) { \
break; \
} else { \
argc--, argv++; \
break; \
} \
} \
} \
}
#define ARGC() ((*argv)[i_])
#define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \
(*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x))
#define EARGF(x) ARGF_(((x), exit(1), (char *)0))
#define ARGF() ARGF_((char *)0)
#endif

15
config.mk Normal file

@ -0,0 +1,15 @@
# Customize below to fit your system
# compiler and linker
CC = cc
LD = cc
AR = ar
# flags
CPPFLAGS = -I./
CFLAGS = -g -std=c99 -pedantic -Wall -Wextra
LDFLAGS = -g
# installation paths
PREFIX = /usr/local

BIN
resources/Ubuntu-R.ttf Normal file

Binary file not shown.

160
schrift.c Normal file

@ -0,0 +1,160 @@
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include "schrift.h"
/* So as it turns out, these first three naive macros are actually
* faster than any bit-tricks or specialized functions on amd64. */
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define ABS(x) ((x) >= 0 ? (x) : -(x))
struct SFT_Font
{
uint8_t *memory;
size_t size;
};
struct SFT
{
SFT_Font *font;
double xScale, yScale;
};
static inline uint8_t
getu8(SFT_Font *font, size_t offset)
{
assert(offset + 1 <= font->size);
return *(font->memory + offset);
}
static inline uint16_t
getu16(SFT_Font *font, size_t offset)
{
assert(offset + 2 <= font->size);
uint8_t *base = font->memory + offset;
uint16_t b1 = base[0], b0 = base[1];
return (uint16_t) (b1 << 8 | b0);
}
static inline int16_t
geti16(SFT_Font *font, size_t offset)
{
return (int16_t) getu16(font, offset);
}
static inline uint32_t
getu32(SFT_Font *font, size_t offset)
{
assert(offset + 4 <= font->size);
uint8_t *base = font->memory + offset;
uint32_t b3 = base[0], b2 = base[1], b1 = base[2], b0 = base[3];
return (uint32_t) (b3 << 24 | b2 << 16 | b1 << 8 | b0);
}
static int
compare_tables(const void *a, const void *b)
{
return memcmp(a, b, 4);
}
static size_t
gettable(SFT_Font *font, char tag[4])
{
uint16_t numTables = getu16(font, 4);
void *match = bsearch(tag, font->memory + 12, numTables, 16, compare_tables);
if (match == NULL) return 0;
size_t matchOffset = font->memory - (uint8_t *) match;
return getu32(font, matchOffset + 4);
}
static int
readfile(SFT_Font *font, const char *filename)
{
struct stat info;
if (stat(filename, &info) < 0) {
return -1;
}
font->size = info.st_size;
font->memory = malloc(font->size);
if (font->memory == NULL) {
return -1;
}
FILE *file = fopen(filename, "rb");
if (file == NULL) {
free(font->memory);
return -1;
}
/* TODO error handling here! */
fread(font->memory, 1, font->size, file);
fclose(file);
return 0;
}
SFT_Font *
sft_loadfile(char const *filename)
{
SFT_Font *font = calloc(1, sizeof(SFT_Font));
if (font == NULL) {
return NULL;
}
if (readfile(font, filename) < 0) {
free(font);
return NULL;
}
uint32_t scalerType = getu32(font, 0);
if (scalerType != 0x00010000 && scalerType != 0x74727565) {
free(font);
return NULL;
}
return font;
}
void
sft_freefont(SFT_Font *font)
{
if (font == NULL) return;
free(font->memory);
free(font);
}
SFT *
sft_create(void)
{
SFT *sft = calloc(1, sizeof(SFT));
if (sft == NULL) return NULL;
return sft;
}
void
sft_destroy(SFT *sft)
{
if (sft == NULL) return;
free(sft);
}
void
sft_setstyle(SFT *sft, struct SFT_Style style)
{
double sizeInPixels = 0.0;
switch (style.units) {
case 'd':
sizeInPixels = style.size * style.vdpi / 72.0;
break;
case 'x':
default:
sizeInPixels = style.size;
break;
}
const long unitsPerEm = 1000; /* FIXME */
double unitsToPixels = sizeInPixels / unitsPerEm;
double ratio = style.hdpi / style.vdpi;
sft->font = style.font;
sft->xScale = unitsToPixels * ratio;
sft->yScale = unitsToPixels;
}

47
schrift.h Normal file

@ -0,0 +1,47 @@
/* This file is part of libschrift.
*
* © 2019, 2020 Thomas Oltmann
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#ifndef SCHRIFT_H
#define SCHRIFT_H 1
struct SFT_Font;
struct SFT;
typedef struct SFT_Font SFT_Font;
typedef struct SFT SFT;
struct SFT_Style
{
SFT_Font *font;
double hdpi, vdpi, size;
char units;
};
SFT_Font *sft_loadfile(char const *filename);
void sft_freefont(SFT_Font *font);
SFT *sft_create(void);
void sft_destroy(SFT *sft);
void sft_clear(SFT *sft);
void sft_setstyle(SFT *sft, struct SFT_Style style);
double sft_linegap(SFT *sft);
void *sft_image(SFT *sft, int bounds[4]);
void sft_boundstring(SFT *sft, double x, double y, const char *str, int bounds[4]);
void sft_drawstring(SFT *sft, double x, double y, const char *str);
#endif

77
sftdemo.c Normal file

@ -0,0 +1,77 @@
#include <stdio.h>
#include <stdlib.h>
#include <schrift.h>
#include "arg.h"
char *argv0;
static void
die(const char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(1);
}
static void
usage(void)
{
fprintf(stderr,
"usage: %s [-f font file] [-h hdpi] [-v vdpi] "
"[-d size in pt] [-x size in px]\n", argv0);
}
int
main(int argc, char *argv[])
{
const char *filename;
SFT_Font *font;
struct SFT_Style style;
SFT *sft;
filename = "resources/Ubuntu-R.ttf";
style.hdpi = 96.0;
style.vdpi = 96.0;
style.size = 64.0;
style.units = 'x';
ARGBEGIN {
case 'f':
filename = EARGF(usage());
break;
case 'h':
style.hdpi = atof(EARGF(usage()));
break;
case 'v':
style.vdpi = atof(EARGF(usage()));
break;
case 'd':
style.size = atof(EARGF(usage()));
style.units = 'd';
break;
case 'x':
style.size = atof(EARGF(usage()));
style.units = 'x';
break;
default:
usage();
exit(1);
} ARGEND
if (argc) {
usage();
exit(1);
}
if ((font = sft_loadfile(filename)) == NULL)
die("Can't load font file.");
if ((sft = sft_create()) == NULL)
die("Can't create schrift context.");
style.font = font;
sft_setstyle(sft, style);
sft_destroy(sft);
sft_freefont(font);
return 0;
}