mirror of
https://github.com/tomolt/libschrift
synced 2025-05-29 20:09:34 -04:00
Initial commit.
This commit is contained in:
commit
d1ea25fcb5
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.o
|
||||
libschrift.a
|
||||
sftdemo
|
16
LICENSE
Normal file
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
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
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
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
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
BIN
resources/Ubuntu-R.ttf
Normal file
Binary file not shown.
160
schrift.c
Normal file
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
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
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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user