Skip to content

Instantly share code, notes, and snippets.

@Midiman
Last active August 29, 2015 14:07
Show Gist options
  • Save Midiman/358d09052630d418e6ea to your computer and use it in GitHub Desktop.
Save Midiman/358d09052630d418e6ea to your computer and use it in GitHub Desktop.
Naive Pango Font Renderer
#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;
}
#### 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