Commit 18dff817 authored by bzt's avatar bzt

Initial commit for editor

parent 041f7bb8
......@@ -148,10 +148,11 @@ bitmap for "A" already).
The [autohinting](https://gitlab.com/bztsrc/scalable-font/blob/master/docs/compare.md) code is very very basic
at the moment, it only use one axis partitions instead of boxes.
A portable SSFN editor with GUI. I'm already on it.
A [portable SSFN editor with GUI](https://gitlab.com/bztsrc/scalable-font/tree/master/sfnedit). I'm already on it.
It' still very early in development, you can't modify yet, but it's good enough to preview any SSFN font.
Authors
-------
SSFN format, converters and renderers: bzt
SSFN format, converters, editor and renderers: bzt
......@@ -75,7 +75,7 @@ Converting SSFN ASCII Fonts
SSFN has a plain text, human readable "source file" format, called [ASC](https://gitlab.com/bztsrc/scalable-font/blob/master/docs/asc_format.md).
The `bit2sfn` tool can read that and convert into a compressed binary SSFN file. The last tool, `sfn2asc` does
the opposite: it converts binary SSFN fonts into plain text SSFN ASCII format. If it can found [UnicodeData.txt](http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt)
in one of /usr/share/ssfn, ~/.config/ssfn, ~/.ssfn or current working directories, then the generated ASC file
in one of /usr/share/unicode, ~/.config/ssfn, ~/.ssfn or current working directories, then the generated ASC file
will contain UNICODE code point names too in glyph definitions (which is very handy).
SSFN ASCII format is a text editor friendly format (with UTF-8 sequences in data). It was designed in a way that
......
Scalable Screen Font Editor
===========================
TODO
features.png

35.1 KB | W: | H:

features.png

35 KB | W: | H:

features.png
features.png
features.png
features.png
  • 2-up
  • Swipe
  • Onion skin
No preview for this file type
No preview for this file type
No preview for this file type
......@@ -392,7 +392,7 @@ int frag_split(int idx, int offs, int siz)
if(siz >= frags[idx].h) return idx;
if(!offs) {
ni = frag_add(frags[idx].t, frags[idx].p, frags[idx].h - siz + 1, frags[idx].data + siz*frags[idx].p);
ni = frag_add(frags[idx].t, frags[idx].p, frags[idx].h - siz, frags[idx].data + siz*frags[idx].p);
frags[idx].h = siz;
} else {
ni = frag_add(frags[idx].t, frags[idx].p, siz, frags[idx].data + (frags[idx].h - siz)*frags[idx].p);
......@@ -599,6 +599,7 @@ uint32_t getnum(char *s)
if((*s=='0' && s[1]=='x') || (*s=='U' && s[1]=='+')) return gethex(s+2,8);
return atoi(s);
}
/**
* Block name comparition according to UNICODE Inc.
*/
......
......@@ -282,7 +282,7 @@ int main(int argc, char **argv)
{
int i = 0, j, k, l, t, T, L, m, n, x, y, a, b, c, d, w, h, map, range = 0, dump = 0, *frgoffs, gs;
char *infile, *outfile, *fam[] = { "Serif", "Sans", "Decorative", "Monospace", "Handwriting", "?" };
char *uninamesdb[] = { "/usr/share/ssfn", "~/.config/ssfn", "~/.ssfn", ".", NULL }, **uninames = NULL;
char *uninamesdb[] = { "/usr/share/unicode", "~/.config/ssfn", "~/.ssfn", ".", NULL }, **uninames = NULL;
char *dump_fam[] = { "SERIF", "SANS", "DECOR", "MONO", "HAND", "?" };
char *dump_cmd[] = { "COLOR", "LINE ", "QUAD ", "CUBIC" };
char *dump_frg[] = { "BITMAP ", "LBITMAP", "PIXMAP ", "HINTING" };
......
TARGET = sfnedit
SRCS = $(filter-out tinflate.c $(wildcard ui_*.c),$(wildcard *.c))
ifneq ("$(wildcard ../fonts/unifont.sfn.gz)","")
FONT = ../fonts/unifont.sfn.gz
else
FONT = unifont.sfn.gz
endif
ifneq ("$(wildcard ../../Config)","")
include ../../Config
SRCS += ui_osZ.c
CFLAGS += -I../../include
LIBS += -lui
DRIVER = osZ
endif
CFLAGS ?= -Wall -Wextra -ansi -pedantic -g
ifneq ("$(wildcard ../ssfn.h)","")
CFLAGS += -I..
endif
ifneq ("$(wildcard /usr/include/zlib.h)","")
CFLAGS += -DHAS_ZLIB=1
LIBS += -lz
else
SRCS += tinflate.c
endif
# SDL driver
ifeq ("$(FORCEX11)","")
ifeq ("$(DRIVER)","")
ifneq ("$(wildcard /Library/Frameworks/SDL*)","")
SRCS += ui_sdl.c
LIBS += -framework SDL
DRIVER = sdl
else
ifneq ("$(wildcard /usr/include/SDL2/SDL.h)","")
SRCS += ui_sdl.c
CFLAGS += -I/usr/include/SDL2
LIBS += -lSDL2
DRIVER = sdl
endif
endif
endif
endif
# fallback X11 driver
ifeq ("$(DRIVER)","")
ifneq ("$(wildcard /opt/local/include/X11/Xlib.h)","")
SRCS += ui_x11.c
CFLAGS += -I/opt/local/include/X11
LIBS += -lX11 -L/opt/X11/lib -L/opt/local/lib
DRIVER = x11
else
ifneq ("$(wildcard /usr/lib/libX11*)","")
SRCS += ui_x11.c
CFLAGS += -I/usr/include/X11
LIBS += -lX11
DRIVER = x11
endif
endif
endif
OBJS = $(SRCS:.c=.o) logofont.o font.o
all: configure $(OBJS) $(TARGET)
configure:
ifeq ("$(DRIVER)","")
@echo "No ui driver can be detected. Install libsdl-dev or libx11-dev."
@false
endif
logofont.o: logofont.sfn
@$(LD) -r -b binary -o logofont.o logofont.sfn
font.o: $(FONT)
@cp $(FONT) ./font
@$(LD) -r -b binary -o font.o font
@rm font
%: %.c ../ssfn.h
$(CC) $(CFLAGS) $< -c $@
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET) $(LIBS)
clean:
@rm config.h $(TARGET) *.o 2>/dev/null || true
Scalable Screen Font Editor
===========================
A little application to read and modify [SSFN](https://gitlab.com/bztsrc/scalable-font/blob/master/docs/sfn_format.md) or
[ASC](https://gitlab.com/bztsrc/scalable-font/blob/master/docs/asc_format.md) files with GUI.
Read the user manual on this tool in the [documentation](https://gitlab.com/bztsrc/scalable-font/blob/master/docs/editor.md).
Dependencies
------------
Uses SDL by default, so it should be easy to port. If SDL not found, it fallbacks to X11.
It autodetects zlib too. If found, then it can read and write gzip compressed files. If not,
then decompression is solved by a built-in tiny inflate library, but no compression support
on writes. Other than those, requires libc only.
<img src='https://gitlab.com/bztsrc/scalable-font/raw/master/docs/sfnedit3.png'>
<img src='https://gitlab.com/bztsrc/scalable-font/raw/master/docs/sfnedit4.png'>
/*
* sfnedit/file.c
*
* Copyright (C) 2019 bzt (bztsrc@gitlab)
*
* 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.
*
* @brief SSFN file functions (Model)
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "lang.h"
#include "file.h"
#include "ui.h"
#include "util.h"
#include "unicode.h"
#if HAS_ZLIB
#include <zlib.h>
#endif
extern char gz;
ssfn_font_t *font = NULL;
char *fontfile = NULL, *fontfilebn = NULL, gzipped = 0;
char *strtable[6];
uint32_t cmap[240];
int modified = 0;
int numchars = 0;
char_t *chars = NULL;
int numfrags = 0;
frag_t *frags = NULL;
int numkerns = 0;
/**
* Sort characters by UNICODE
*/
int file_chrsort(const void *a, const void *b)
{
return ((char_t*)a)->unicode - ((char_t*)b)->unicode;
}
/**
* Return a character for UNICODE
*/
int file_findchar(uint32_t unicode)
{
register int i=0, j=numchars-1, k, l=22;
if(!numchars || unicode > 0x10FFFF) return -1;
while(l--) {
k = i + ((j-i) >> 1);
if(chars[k].unicode == unicode) return k;
if(i >= j) break;
if(chars[k].unicode < unicode) i = k + 1; else j = k;
}
return -1;
}
/**
* Find a character for UNICODE and add a new one if not found
*/
int file_addchar(uint32_t unicode, int check)
{
int i;
if(check) {
i = file_findchar(unicode);
if(i != -1) return i;
}
for(i=0; i < UNICODE_NUMBLOCKS; i++)
if(ublocks[i].start <= (int)unicode && ublocks[i].end >= (int)unicode) { ublocks[i].cnt++; break; }
i = numchars++;
chars = (char_t*)realloc(chars, numchars*sizeof(char_t));
if(!chars) error("file_addchar", ERR_MEM);
memset(&chars[i], 0, sizeof(char_t));
chars[i].unicode = unicode;
if(check) {
qsort(chars, numchars, sizeof(char_t), file_chrsort);
ui_updatewin(unicode, +1);
}
return i;
}
/**
* Add a contour fragment
*/
void file_contadd(int fragidx, int type, int px, int py, int c1x, int c1y, int c2x, int c2y)
{
int i;
frag_t *f = &frags[fragidx];
cont_t *c;
i = f->len++;
f->data = (cont_t*)realloc(f->data, f->len*sizeof(char_t));
if(!f->data) error("file_contadd", ERR_MEM);
c = &f->data[i];
memset(c, 0, sizeof(char_t));
c->type = type;
c->px = px;
c->py = py;
c->c1x = c1x;
c->c1y = c1y;
c->c2x = c2x;
c->c2y = c2y;
}
/**
* Add a fragment to character
*/
int file_fragadd(int type)
{
int i, s = 16 << font->quality;
i = numfrags++;
frags = (frag_t*)realloc(frags, numfrags*sizeof(frag_t));
if(!frags) error("file_fragadd", ERR_MEM);
memset(&frags[i], 0, sizeof(frag_t));
frags[i].type = type;
if(type == SSFN_FRAG_BITMAP) {
frags[i].data = (cont_t*)malloc(s*s/8);
if(!frags[i].data) error("file_fragadd", ERR_MEM);
memset(frags[i].data, 0, s*s/8);
} else
if(type == SSFN_FRAG_PIXMAP) {
frags[i].data = (cont_t*)malloc(s*s);
if(!frags[i].data) error("file_fragadd", ERR_MEM);
memset(frags[i].data, 0xF0, s*s);
}
return i;
}
/**
* Read and parse a fragment from compressed SSFN data
*/
void file_addrawfrag(char_t *chr, uint8_t* raw, int ox, int oy)
{
int i, j, l, n, t, x, y, a, b, c, d, s = 16 << font->quality;
unsigned char *ra, *re, *data;
if(raw[0] & 0x80) {
switch((raw[0] & 0x60)>>5) {
case SSFN_FRAG_LBITMAP:
x = ((((raw[0]>>2)&3)<<8)+raw[1])+1;
y = (((raw[0]&3)<<8)|raw[2])+1;
raw += 3;
goto bitmap;
case SSFN_FRAG_BITMAP:
x = (raw[0] & 0x1F)+1;
y = raw[1]+1;
raw += 2;
bitmap: for(i=0,l=-1;i<chr->len;i++)
if(frags[chr->frags[i]].type == SSFN_FRAG_BITMAP) { l = i; break; }
if(l == -1) {
l = chr->len; chr->len++;
chr->frags = (int*)realloc(chr->frags, (chr->len)*sizeof(int));
if(!chr->frags) error("file_addrawfrag", ERR_MEM);
chr->frags[l] = file_fragadd(SSFN_FRAG_BITMAP);
}
data = (unsigned char*)frags[chr->frags[l]].data;
b = s >> 3;
for(j=0; j < y && oy + j < s; j++)
for(i=0; i < x && i < b; i++)
data[(oy + j) * b + i] = *raw++;
break;
case SSFN_FRAG_PIXMAP:
x = (((raw[0] & 12) << 6) | raw[1])+1;
y = (((raw[0] & 3) << 8) | raw[2])+1;
n = ((raw[4]<<8) | raw[3])+1;
raw += 5;
if(raw[-5] & 0x10) {
/* todo: direct ARGB values in pixmap fragment */
}
a = x * y;
ra = (unsigned char *)malloc(a);
if(!ra) error("file_addrawfrag", ERR_MEM);
for(re = raw+n, i=0; i < a && raw < re;) {
c = (raw[0] & 0x7F)+1;
if(raw[0] & 0x80) { for(j=0; j < c; j++) ra[i++] = raw[1]; raw += 2; }
else { raw++; for(j=0; j < c; j++) ra[i++] = *raw++; }
}
for(i=0,l=-1;i<chr->len;i++)
if(frags[chr->frags[i]].type == SSFN_FRAG_PIXMAP) { l = i; break; }
if(l == -1) {
l = chr->len; chr->len++;
chr->frags = (int*)realloc(chr->frags, (chr->len)*sizeof(int));
if(!chr->frags) error("file_addrawfrag", ERR_MEM);
chr->frags[l] = file_fragadd(SSFN_FRAG_PIXMAP);
}
data = (unsigned char*)frags[chr->frags[l]].data;
re = ra;
for(j=0; j < y && oy + j < s; j++)
for(i=0; i < x && ox + i < s; i++)
data[(oy + j) * s + ox + i] = *re++;
free(ra);
break;
case SSFN_FRAG_HINTING:
/*
if(raw[0] & 0x10) { n = ((raw[0]&0xF)<<8) | raw[1]; raw += 2; } else { n = raw[0] & 0xF; raw++; }
y = 4096; x = ((ox >> s) - 1) << (s-4);
chr->h[y++] = x;
for(n++;n-- && x < 4096;) {
x = raw[0]; raw++;
if(font->features & SSFN_FEAT_HBIGCRD) { x |= (raw[0] << 8); raw++; }
x <<= (s-4);
chr->h[y++] = x;
}
if(y < 4096) chr->h[y++] = 65535;
*/
break;
}
} else {
if(raw[0] & 0x40) {
l = ((raw[0] & 0x3F) << 8) | raw[1];
raw += 2;
} else {
l = raw[0] & 0x3F;
raw++;
}
l++;
if(font->quality < 5) {
x = raw[0] & 0xFF; y = raw[1] & 0xFF;
raw += 2;
} else {
x = ((raw[0] & 15) << 8) | raw[1]; y = ((raw[0] & 0xF0) << 4) | raw[2];
raw += 3;
}
j=chr->len; chr->len++;
chr->frags = (int*)realloc(chr->frags, (chr->len)*sizeof(int));
if(!chr->frags) error("file_addrawfrag", ERR_MEM);
chr->frags[j] = file_fragadd(SSFN_FRAG_CONTOUR);
file_contadd(chr->frags[j], SSFN_CONTOUR_MOVE, ox+x, oy+y, 0,0, 0,0);
for(i=0;i<l;i++) {
t = font->quality < 4 ? (raw[0] >> 7) | ((raw[1] >> 6) & 2) : raw[0] & 3;
x = y = a = b = c = d = 0;
switch(font->quality) {
case 0:
case 1:
case 2:
case 3:
x = raw[0] & 0x7F; y = raw[1] & 0x7F;
switch(t) {
case 0: a = (x << 8) | y; raw += 2; break;
case 1: raw += 2; break;
case 2: a = raw[2] & 0x7F; b = raw[3] & 0x7F; raw += 4; break;
case 3: a = raw[2] & 0x7F; b = raw[3] & 0x7F; c = raw[4] & 0x7F; d = raw[5] & 0x7F; raw += 6; break;
}
break;
case 4:
x = raw[1]; y = raw[2];
switch(t) {
case 0: a = raw[1]; raw += 2; break;
case 1: raw += 3; break;
case 2: a = raw[3]; b = raw[4]; raw += 5; break;
case 3: a = raw[3]; b = raw[4]; c = raw[5]; d = raw[6]; raw += 7; break;
}
break;
case 5:
x = ((raw[0] & 4) << 6) | raw[1]; y = ((raw[0] & 8) << 5) | raw[2];
switch(t) {
case 0: a = raw[1]; raw += 2; break;
case 1: raw += 3; break;
case 2: a = ((raw[0] & 16) << 4) | raw[3]; b = ((raw[0] & 32) << 3) | raw[4]; raw += 5; break;
case 3: a = ((raw[0] & 16) << 4) | raw[3]; b = ((raw[0] & 32) << 3) | raw[4];
c = ((raw[0] & 64) << 2) | raw[5]; d = ((raw[0] & 128) << 1) | raw[6]; raw += 7; break;
}
break;
default:
x = ((raw[0] & 12) << 6) | raw[1]; y = ((raw[0] & 48) << 4) | raw[2];
switch(t) {
case 0: a = raw[1]; raw += 2; break;
case 1: raw += 3; break;
case 2: a = ((raw[3] & 3) << 8) | raw[4]; b = ((raw[3] & 12) << 6) | raw[5]; raw += 6; break;
case 3: a = ((raw[3] & 3) << 8) | raw[4]; b = ((raw[3] & 12) << 6) | raw[5];
c = ((raw[3] & 48) << 4) | raw[6]; d = ((raw[3] & 192) << 2) | raw[7]; raw += 8; break;
}
break;
}
switch(t) {
case 0: file_contadd(chr->frags[j], SSFN_CONTOUR_COLOR, a,0, 0,0, 0,0); break;
case 1: file_contadd(chr->frags[j], SSFN_CONTOUR_LINE, ox+x, oy+y, 0,0, 0,0); break;
case 2: file_contadd(chr->frags[j], SSFN_CONTOUR_QUAD, ox+x,oy+y,ox+a,oy+b, 0,0); break;
case 3: file_contadd(chr->frags[j], SSFN_CONTOUR_CUBIC, ox+x,oy+y,ox+a,oy+b,ox+c,oy+d); break;
}
} /* for numfrags */
}
}
/**
* Load a font file
*/
void file_load(char *filename)
{
unsigned int size;
int i, j, k, l, n, x, y, L, T;
char *str;
uint8_t *ptr, *end, *e;
char_t *c;
fontfile = filename;
ptr = load_file(fontfile, (int*)&size);
if(!ptr || !size) error("file_load", ERR_LOAD, filename);
gzipped = gz;
if(!memcmp((char*)ptr, "## S", 4)) {
font = (ssfn_font_t*)malloc(sizeof(ssfn_font_t));
if(!font) error("file_load", ERR_MEM);
memset(font, 0, sizeof(ssfn_font_t));
memcpy(font->magic, SSFN_MAGIC, 4);
font->bbox_top = font->bbox_left = 65535;
end = ptr + size;
while(ptr < end && *ptr) {
if(*ptr=='+') {
if(ptr[1]=='#') {
if(ptr[2]=='f' && ptr[3]=='a') { ptr += 10; font->family = atoi((const char*)ptr); }
if(ptr[2]=='q') { ptr += 11; font->quality = atoi((const char*)ptr); }
if(ptr[2]=='b' && ptr[3]=='a') { ptr += 12; if(!font->baseline) font->baseline = atoi((const char*)ptr); }
if(ptr[2]=='u') { ptr += 13; font->underline = atoi((const char*)ptr); }
if(ptr[2]=='s') {
for(ptr += 9;*ptr!='\n';ptr++) {
if(*ptr=='b') font->style |= SSFN_STYLE_BOLD;
if(*ptr=='i') font->style |= SSFN_STYLE_ITALIC;
}
}
} else
if(ptr[1]=='$') {
k = ptr[2];
while(*ptr && *ptr!=' ') ptr++;
while(*ptr==' ') ptr++;
for(e=ptr;*e && *e!='\r' && *e!='\n';e++);
str = malloc(256);
if(!str) error("file_load", ERR_MEM);
memcpy(str, ptr, e-ptr);
str[e-ptr]=0;
switch(k) {
case 'n': strtable[0] = str; break;
case 'f': strtable[1] = str; break;
case 's': strtable[2] = str; break;
case 'r': strtable[3] = str; break;
case 'm': strtable[4] = str; break;
case 'l': strtable[5] = str; break;
default: free(str); str = NULL; break;
}
} else
if(ptr[1]!='@') break;
}
ptr++;
}
return;
}
font = (ssfn_font_t*)ptr;
if(memcmp(font->magic, SSFN_MAGIC, 4) || size != font->size ||
memcmp((uint8_t*)font + font->size - 4, SSFN_ENDMAGIC, 4) ||
font->family > SSFN_FAMILY_HAND || font->fragments_offs > font->size || font->characters_offs > font->size ||
font->kerning_offs > font->size || font->fragments_offs > font->characters_offs) {
error("file_load", ERR_BADFILE, filename);