mirror of https://gitlab.com/nakst/essence
628 lines
20 KiB
C
628 lines
20 KiB
C
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
// TODO Extensions: binary search, shifting glyphs in editor, undo/redo.
|
|
|
|
#define UI_IMPLEMENTATION
|
|
#define UI_LINUX
|
|
#include "luigi.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "../shared/bitmap_font.h"
|
|
|
|
#define BITS_WIDTH (50)
|
|
#define BITS_HEIGHT (50)
|
|
#define ZOOM (18)
|
|
|
|
typedef struct Kerning {
|
|
int number, xOffset;
|
|
} Kerning;
|
|
|
|
typedef struct Glyph {
|
|
int number;
|
|
int xOrigin, yOrigin, xAdvance;
|
|
int x0, x1, y0, y1; // Tight bounding box.
|
|
uint8_t bits[BITS_HEIGHT][BITS_WIDTH];
|
|
Kerning *kerningArray; // When this glyph is on the left.
|
|
size_t kerningCount;
|
|
} Glyph;
|
|
|
|
UITable *glyphsTable;
|
|
UIWindow *window;
|
|
UITabPane *tabPane;
|
|
UIElement *editor;
|
|
UIElement *kerning;
|
|
UITextbox *previewText, *yAscentTextbox, *yDescentTextbox, *xEmWidthTextbox;
|
|
UIScrollBar *kerningHScroll, *kerningVScroll;
|
|
Glyph *glyphsArray;
|
|
size_t glyphCount;
|
|
intptr_t selectedGlyph = -1;
|
|
int selectedPixelX, selectedPixelY;
|
|
int selectedPairX, selectedPairY, selectedPairI, selectedPairJ;
|
|
int yAscent, yDescent, xEmWidth;
|
|
char *path;
|
|
|
|
void Save(void *cp) {
|
|
FILE *f = fopen(path, "wb");
|
|
|
|
if (f) {
|
|
BitmapFontHeader header = {
|
|
.signature = BITMAP_FONT_SIGNATURE,
|
|
.glyphCount = glyphCount,
|
|
.headerBytes = sizeof(BitmapFontHeader),
|
|
.glyphBytes = sizeof(BitmapFontGlyph),
|
|
.yAscent = yAscent,
|
|
.yDescent = yDescent,
|
|
.xEmWidth = xEmWidth,
|
|
};
|
|
|
|
fwrite(&header, 1, sizeof(header), f);
|
|
|
|
uint32_t bitsOffset = sizeof(BitmapFontHeader) + glyphCount * sizeof(BitmapFontGlyph);
|
|
|
|
for (uintptr_t i = 0; i < glyphCount; i++) {
|
|
BitmapFontGlyph glyphHeader = {
|
|
.bitsOffset = bitsOffset,
|
|
.codepoint = glyphsArray[i].number,
|
|
.xOrigin = glyphsArray[i].xOrigin - glyphsArray[i].x0,
|
|
.yOrigin = glyphsArray[i].yOrigin - glyphsArray[i].y0,
|
|
.xAdvance = glyphsArray[i].xAdvance,
|
|
.bitsWidth = glyphsArray[i].x1 - glyphsArray[i].x0 + 1,
|
|
.bitsHeight = glyphsArray[i].y1 - glyphsArray[i].y0 + 1,
|
|
.kerningEntryCount = glyphsArray[i].kerningCount,
|
|
};
|
|
|
|
fwrite(&glyphHeader, 1, sizeof(glyphHeader), f);
|
|
bitsOffset += ((glyphHeader.bitsWidth + 7) >> 3) * glyphHeader.bitsHeight + sizeof(BitmapFontKerningEntry) * glyphHeader.kerningEntryCount;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < glyphCount; i++) {
|
|
Glyph *g = &glyphsArray[i];
|
|
|
|
for (int y = g->y0; y <= g->y1; y++) {
|
|
for (int x = g->x0; x <= g->x1; x += 8) {
|
|
uint8_t b = 0;
|
|
|
|
for (int xb = 0; xb < 8; xb++) {
|
|
if (x + xb <= g->x1) {
|
|
b |= (uint8_t) g->bits[y][x + xb] << xb;
|
|
}
|
|
}
|
|
|
|
fwrite(&b, 1, 1, f);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < g->kerningCount; i++) {
|
|
BitmapFontKerningEntry entry = { 0 };
|
|
entry.rightCodepoint = g->kerningArray[i].number;
|
|
entry.xOffset = g->kerningArray[i].xOffset;
|
|
fwrite(&entry, 1, sizeof(BitmapFontKerningEntry), f);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
} else {
|
|
UIDialogShow(window, 0, "Could not save the file.\n%f%b", "OK");
|
|
}
|
|
}
|
|
|
|
void Load() {
|
|
FILE *f = fopen(path, "rb");
|
|
|
|
if (f) {
|
|
BitmapFontHeader header = { 0 };
|
|
fread(&header, 1, 8, f);
|
|
if (ferror(f)) goto end;
|
|
fseek(f, 0, SEEK_SET);
|
|
fread(&header, 1, header.headerBytes > sizeof(BitmapFontHeader) ? sizeof(BitmapFontHeader) : header.headerBytes, f);
|
|
fseek(f, header.headerBytes, SEEK_SET);
|
|
glyphCount = header.glyphCount;
|
|
glyphsArray = (Glyph *) calloc(glyphCount, sizeof(Glyph));
|
|
if (!glyphsArray) goto end;
|
|
|
|
for (uintptr_t i = 0; i < glyphCount; i++) {
|
|
BitmapFontGlyph glyphHeader;
|
|
fread(&glyphHeader, 1, sizeof(glyphHeader), f);
|
|
if (ferror(f)) goto end;
|
|
if (glyphHeader.bitsWidth >= BITS_WIDTH || glyphHeader.bitsHeight >= BITS_HEIGHT) goto end;
|
|
Glyph *g = &glyphsArray[i];
|
|
g->number = glyphHeader.codepoint;
|
|
g->x0 = BITS_WIDTH / 2 - glyphHeader.bitsWidth / 2;
|
|
g->x1 = g->x0 + glyphHeader.bitsWidth - 1;
|
|
g->y0 = BITS_HEIGHT / 2 - glyphHeader.bitsHeight / 2;
|
|
g->y1 = g->y0 + glyphHeader.bitsHeight - 1;
|
|
g->xOrigin = glyphHeader.xOrigin + g->x0;
|
|
g->yOrigin = glyphHeader.yOrigin + g->y0;
|
|
g->xAdvance = glyphHeader.xAdvance;
|
|
g->kerningCount = glyphHeader.kerningEntryCount;
|
|
g->kerningArray = (Kerning *) malloc(sizeof(Kerning) * glyphHeader.kerningEntryCount);
|
|
if (!g->kerningArray) goto end;
|
|
off_t position = ftell(f);
|
|
fseek(f, glyphHeader.bitsOffset, SEEK_SET);
|
|
if (ferror(f)) goto end;
|
|
size_t bytesPerLine = (glyphHeader.bitsWidth + 7) >> 3;
|
|
uint8_t *bits = (uint8_t *) malloc(bytesPerLine * glyphHeader.bitsHeight);
|
|
if (!bits) goto end;
|
|
fread(bits, 1, bytesPerLine * glyphHeader.bitsHeight, f);
|
|
|
|
for (int i = 0; i < glyphHeader.bitsHeight; i++) {
|
|
for (int j = 0; j < glyphHeader.bitsWidth; j++) {
|
|
g->bits[i + g->y0][j + g->x0] = (bits[i * bytesPerLine + j / 8] & (1 << (j % 8))) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < g->kerningCount; i++) {
|
|
BitmapFontKerningEntry entry;
|
|
fread(&entry, 1, sizeof(BitmapFontKerningEntry), f);
|
|
if (ferror(f)) goto end;
|
|
g->kerningArray[i].number = entry.rightCodepoint;
|
|
g->kerningArray[i].xOffset = entry.xOffset;
|
|
}
|
|
|
|
if (ferror(f)) { free(bits); goto end; }
|
|
free(bits);
|
|
fseek(f, position, SEEK_SET);
|
|
}
|
|
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%d", header.yAscent);
|
|
UITextboxReplace(yAscentTextbox, buffer, -1, true);
|
|
snprintf(buffer, sizeof(buffer), "%d", header.yDescent);
|
|
UITextboxReplace(yDescentTextbox, buffer, -1, true);
|
|
snprintf(buffer, sizeof(buffer), "%d", header.xEmWidth);
|
|
UITextboxReplace(xEmWidthTextbox, buffer, -1, true);
|
|
|
|
end:;
|
|
|
|
if (ferror(f)) {
|
|
UIDialogShow(window, 0, "Could not load the file.\n%f%b", "OK");
|
|
|
|
for (uintptr_t i = 0; i < glyphCount; i++) {
|
|
free(glyphsArray[i].kerningArray);
|
|
}
|
|
|
|
glyphCount = 0;
|
|
free(glyphsArray);
|
|
glyphsArray = NULL;
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
glyphsTable->itemCount = glyphCount;
|
|
UITableResizeColumns(glyphsTable);
|
|
UIElementRefresh(&glyphsTable->e);
|
|
}
|
|
|
|
int CompareGlyphs(const void *_a, const void *_b) {
|
|
Glyph *a = (Glyph *) _a, *b = (Glyph *) _b;
|
|
return a->number < b->number ? -1 : a->number != b->number;
|
|
}
|
|
|
|
int CompareKernings(const void *_a, const void *_b) {
|
|
Kerning *a = (Kerning *) _a, *b = (Kerning *) _b;
|
|
return a->number < b->number ? -1 : a->number != b->number;
|
|
}
|
|
|
|
void AddGlyph(void *cp) {
|
|
char *number = NULL;
|
|
UIDialogShow(window, 0, "Enter the glyph number (base 16):\n%t\n%f%b", &number, "Add");
|
|
Glyph g = { 0 };
|
|
g.number = strtol(number, NULL, 16);
|
|
free(number);
|
|
glyphsTable->itemCount = ++glyphCount;
|
|
glyphsArray = realloc(glyphsArray, sizeof(Glyph) * glyphCount);
|
|
glyphsArray[glyphCount - 1] = g;
|
|
qsort(glyphsArray, glyphCount, sizeof(Glyph), CompareGlyphs);
|
|
selectedGlyph = -1;
|
|
UITableResizeColumns(glyphsTable);
|
|
UIElementRefresh(&glyphsTable->e);
|
|
UIElementRefresh(editor);
|
|
UIElementRefresh(kerning);
|
|
}
|
|
|
|
void DeleteGlyph(void *cp) {
|
|
if (selectedGlyph == -1) return;
|
|
memmove(glyphsArray + selectedGlyph, glyphsArray + selectedGlyph + 1, sizeof(Glyph) * (glyphCount - selectedGlyph - 1));
|
|
selectedGlyph = -1;
|
|
glyphsTable->itemCount = --glyphCount;
|
|
glyphsArray = realloc(glyphsArray, sizeof(Glyph) * glyphCount);
|
|
UITableResizeColumns(glyphsTable);
|
|
UIElementRefresh(&glyphsTable->e);
|
|
UIElementRefresh(editor);
|
|
UIElementRefresh(kerning);
|
|
}
|
|
|
|
void DrawGlyph(UIPainter *painter, Glyph *g, int _x, int _y) {
|
|
for (int y = g->y0; y <= g->y1; y++) {
|
|
for (int x = g->x0; x <= g->x1; x++) {
|
|
if (g->bits[y][x]) {
|
|
UIDrawBlock(painter, UI_RECT_4(_x + x - g->xOrigin, _x + x + 1 - g->xOrigin, _y + y - g->yOrigin, _y + y + 1 - g->yOrigin), 0xFF000000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int GlyphsTableMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
UITable *table = (UITable *) element;
|
|
|
|
if (message == UI_MSG_TABLE_GET_ITEM) {
|
|
UITableGetItem *m = (UITableGetItem *) dp;
|
|
m->isSelected = selectedGlyph == m->index;
|
|
|
|
if (m->column == 0) {
|
|
if (glyphsArray[m->index].number < 256) {
|
|
return snprintf(m->buffer, m->bufferBytes, "%c", glyphsArray[m->index].number);
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else if (m->column == 1) {
|
|
return snprintf(m->buffer, m->bufferBytes, "U+%.4X", glyphsArray[m->index].number);
|
|
}
|
|
} else if (message == UI_MSG_LEFT_DOWN || message == UI_MSG_MOUSE_DRAG) {
|
|
int index = UITableHitTest(table, element->window->cursorX, element->window->cursorY);
|
|
|
|
if (index != -1) {
|
|
selectedGlyph = index;
|
|
tabPane->active = 1;
|
|
UIElementMessage(&tabPane->e, UI_MSG_LAYOUT, 0, 0);
|
|
UIElementRepaint(&tabPane->e, NULL);
|
|
}
|
|
} else if (message == UI_MSG_PAINT) {
|
|
element->messageClass(element, message, di, dp);
|
|
|
|
UIPainter *painter = (UIPainter *) dp;
|
|
UIRectangle oldClip = painter->clip;
|
|
UIRectangle bounds = element->bounds;
|
|
bounds.t += UI_SIZE_TABLE_HEADER * element->window->scale;
|
|
bounds.r -= UI_SIZE_SCROLL_BAR * element->window->scale;
|
|
painter->clip = UIRectangleIntersection(painter->clip, bounds);
|
|
UIRectangle row = bounds;
|
|
int rowHeight = UI_SIZE_TABLE_ROW * element->window->scale;
|
|
row.t -= (int64_t) table->vScroll->position % rowHeight;
|
|
|
|
for (int i = table->vScroll->position / rowHeight; i < table->itemCount; i++) {
|
|
if (row.t > element->clip.b) break;
|
|
row.b = row.t + rowHeight;
|
|
DrawGlyph(painter, &glyphsArray[i], row.r - 50, row.b - 8);
|
|
row.t += rowHeight;
|
|
}
|
|
|
|
painter->clip = oldClip;
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SetOrigin(void *cp) {
|
|
Glyph *g = &glyphsArray[selectedGlyph];
|
|
g->xOrigin = selectedPixelX;
|
|
g->yOrigin = selectedPixelY;
|
|
UIElementRepaint(editor, NULL);
|
|
}
|
|
|
|
void SetXAdvance(void *cp) {
|
|
Glyph *g = &glyphsArray[selectedGlyph];
|
|
g->xAdvance = selectedPixelX - g->xOrigin;
|
|
UIElementRepaint(editor, NULL);
|
|
}
|
|
|
|
int GetAdvance(int leftGlyph, int rightGlyph, bool *hasKerningEntry) {
|
|
if (hasKerningEntry) *hasKerningEntry = false;
|
|
int p = glyphsArray[leftGlyph].xAdvance;
|
|
|
|
if (rightGlyph != -1) {
|
|
// TODO Binary search.
|
|
|
|
for (uintptr_t i = 0; i < glyphsArray[leftGlyph].kerningCount; i++) {
|
|
if (glyphsArray[leftGlyph].kerningArray[i].number == glyphsArray[rightGlyph].number) {
|
|
p += glyphsArray[leftGlyph].kerningArray[i].xOffset;
|
|
if (hasKerningEntry) *hasKerningEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
void DrawPreviewText(UIPainter *painter, UIElement *element, Glyph *g) {
|
|
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t, element->bounds.t + 50), 0xFFFFFFFF);
|
|
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t + 25 - yAscent, element->bounds.t + 26 - yAscent), 0xFF88FF88);
|
|
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t + 25, element->bounds.t + 26), 0xFF88FF88);
|
|
UIDrawBlock(painter, UI_RECT_4(element->bounds.r - 100, element->bounds.r, element->bounds.t + 25 + yDescent, element->bounds.t + 26 + yDescent), 0xFF88FF88);
|
|
|
|
if (previewText->bytes == 0 && g) {
|
|
DrawGlyph(painter, g, element->bounds.r - 100 + 5, element->bounds.t + 25);
|
|
return;
|
|
}
|
|
|
|
int px = 0;
|
|
int previous = -1;
|
|
|
|
for (int i = 0; i < previewText->bytes; i++) {
|
|
// TODO Binary search.
|
|
|
|
for (uintptr_t j = 0; j < glyphCount; j++) {
|
|
if (glyphsArray[j].number == previewText->string[i]) {
|
|
if (previous != -1) px += GetAdvance(previous, j, NULL);
|
|
DrawGlyph(painter, &glyphsArray[j], element->bounds.r - 100 + 5 + px, element->bounds.t + 25);
|
|
previous = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int GlyphEditorMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_PAINT) {
|
|
UIPainter *painter = (UIPainter *) dp;
|
|
UIDrawBlock(painter, element->bounds, 0xD0D1D4);
|
|
|
|
if (selectedGlyph >= 0 && selectedGlyph < (intptr_t) glyphCount) {
|
|
Glyph *g = &glyphsArray[selectedGlyph];
|
|
|
|
for (int y = 0; y < BITS_HEIGHT; y++) {
|
|
for (int x = 0; x < BITS_WIDTH; x++) {
|
|
UIRectangle rectangle = UIRectangleAdd(element->bounds, UI_RECT_2(x * ZOOM, y * ZOOM));
|
|
rectangle.r = rectangle.l + ZOOM, rectangle.b = rectangle.t + ZOOM;
|
|
|
|
if (g->bits[y][x]) {
|
|
UIDrawBlock(painter, rectangle, 0xFF000000);
|
|
} else {
|
|
UIDrawBorder(painter, rectangle, 0xFF000000, UI_RECT_4(1, 0, 1, 0));
|
|
}
|
|
|
|
if (g->xOrigin == x && g->yOrigin == y) {
|
|
UIDrawBorder(painter, rectangle, 0xFFFF0000, UI_RECT_1(2));
|
|
}
|
|
|
|
if (g->xOrigin + g->xAdvance == x && g->yOrigin == y) {
|
|
UIDrawBorder(painter, rectangle, 0xFF0099FF, UI_RECT_1(2));
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawPreviewText(painter, element, g);
|
|
}
|
|
} else if (message == UI_MSG_MIDDLE_UP) {
|
|
if (selectedGlyph >= 0 && selectedGlyph < (intptr_t) glyphCount) {
|
|
int mx = (window->cursorX - element->bounds.l) / ZOOM;
|
|
int my = (window->cursorY - element->bounds.t) / ZOOM;
|
|
|
|
if (mx >= 0 && my >= 0 && mx < BITS_WIDTH && my < BITS_HEIGHT) {
|
|
selectedPixelX = mx;
|
|
selectedPixelY = my;
|
|
UIMenu *menu = UIMenuCreate(&window->e, UI_MENU_NO_SCROLL);
|
|
UIMenuAddItem(menu, 0, "Set origin", -1, SetOrigin, element);
|
|
UIMenuAddItem(menu, 0, "Set X advance", -1, SetXAdvance, element);
|
|
UIMenuShow(menu);
|
|
}
|
|
}
|
|
} else if (message == UI_MSG_MOUSE_DRAG || message == UI_MSG_LEFT_DOWN || message == UI_MSG_RIGHT_DOWN) {
|
|
if (selectedGlyph >= 0 && selectedGlyph < (intptr_t) glyphCount && (window->pressedButton == 1 || window->pressedButton == 3)) {
|
|
Glyph *g = &glyphsArray[selectedGlyph];
|
|
int mx = (window->cursorX - element->bounds.l) / ZOOM;
|
|
int my = (window->cursorY - element->bounds.t) / ZOOM;
|
|
|
|
if (mx >= 0 && my >= 0 && mx < BITS_WIDTH && my < BITS_HEIGHT) {
|
|
g->bits[my][mx] = window->pressedButton == 1;
|
|
|
|
g->x0 = 0;
|
|
g->x1 = 0;
|
|
g->y0 = 0;
|
|
g->y1 = 0;
|
|
|
|
bool first = true;
|
|
|
|
for (int y = 0; y < BITS_HEIGHT; y++) {
|
|
for (int x = 0; x < BITS_WIDTH; x++) {
|
|
if (g->bits[y][x]) {
|
|
if (first) {
|
|
g->x0 = x;
|
|
g->x1 = x;
|
|
g->y0 = y;
|
|
g->y1 = y;
|
|
first = false;
|
|
} else {
|
|
if (x < g->x0) g->x0 = x;
|
|
if (x > g->x1) g->x1 = x;
|
|
if (y < g->y0) g->y0 = y;
|
|
if (y > g->y1) g->y1 = y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UIElementRepaint(element, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int KerningEditorMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_PAINT) {
|
|
UIPainter *painter = (UIPainter *) dp;
|
|
UIDrawBlock(painter, element->bounds, 0xD0D1D4);
|
|
|
|
int x = element->bounds.l + 20 - kerningHScroll->position, y = element->bounds.t + 20 - kerningVScroll->position;
|
|
|
|
selectedPairI = -1, selectedPairJ = -1;
|
|
|
|
for (uintptr_t i = 0; i < glyphCount; i++) {
|
|
for (uintptr_t j = 0; j < glyphCount; j++) {
|
|
bool hasKerningEntry = false;
|
|
|
|
DrawGlyph(painter, &glyphsArray[j], x, y);
|
|
DrawGlyph(painter, &glyphsArray[i], x + GetAdvance(j, i, &hasKerningEntry), y);
|
|
|
|
UIRectangle border = UI_RECT_4(x - 5, x + 20, y - 15, y + 5);
|
|
|
|
if (hasKerningEntry) {
|
|
UIDrawBorder(painter, border, 0xFF0099FF, UI_RECT_1(2));
|
|
}
|
|
|
|
if (selectedPairX == (x - 20 - element->bounds.l) / 25 && selectedPairY == (y - 20 - element->bounds.t) / 20) {
|
|
UIDrawBorder(painter, border, 0xFF000000, UI_RECT_1(1));
|
|
selectedPairI = i, selectedPairJ = j;
|
|
}
|
|
|
|
x += 25;
|
|
}
|
|
|
|
x = element->bounds.l + 20 - kerningHScroll->position;
|
|
y += 20;
|
|
}
|
|
|
|
DrawPreviewText(painter, element, NULL);
|
|
} else if (message == UI_MSG_LAYOUT) {
|
|
{
|
|
kerningHScroll->maximum = 25 * glyphCount + 40;
|
|
kerningHScroll->page = UI_RECT_WIDTH(element->bounds);
|
|
UIRectangle scrollBarBounds = element->bounds;
|
|
scrollBarBounds.r = scrollBarBounds.r - UI_SIZE_SCROLL_BAR * element->window->scale;
|
|
scrollBarBounds.t = scrollBarBounds.b - UI_SIZE_SCROLL_BAR * element->window->scale;
|
|
UIElementMove(&kerningHScroll->e, scrollBarBounds, true);
|
|
}
|
|
|
|
{
|
|
kerningVScroll->maximum = 20 * glyphCount + 40;
|
|
kerningVScroll->page = UI_RECT_HEIGHT(element->bounds);
|
|
UIRectangle scrollBarBounds = element->bounds;
|
|
scrollBarBounds.b = scrollBarBounds.b - UI_SIZE_SCROLL_BAR * element->window->scale;
|
|
scrollBarBounds.l = scrollBarBounds.r - UI_SIZE_SCROLL_BAR * element->window->scale;
|
|
UIElementMove(&kerningVScroll->e, scrollBarBounds, true);
|
|
}
|
|
} else if (message == UI_MSG_LEFT_DOWN || message == UI_MSG_RIGHT_DOWN) {
|
|
int delta = message == UI_MSG_LEFT_DOWN ? 1 : -1;
|
|
|
|
if (selectedPairI >= 0 && selectedPairI < (int) glyphCount && selectedPairJ >= 0 && selectedPairJ < (int) glyphCount) {
|
|
int j = selectedPairJ;
|
|
bool found = false;
|
|
|
|
for (uintptr_t i = 0; i < glyphsArray[j].kerningCount; i++) {
|
|
if (glyphsArray[j].kerningArray[i].number == glyphsArray[selectedPairI].number) {
|
|
glyphsArray[j].kerningArray[i].xOffset += delta;
|
|
|
|
if (!glyphsArray[j].kerningArray[i].xOffset) {
|
|
memmove(glyphsArray[j].kerningArray + i, glyphsArray[j].kerningArray + i + 1,
|
|
sizeof(Kerning) * (glyphsArray[j].kerningCount - i - 1));
|
|
glyphsArray[j].kerningCount--;
|
|
}
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
glyphsArray[j].kerningCount++;
|
|
glyphsArray[j].kerningArray = (Kerning *) realloc(glyphsArray[j].kerningArray, sizeof(Kerning) * glyphsArray[j].kerningCount);
|
|
glyphsArray[j].kerningArray[glyphsArray[j].kerningCount - 1].number = glyphsArray[selectedPairI].number;
|
|
glyphsArray[j].kerningArray[glyphsArray[j].kerningCount - 1].xOffset = delta;
|
|
qsort(glyphsArray[j].kerningArray, glyphsArray[j].kerningCount, sizeof(Kerning), CompareKernings);
|
|
}
|
|
|
|
UIElementRepaint(element, NULL);
|
|
}
|
|
} else if (message == UI_MSG_MOUSE_MOVE) {
|
|
int pairX = (window->cursorX - element->bounds.l - 20 + 5) / 25;
|
|
int pairY = (window->cursorY - element->bounds.t - 20 + 15) / 20;
|
|
|
|
if (pairX != selectedPairX || pairY != selectedPairY) {
|
|
selectedPairX = pairX;
|
|
selectedPairY = pairY;
|
|
UIElementRepaint(element, NULL);
|
|
}
|
|
} else if (message == UI_MSG_SCROLLED) {
|
|
UIElementRepaint(element, NULL);
|
|
} else if (message == UI_MSG_MOUSE_WHEEL) {
|
|
return UIElementMessage(&kerningVScroll->e, message, di, dp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int PreviewTextMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
UIElementRepaint(editor, NULL);
|
|
UIElementRepaint(kerning, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int NumberTextboxMessage(UIElement *element, UIMessage message, int di, void *dp) {
|
|
if (message == UI_MSG_VALUE_CHANGED) {
|
|
UITextbox *textbox = (UITextbox *) element;
|
|
char buffer[32];
|
|
snprintf(buffer, sizeof(buffer), "%.*s", (int) textbox->bytes, textbox->string);
|
|
*(int *) element->cp = atoi(buffer);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s <path to font file>\n", argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
path = argv[1];
|
|
|
|
UIInitialise();
|
|
ui.theme = _uiThemeClassic;
|
|
window = UIWindowCreate(0, UI_ELEMENT_PARENT_PUSH, "Font Editor", 1024, 768);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
|
|
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_GRAY | UI_PANEL_HORIZONTAL | UI_PANEL_MEDIUM_SPACING);
|
|
UIButtonCreate(0, 0, "Save", -1)->invoke = Save;
|
|
UIButtonCreate(0, 0, "Add glyph", -1)->invoke = AddGlyph;
|
|
UIButtonCreate(0, 0, "Delete glyph", -1)->invoke = DeleteGlyph;
|
|
UISpacerCreate(0, UI_ELEMENT_H_FILL, 0, 0);
|
|
UILabelCreate(0, 0, "Preview text:", -1);
|
|
previewText = UITextboxCreate(0, 0);
|
|
previewText->e.messageUser = PreviewTextMessage;
|
|
UIParentPop();
|
|
|
|
tabPane = UITabPaneCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_V_FILL, "Glyphs\tEdit\tKerning\tGeneral");
|
|
glyphsTable = UITableCreate(0, 0, "ASCII\tNumber");
|
|
glyphsTable->e.messageUser = GlyphsTableMessage;
|
|
editor = UIElementCreate(sizeof(UIElement), 0, 0, GlyphEditorMessage, "Glyph editor");
|
|
kerning = UIElementCreate(sizeof(UIElement), 0, 0, KerningEditorMessage, "Kerning editor");
|
|
kerningHScroll = UIScrollBarCreate(kerning, UI_SCROLL_BAR_HORIZONTAL);
|
|
kerningVScroll = UIScrollBarCreate(kerning, 0);
|
|
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_PANEL_SCROLL);
|
|
UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND | UI_PANEL_MEDIUM_SPACING);
|
|
UILabelCreate(0, 0, "Y ascent:", -1);
|
|
yAscentTextbox = UITextboxCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0);
|
|
yAscentTextbox->e.cp = &yAscent;
|
|
yAscentTextbox->e.messageUser = NumberTextboxMessage;
|
|
UILabelCreate(0, 0, "Y descent:", -1);
|
|
yDescentTextbox = UITextboxCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0);
|
|
yDescentTextbox->e.cp = &yDescent;
|
|
yDescentTextbox->e.messageUser = NumberTextboxMessage;
|
|
UILabelCreate(0, 0, "The sum of the ascent and descent determine the line height.", -1);
|
|
|
|
UILabelCreate(0, 0, "X em width:", -1);
|
|
xEmWidthTextbox = UITextboxCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0);
|
|
xEmWidthTextbox->e.cp = &xEmWidth;
|
|
xEmWidthTextbox->e.messageUser = NumberTextboxMessage;
|
|
UIParentPop();
|
|
UIParentPop();
|
|
|
|
UIWindowRegisterShortcut(window, (UIShortcut) { .code = UI_KEYCODE_LETTER('S'), .ctrl = true, .invoke = Save });
|
|
|
|
Load();
|
|
|
|
return UIMessageLoop();
|
|
}
|