Last active
August 29, 2015 14:07
-
-
Save Midiman/358d09052630d418e6ea to your computer and use it in GitHub Desktop.
Naive Pango Font Renderer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <string> | |
#include <stdexcept> | |
#include <clocale> | |
#include <cstring> | |
#include <cwchar> | |
#include <cmath> | |
#include <cstdio> | |
#include <cairo.h> | |
#define SHOW_DEBUG 0 | |
using namespace std; | |
string map_ascii = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; | |
static const int surface_width = 2048; | |
static const int surface_height = 2048; | |
static const int rows = 15; | |
static const int columns = 15; | |
static const char* font_name = "Roboto"; | |
static const float font_size = 24; | |
static const float font_padding = 2; | |
static const float outline_size = 0; | |
static const float scale = 1; | |
int main(int argc, char* argv[]) { | |
std::setlocale(LC_ALL, ""); | |
cairo_surface_t *surface; | |
cairo_t *cr; | |
vector<float> glyph_widths; | |
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, surface_width * scale, surface_height * scale); | |
cr = cairo_create(surface); | |
cairo_select_font_face(cr, | |
font_name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); | |
cairo_set_font_size(cr, | |
font_size); | |
cairo_font_extents_t fe; | |
cairo_font_extents(cr, &fe); | |
cairo_text_extents_t te; | |
cairo_scale(cr, scale, scale); | |
float cell_width = floor(fe.max_x_advance + outline_size); | |
float cell_height = floor(fe.height + outline_size + (font_padding * 2)); | |
const char* ptr = map_ascii.data(); | |
const char* end = ptr + map_ascii.size(); | |
size_t i = 0; | |
std::mblen(NULL, 0); // reset the conversion state | |
while ( ptr < end ) { | |
int next = mblen(ptr, end - ptr); | |
char* this_str = new char[1024]; | |
strcpy(this_str, ptr); | |
char* this_char = new char[next]; | |
strncpy(this_char, this_str, next); | |
this_char[next] = '\0'; | |
printf("i: %d, next: %d str: %s\n", i, next, this_char); | |
if ( next == -1 ) { | |
throw std::runtime_error("strlen_mb(): conversion error"); | |
} | |
cairo_text_extents(cr, this_char, &te); | |
float x_base = ( i % columns ) * cell_width; | |
float y_base = ((i / columns ) % rows) * cell_height; | |
float x_offset = (cell_width * 0.5) - te.x_bearing - te.width / 2; | |
float y_offset = (cell_height * 0.5) - fe.descent + fe.height / 2; | |
float x_pos = x_base + x_offset; | |
float y_pos = y_base + y_offset; | |
glyph_widths.push_back( ((te.x_advance - te.x_bearing*2) + font_padding) * scale ); | |
if ( SHOW_DEBUG >= 1 ) { | |
/* Maximum Height | |
cairo_rectangle(cr, x_base, y_base + cell_height/2 - fe.height/2, cell_width, fe.height); | |
cairo_set_source_rgba(cr,0,0,0,0.25); | |
cairo_fill(cr); | |
*/ | |
if ( SHOW_DEBUG >=2 ) { | |
// Glyph Region | |
cairo_rectangle(cr, x_pos + te.x_bearing - outline_size, y_pos + te.y_bearing - outline_size, te.width + outline_size * 2, te.height + outline_size * 2 ); | |
cairo_set_source_rgba(cr,1,1,0,1); | |
cairo_fill(cr); | |
cairo_rectangle(cr, x_pos + te.x_bearing, y_pos + te.y_bearing, te.width, te.height ); | |
cairo_set_source_rgba(cr,0.5,0.5,1,1); | |
cairo_fill(cr); | |
if ( SHOW_DEBUG >=3 ) { | |
cairo_rectangle(cr, x_pos + te.x_bearing, y_pos + te.y_bearing, te.x_bearing, te.height); | |
cairo_set_source_rgba(cr,0.8,0.25,0.8,1); | |
cairo_fill(cr); | |
cairo_rectangle(cr, x_pos , y_pos + te.y_bearing, te.x_advance, te.height); | |
cairo_set_source_rgba(cr,1,1,0.75,1); | |
cairo_fill(cr); | |
} | |
} | |
// Right Border | |
cairo_rectangle(cr, x_base + cell_width, y_base, 1, cell_height); | |
cairo_set_source_rgba(cr,0,1,1,1); | |
cairo_fill(cr); | |
// Bottom Border | |
cairo_rectangle(cr, x_base, y_base + cell_height, cell_width, 1); | |
cairo_set_source_rgba(cr,1,0,1,1); | |
cairo_fill(cr); | |
if ( SHOW_DEBUG >=2 ) { | |
// Vertical Center | |
cairo_rectangle(cr, x_base, y_base + cell_height/2, cell_width, 1); | |
cairo_set_source_rgba(cr,1,1,1,0.5); | |
cairo_fill(cr); | |
// Horizontal Center | |
cairo_rectangle(cr, x_base + cell_width/2, y_base, 1, cell_height); | |
cairo_set_source_rgba(cr,1,1,1,0.5); | |
cairo_fill(cr); | |
} | |
// Descent | |
cairo_rectangle(cr, x_base, y_base + fe.descent, cell_width, 1); | |
cairo_set_source_rgba(cr,0,1,0,1); | |
cairo_fill(cr); | |
// Ascent | |
cairo_rectangle(cr, x_base, y_base + fe.ascent, cell_width, 1); | |
cairo_set_source_rgba(cr,1,0,0,1); | |
cairo_fill(cr); | |
} | |
// Outline | |
cairo_move_to(cr, x_pos, y_pos); | |
cairo_set_source_rgba(cr,0,0,0,1); | |
cairo_text_path(cr, this_char); | |
cairo_set_line_width(cr, outline_size); | |
cairo_stroke_preserve(cr); | |
// Fill | |
cairo_set_source_rgba(cr,1,1,1,1); | |
cairo_fill(cr); | |
ptr += next; | |
++i; | |
}; | |
cairo_surface_t *surface_final; | |
surface_final = cairo_surface_create_for_rectangle(surface, 0, 0, (cell_width * columns) * scale, (cell_height * rows) * scale); | |
char filename[1024]; | |
*filename = '\0'; | |
sprintf( filename, "output %dx%d.png", columns, rows); | |
cairo_surface_write_to_png(surface_final, filename); | |
cairo_surface_finish(surface_final); | |
cairo_surface_destroy(surface_final); | |
cairo_surface_finish(surface); | |
cairo_surface_destroy(surface); | |
// | |
FILE* metrics; | |
metrics = fopen("output.ini", "w+"); | |
fprintf(metrics, "[common]\n"); | |
fprintf(metrics, "CapitalsOnly=%d\n", 0); | |
fprintf(metrics, "RightToLeft=%d\n", 0); | |
fprintf(metrics, "DefaultStrokeColor=%s\n", "#00000000"); | |
fprintf(metrics, "\n"); | |
fprintf(metrics, "LineSpacing=%d\n", (int)fe.height + (int)(font_padding) + (int)outline_size); | |
fprintf(metrics, "Top=%d\n", (int)fe.descent); | |
fprintf(metrics, "Baseline=%d\n", (int)fe.ascent); | |
fprintf(metrics, "DrawExtraPixelsLeft=%d\n", (int)outline_size/2); | |
fprintf(metrics, "DrawExtraPixelsRight=%d\n", (int)outline_size/2); | |
fprintf(metrics, "\n"); | |
for ( size_t i = 0; i < rows; i++ ) { | |
char this_line[columns+1]; | |
*this_line = '\0'; | |
for ( size_t j = 0; j < columns; j++ ) { | |
size_t index = (i * columns) + j; | |
strncat(this_line, &map_ascii[index], 1); | |
} | |
fprintf(metrics, "Line %2lu=%s\n", i, this_line); | |
} | |
fprintf(metrics, "\n"); | |
for ( size_t l = 0; l < glyph_widths.size(); l++ ) { | |
fprintf(metrics, "%lu=%d\n", l, (int)glyph_widths[l]); | |
} | |
fclose(metrics); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#### PROJECT SETTINGS #### | |
# The name of the executable to be created | |
BIN_NAME := font | |
# Compiler used | |
CXX ?= g++ | |
# Extension of source files used in the project | |
SRC_EXT = cpp | |
# Path to the source directory, relative to the makefile | |
SRC_PATH = . | |
# General compiler flags | |
COMPILE_FLAGS = -std=c++11 -Wall -Wextra -g | |
# Additional release-specific flags | |
RCOMPILE_FLAGS = -D NDEBUG | |
# Additional debug-specific flags | |
DCOMPILE_FLAGS = -D DEBUG | |
# Add additional include paths | |
INCLUDES = -I $(SRC_PATH)/ -I/usr/include/cairo -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/pixman-1 -I/usr/include/freetype2 -I/usr/include/libpng12 | |
# General linker settings | |
LINK_FLAGS = -lcairo | |
# Additional release-specific linker settings | |
RLINK_FLAGS = | |
# Additional debug-specific linker settings | |
DLINK_FLAGS = | |
# Destination directory, like a jail or mounted system | |
DESTDIR = / | |
# Install path (bin/ is appended automatically) | |
INSTALL_PREFIX = usr/local | |
#### END PROJECT SETTINGS #### | |
# Generally should not need to edit below this line | |
# Shell used in this makefile | |
# bash is used for 'echo -en' | |
SHELL = /bin/bash | |
# Clear built-in rules | |
.SUFFIXES: | |
# Programs for installation | |
INSTALL = install | |
INSTALL_PROGRAM = $(INSTALL) | |
INSTALL_DATA = $(INSTALL) -m 644 | |
# Verbose option, to output compile and link commands | |
export V = false | |
export CMD_PREFIX = @ | |
ifeq ($(V),true) | |
CMD_PREFIX = | |
endif | |
# Combine compiler and linker flags | |
release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) $(RCOMPILE_FLAGS) | |
release: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(RLINK_FLAGS) | |
debug: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) $(DCOMPILE_FLAGS) | |
debug: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(DLINK_FLAGS) | |
# Build and output paths | |
release: export BUILD_PATH := build/release | |
release: export BIN_PATH := bin/release | |
debug: export BUILD_PATH := build/debug | |
debug: export BIN_PATH := bin/debug | |
install: export BIN_PATH := bin/release | |
# Find all source files in the source directory | |
SOURCES = $(shell find $(SRC_PATH)/ -name '*.$(SRC_EXT)') | |
# Set the object file names, with the source directory stripped | |
# from the path, and the build path prepended in its place | |
OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o) | |
# Set the dependency files that will be used to add header dependencies | |
DEPS = $(OBJECTS:.o=.d) | |
# Macros for timing compilation | |
TIME_FILE = $(dir $@).$(notdir $@)_time | |
START_TIME = date '+%s' > $(TIME_FILE) | |
END_TIME = read st < $(TIME_FILE) ; \ | |
$(RM) $(TIME_FILE) ; \ | |
st=$$((`date '+%s'` - $$st - 86400)) ; \ | |
echo `date -u -d @$$st '+%H:%M:%S'` | |
# Version macros | |
# Comment/remove this section to remove versioning | |
VERSION := $(shell git describe --tags --long --dirty --always | \ | |
sed 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)-\?.*-\([0-9]*\)-\(.*\)/\1 \2 \3 \4 \5/g') | |
VERSION_MAJOR := $(word 1, $(VERSION)) | |
VERSION_MINOR := $(word 2, $(VERSION)) | |
VERSION_PATCH := $(word 3, $(VERSION)) | |
VERSION_REVISION := $(word 4, $(VERSION)) | |
VERSION_HASH := $(word 5, $(VERSION)) | |
VERSION_STRING := \ | |
"$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH).$(VERSION_REVISION)" | |
override CXXFLAGS := $(CXXFLAGS) \ | |
-D VERSION_MAJOR=$(VERSION_MAJOR) \ | |
-D VERSION_MINOR=$(VERSION_MINOR) \ | |
-D VERSION_PATCH=$(VERSION_PATCH) \ | |
-D VERSION_REVISION=$(VERSION_REVISION) \ | |
-D VERSION_HASH=\"$(VERSION_HASH)\" | |
# Standard, non-optimized release build | |
.PHONY: release | |
release: dirs | |
@echo "Beginning release build v$(VERSION_STRING)" | |
@$(START_TIME) | |
@$(MAKE) all --no-print-directory | |
@echo -n "Total build time: " | |
@$(END_TIME) | |
# Debug build for gdb debugging | |
.PHONY: debug | |
debug: dirs | |
@echo "Beginning debug build v$(VERSION_STRING)" | |
@$(START_TIME) | |
@$(MAKE) all --no-print-directory | |
@echo -n "Total build time: " | |
@$(END_TIME) | |
# Create the directories used in the build | |
.PHONY: dirs | |
dirs: | |
@echo "Creating directories" | |
@mkdir -p $(dir $(OBJECTS)) | |
@mkdir -p $(BIN_PATH) | |
# Installs to the set path | |
.PHONY: install | |
install: | |
@echo "Installing to $(DESTDIR)$(INSTALL_PREFIX)/bin" | |
@$(INSTALL_PROGRAM) $(BIN_PATH)/$(BIN_NAME) $(DESTDIR)$(INSTALL_PREFIX)/bin | |
# Uninstalls the program | |
.PHONY: uninstall | |
uninstall: | |
@echo "Removing $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME)" | |
@$(RM) $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME) | |
# Removes all build files | |
.PHONY: clean | |
clean: | |
@echo "Deleting $(BIN_NAME) symlink" | |
@$(RM) $(BIN_NAME) | |
@echo "Deleting directories" | |
@$(RM) -r build | |
@$(RM) -r bin | |
# Main rule, checks the executable and symlinks to the output | |
all: $(BIN_PATH)/$(BIN_NAME) | |
@echo "Making symlink: $(BIN_NAME) -> $<" | |
@$(RM) $(BIN_NAME) | |
@ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME) | |
# Link the executable | |
$(BIN_PATH)/$(BIN_NAME): $(OBJECTS) | |
@echo "Linking: $@" | |
@$(START_TIME) | |
$(CMD_PREFIX)$(CXX) $(OBJECTS) $(LDFLAGS) -o $@ | |
@echo -en "\t Link time: " | |
@$(END_TIME) | |
# Add dependency files, if they exist | |
-include $(DEPS) | |
# Source file rules | |
# After the first compilation they will be joined with the rules from the | |
# dependency files to provide header dependencies | |
$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT) | |
@echo "Compiling: $< -> $@" | |
@$(START_TIME) | |
$(CMD_PREFIX)$(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@ | |
@echo -en "\t Compile time: " | |
@$(END_TIME) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment