initial font layout/font renderer module
This commit is contained in:
parent
7d2b80374a
commit
a0148e76e0
165
font.js
Normal file
165
font.js
Normal file
@ -0,0 +1,165 @@
|
||||
import { getPath } from "./common.js";
|
||||
|
||||
class FontRenderer {
|
||||
createBuffers(device)
|
||||
{
|
||||
this.maxGlyphs = 1024;
|
||||
this.glyphBufferSize = 4 * this.maxGlyphs;
|
||||
|
||||
this.frames = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const glyphBuffer = device.createBuffer({
|
||||
label: `glyph buffer ${i}`,
|
||||
size: this.glyphBufferSize,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
const bindGroup = device.createBindGroup({
|
||||
label: `font bind group ${i}`,
|
||||
layout: this.bindGroupLayout.glyph,
|
||||
entries: [{
|
||||
binding: 0,
|
||||
resource: { buffer: glyphBuffer },
|
||||
}]
|
||||
});
|
||||
|
||||
this.frames.push({
|
||||
glyphBuffer: glyphBuffer,
|
||||
bindGroup: bindGroup,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(device, canvasFormat, viewUniformBuffer, wgsl, module)
|
||||
{
|
||||
const label = "font";
|
||||
|
||||
this.module = module;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// shader module
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
const shaderModule = device.createShaderModule({
|
||||
label: `${label} shader`,
|
||||
code: wgsl,
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// index buffer
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
const array = new Uint16Array(6);
|
||||
array[0] = 0;
|
||||
array[1] = 1;
|
||||
array[2] = 2;
|
||||
array[3] = 0;
|
||||
array[4] = 3;
|
||||
array[5] = 1;
|
||||
|
||||
this.buffer = device.createBuffer({
|
||||
label: `${label} renderer buffer`,
|
||||
size: array.byteLength,
|
||||
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
device.queue.writeBuffer(this.buffer, 0, array, 0, array.length);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// pipeline
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
this.bindGroupLayout = {
|
||||
view: device.createBindGroupLayout({
|
||||
label: `${label} bind group layout view`,
|
||||
entries: [{
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.VERTEX,
|
||||
buffer: { type: "uniform" }
|
||||
}]
|
||||
}),
|
||||
glyph: device.createBindGroupLayout({
|
||||
label: `${label} bind group layout glyph`,
|
||||
entries: [{ // glyphs
|
||||
binding: 0,
|
||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
||||
buffer: { type: "read-only-storage" }
|
||||
}/*, { // configuration
|
||||
binding: 1,
|
||||
visibility: GPUShaderStage.FRAGMENT,
|
||||
buffer: { type: "uniform" }
|
||||
}*/]
|
||||
}),
|
||||
};
|
||||
|
||||
const pipelineLayout = device.createPipelineLayout({
|
||||
label: `${label} pipeline layout`,
|
||||
bindGroupLayouts: [
|
||||
this.bindGroupLayout.view,
|
||||
this.bindGroupLayout.glyph
|
||||
],
|
||||
});
|
||||
|
||||
this.renderPipeline = device.createRenderPipeline({
|
||||
label: `${label} pipeline`,
|
||||
layout: pipelineLayout,
|
||||
vertex: {
|
||||
module: shaderModule,
|
||||
entryPoint: "vertexMain",
|
||||
},
|
||||
fragment: {
|
||||
module: shaderModule,
|
||||
entryPoint: "fragmentMain",
|
||||
targets: [{
|
||||
format: canvasFormat,
|
||||
}]
|
||||
},
|
||||
primitive: {
|
||||
topology: "triangle-list",
|
||||
},
|
||||
depthStencil: {
|
||||
depthWriteEnabled: true,
|
||||
depthCompare: "less",
|
||||
format: "depth24plus",
|
||||
},
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// buffers
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
this.createBuffers(device);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// bind group
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
this.viewBindGroup = device.createBindGroup({
|
||||
label: `${label} bind group`,
|
||||
layout: this.bindGroupLayout.view,
|
||||
entries: [{
|
||||
binding: 0,
|
||||
resource: { buffer: viewUniformBuffer },
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
render(renderPass, frameNumber)
|
||||
{
|
||||
renderPass.setPipeline(this.renderPipeline);
|
||||
renderPass.setIndexBuffer(this.buffer, "uint16");
|
||||
renderPass.setBindGroup(0, this.viewBindGroup);
|
||||
renderPass.setBindGroup(1, this.frames[frameNumber].bindGroup);
|
||||
renderPass.drawIndexed(6);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFont(device, canvasFormat, viewUniformBuffer, module)
|
||||
{
|
||||
const fontWgsl = await getPath("font.wgsl");
|
||||
|
||||
const renderer = new FontRenderer(device, canvasFormat, viewUniformBuffer, fontWgsl, module);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
export { loadFont };
|
||||
52
font.wgsl
Normal file
52
font.wgsl
Normal file
@ -0,0 +1,52 @@
|
||||
struct Configuration {
|
||||
viewProj: mat4x4f,
|
||||
lightViewProj: mat4x4f,
|
||||
lightPosition: vec4f,
|
||||
eyePosition: vec4f,
|
||||
aspect: f32,
|
||||
};
|
||||
|
||||
struct GlyphBitmap {
|
||||
position: vec2f,
|
||||
velocity: vec2f,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> config: Configuration;
|
||||
@group(1) @binding(0) var<storage> glyphs: array<GlyphBitmap>;
|
||||
|
||||
struct VertexInput {
|
||||
@builtin(vertex_index) vertex_index: u32,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texture: vec2f,
|
||||
};
|
||||
|
||||
const vertices = array(
|
||||
vec2f(1.0, 0.0),
|
||||
vec2f(0.0, 1.0),
|
||||
vec2f(0.0, 0.0),
|
||||
vec2f(1.0, 1.0),
|
||||
);
|
||||
|
||||
@vertex
|
||||
fn vertexMain(input: VertexInput) -> VertexOutput
|
||||
{
|
||||
let texture = vertices[input.vertex_index];
|
||||
|
||||
let position = texture * 2.0 - 1.0;
|
||||
|
||||
var output: VertexOutput;
|
||||
output.position = vec4f(position, 0.0, 1.0);
|
||||
output.texture = position * vec2f(config.aspect, 1);
|
||||
return output;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f
|
||||
{
|
||||
var color = vec3f(input.texture.xy, 0);
|
||||
|
||||
return vec4f(color, 1);
|
||||
}
|
||||
51
include/font.h
Normal file
51
include/font.h
Normal file
@ -0,0 +1,51 @@
|
||||
// this file is designed to be platform-agnostic
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// metrics are 26.6 fixed point
|
||||
struct glyph_metrics {
|
||||
int32_t horiBearingX;
|
||||
int32_t horiBearingY;
|
||||
int32_t horiAdvance;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
static_assert((sizeof (struct glyph_metrics)) == ((sizeof (int32_t)) * 3));
|
||||
|
||||
struct glyph_bitmap {
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
static_assert((sizeof (struct glyph_bitmap)) == ((sizeof (uint16_t)) * 4));
|
||||
|
||||
struct glyph {
|
||||
struct glyph_bitmap bitmap;
|
||||
struct glyph_metrics metrics;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
static_assert((sizeof (struct glyph)) == ((sizeof (struct glyph_bitmap)) + (sizeof (struct glyph_metrics))));
|
||||
|
||||
struct font {
|
||||
uint32_t first_char_code;
|
||||
uint32_t last_char_code;
|
||||
struct face_metrics {
|
||||
int32_t height; // 26.6 fixed point
|
||||
int32_t max_advance; // 26.6 fixed point
|
||||
} face_metrics;
|
||||
uint32_t glyph_count;
|
||||
uint16_t texture_width;
|
||||
uint16_t texture_height;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
static_assert((sizeof (struct font)) == ((sizeof (uint32_t)) * 6));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
47
src/font_layout/api.cpp
Normal file
47
src/font_layout/api.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "new.h"
|
||||
#include "font_layout.h"
|
||||
|
||||
extern "C" {
|
||||
FontLayout * font_layout__create(size_t buffer, int layout_buffer_length)
|
||||
{
|
||||
FontLayout * font_layout = New<FontLayout>();
|
||||
font_layout->init(buffer, layout_buffer_length);
|
||||
return font_layout;
|
||||
}
|
||||
|
||||
size_t font_layout__glyph_buffer_size(FontLayout * font_layout)
|
||||
{
|
||||
return font_layout->font->glyph_count * (sizeof (GlyphBuffer));
|
||||
}
|
||||
|
||||
void * font_layout__glyph_buffer_address(FontLayout * font_layout)
|
||||
{
|
||||
return font_layout->glyph_buffer;
|
||||
}
|
||||
|
||||
size_t font_layout__texture_size(FontLayout * font_layout)
|
||||
{
|
||||
int const bytes_per_pixel = 1;
|
||||
return font_layout->font->texture_width * font_layout->font->texture_width * bytes_per_pixel;
|
||||
}
|
||||
|
||||
void * font_layout__texture_address(FontLayout * font_layout)
|
||||
{
|
||||
return font_layout->texture;
|
||||
}
|
||||
|
||||
size_t font_layout__layout_buffer_size(FontLayout * font_layout)
|
||||
{
|
||||
return font_layout->layout_buffer_length * (sizeof (LayoutBuffer));
|
||||
}
|
||||
|
||||
void * font_layout__layout_buffer_address(FontLayout * font_layout)
|
||||
{
|
||||
return font_layout->layout_buffer;
|
||||
}
|
||||
|
||||
int font_layout__layout_buffer_index(FontLayout * font_layout)
|
||||
{
|
||||
return font_layout->layout_buffer_index;
|
||||
}
|
||||
};
|
||||
28
src/font_layout/font_layout.cpp
Normal file
28
src/font_layout/font_layout.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "new.h"
|
||||
#include "font_layout.h"
|
||||
|
||||
static inline size_t texture_offset(font * font)
|
||||
{
|
||||
return (sizeof (font)) * (sizeof (glyph)) * font->glyph_count;
|
||||
}
|
||||
|
||||
void FontLayout::init(size_t buffer, int layout_buffer_length)
|
||||
{
|
||||
this->font = reinterpret_cast<struct font *>(buffer + 0);
|
||||
this->glyphs = reinterpret_cast<struct glyph *>(buffer + (sizeof (font)));
|
||||
|
||||
this->texture = reinterpret_cast<void *>(buffer + texture_offset(this->font));
|
||||
|
||||
this->layout_buffer_length = layout_buffer_length;
|
||||
this->layout_buffer_index = 0;
|
||||
this->layout_buffer = New<LayoutBuffer>(layout_buffer_length);
|
||||
|
||||
this->glyph_buffer = New<GlyphBuffer>(this->font->glyph_count);
|
||||
for (uint16_t i = 0; i < this->font->glyph_count; i++) {
|
||||
glyph_bitmap const & bitmap = this->glyphs[i].bitmap;
|
||||
this->glyph_buffer[i].position.x = float(bitmap.x) / float(this->font->texture_width);
|
||||
this->glyph_buffer[i].position.y = float(bitmap.y) / float(this->font->texture_height);
|
||||
this->glyph_buffer[i].size.x = float(bitmap.width) / float(this->font->texture_width);
|
||||
this->glyph_buffer[i].size.y = float(bitmap.height) / float(this->font->texture_height);
|
||||
}
|
||||
}
|
||||
26
src/font_layout/font_layout.h
Normal file
26
src/font_layout/font_layout.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "font.h"
|
||||
#include "directxmath/DirectXMath.h"
|
||||
|
||||
struct GlyphBuffer {
|
||||
XMFLOAT2 position; // in uv units
|
||||
XMFLOAT2 size; // in uv units
|
||||
};
|
||||
|
||||
struct LayoutBuffer {
|
||||
XMFLOAT3 position; // in local coordinate space units
|
||||
float glyph_index;
|
||||
};
|
||||
|
||||
struct FontLayout {
|
||||
font * font;
|
||||
glyph * glyphs;
|
||||
void * texture;
|
||||
int layout_buffer_length; // in elements
|
||||
int layout_buffer_index;
|
||||
GlyphBuffer * glyph_buffer;
|
||||
LayoutBuffer * layout_buffer;
|
||||
|
||||
void init(size_t buffer, int layout_buffer_length);
|
||||
};
|
||||
16
tools/ttf_outline/Makefile
Normal file
16
tools/ttf_outline/Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
CFLAGS = -Og -g -gdwarf-4 -Wall -Wextra -Wno-error -Wfatal-errors
|
||||
CFLAGS += -Wno-error=unused-parameter
|
||||
CFLAGS += -Wno-error=unused-variable
|
||||
CFLAGS += -Wno-error=unused-but-set-variable
|
||||
CFLAGS += -I../../include
|
||||
|
||||
CXXFLAGS = -std=c++23
|
||||
|
||||
FREETYPE_CFLAGS = $(shell pkg-config --cflags freetype2)
|
||||
FREETYPE_LDFLAGS = $(shell pkg-config --libs freetype2)
|
||||
|
||||
%.o: %.cpp
|
||||
$(CXX) $(CFLAGS) $(CXXFLAGS) $(FREETYPE_CFLAGS) -c $< -o $@
|
||||
|
||||
ttf_outline: ttf_outline.o ttf_2d_pack.o
|
||||
$(CXX) $(LDFLAGS) $(FREETYPE_LDFLAGS) $^ -o $@
|
||||
159
tools/ttf_outline/ttf_2d_pack.cpp
Normal file
159
tools/ttf_outline/ttf_2d_pack.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <array>
|
||||
|
||||
#include "ttf_2d_pack.h"
|
||||
|
||||
struct size {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
};
|
||||
|
||||
constexpr struct size max_texture = {1024, 1024};
|
||||
|
||||
inline bool area_valid(const uint8_t texture[max_texture.height][max_texture.width],
|
||||
const uint32_t x_offset,
|
||||
const uint32_t y_offset,
|
||||
const struct rect& rect,
|
||||
const struct size& window)
|
||||
{
|
||||
for (uint32_t yi = 0; yi < rect.height; yi++) {
|
||||
for (uint32_t xi = 0; xi < rect.width; xi++) {
|
||||
uint32_t x = x_offset + xi;
|
||||
uint32_t y = y_offset + yi;
|
||||
|
||||
if (texture[y][x] != 0)
|
||||
return false;
|
||||
|
||||
if (x >= window.width || y >= window.height)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr inline std::array<uint32_t, 2>
|
||||
from_ix(uint32_t curve_ix)
|
||||
{
|
||||
std::array<uint32_t, 2> x_y = {0, 0};
|
||||
uint32_t curve_bit = 0;
|
||||
|
||||
while (curve_ix != 0) {
|
||||
x_y[(curve_bit + 1) % 2] |= (curve_ix & 1) << (curve_bit / 2);
|
||||
curve_ix >>= 1;
|
||||
curve_bit += 1;
|
||||
}
|
||||
|
||||
return x_y;
|
||||
}
|
||||
|
||||
static_assert(from_ix(0) == std::array<uint32_t, 2>{{0b000, 0b000}});
|
||||
static_assert(from_ix(2) == std::array<uint32_t, 2>{{0b001, 0b000}});
|
||||
static_assert(from_ix(8) == std::array<uint32_t, 2>{{0b010, 0b000}});
|
||||
static_assert(from_ix(10) == std::array<uint32_t, 2>{{0b011, 0b000}});
|
||||
static_assert(from_ix(32) == std::array<uint32_t, 2>{{0b100, 0b000}});
|
||||
static_assert(from_ix(34) == std::array<uint32_t, 2>{{0b101, 0b000}});
|
||||
static_assert(from_ix(40) == std::array<uint32_t, 2>{{0b110, 0b000}});
|
||||
static_assert(from_ix(42) == std::array<uint32_t, 2>{{0b111, 0b000}});
|
||||
|
||||
static_assert(from_ix(1) == std::array<uint32_t, 2>{{0b000, 0b001}});
|
||||
static_assert(from_ix(4) == std::array<uint32_t, 2>{{0b000, 0b010}});
|
||||
static_assert(from_ix(5) == std::array<uint32_t, 2>{{0b000, 0b011}});
|
||||
static_assert(from_ix(16) == std::array<uint32_t, 2>{{0b000, 0b100}});
|
||||
static_assert(from_ix(17) == std::array<uint32_t, 2>{{0b000, 0b101}});
|
||||
static_assert(from_ix(20) == std::array<uint32_t, 2>{{0b000, 0b110}});
|
||||
static_assert(from_ix(21) == std::array<uint32_t, 2>{{0b000, 0b111}});
|
||||
|
||||
constexpr inline int log2(uint32_t n)
|
||||
{
|
||||
switch (n) {
|
||||
default:
|
||||
case 8: return 3;
|
||||
case 16: return 4;
|
||||
case 32: return 5;
|
||||
case 64: return 6;
|
||||
case 128: return 7;
|
||||
case 256: return 8;
|
||||
case 512: return 9;
|
||||
case 1024: return 10;
|
||||
}
|
||||
}
|
||||
|
||||
static void pack_into(uint8_t texture[max_texture.height][max_texture.width],
|
||||
size & window,
|
||||
rect & rect)
|
||||
{
|
||||
uint32_t z_curve_ix = 0;
|
||||
|
||||
if (rect.width == 0 || rect.height == 0) {
|
||||
rect.x = 0;
|
||||
rect.y = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
auto [x_offset, y_offset] = from_ix(z_curve_ix);
|
||||
|
||||
if (x_offset >= window.width and y_offset >= window.height) {
|
||||
assert(window.width < max_texture.width || window.height < max_texture.height);
|
||||
if (window.width == window.height) { window.height *= 2; }
|
||||
else { window.width *= 2; }
|
||||
|
||||
// when the window changes; start again from the beginning and
|
||||
// re-check earlier locations that might have been skipped due
|
||||
// to window size
|
||||
z_curve_ix = 0;
|
||||
}
|
||||
|
||||
if (area_valid(texture, x_offset, y_offset, rect, window)) {
|
||||
for (uint32_t yi = 0; yi < rect.height; yi++) {
|
||||
for (uint32_t xi = 0; xi < rect.width; xi++) {
|
||||
uint32_t x = x_offset + xi;
|
||||
uint32_t y = y_offset + yi;
|
||||
|
||||
texture[y][x] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
rect.x = x_offset;
|
||||
rect.y = y_offset;
|
||||
|
||||
return;
|
||||
} else {
|
||||
z_curve_ix += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void insertion_sort(T * arr, int len)
|
||||
{
|
||||
int i = 1;
|
||||
while (i < len) {
|
||||
int j = i;
|
||||
while (j > 0 && arr[j - 1] < arr[j]) {
|
||||
std::swap(arr[j - 1], arr[j]);
|
||||
j -= 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
window_curve_ix pack_all(struct rect * rects, const uint32_t num_rects)
|
||||
{
|
||||
uint8_t texture[max_texture.height][max_texture.width] = { 0 };
|
||||
size window = {1, 1};
|
||||
|
||||
// sort all rectangles by size
|
||||
insertion_sort(rects, num_rects);
|
||||
|
||||
for (uint32_t i = 0; i < num_rects; i++) {
|
||||
pack_into(texture, window, rects[i]);
|
||||
}
|
||||
|
||||
return {window.width, window.height};
|
||||
}
|
||||
25
tools/ttf_outline/ttf_2d_pack.h
Normal file
25
tools/ttf_outline/ttf_2d_pack.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct window_curve_ix {
|
||||
struct {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} window;
|
||||
};
|
||||
|
||||
window_curve_ix pack_all(struct rect * rects, const uint32_t num_rects);
|
||||
|
||||
struct rect {
|
||||
uint32_t char_code;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
|
||||
std::strong_ordering operator<=>(const rect& b) const
|
||||
{
|
||||
return (width * height) <=> (b.width * b.height);
|
||||
}
|
||||
};
|
||||
299
tools/ttf_outline/ttf_outline.cpp
Normal file
299
tools/ttf_outline/ttf_outline.cpp
Normal file
@ -0,0 +1,299 @@
|
||||
#include <bit>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include "font.h"
|
||||
#include "ttf_2d_pack.h"
|
||||
|
||||
std::endian _target_endian;
|
||||
|
||||
constexpr uint32_t max_texture_dim = 1024;
|
||||
constexpr uint32_t max_texture_size = max_texture_dim * max_texture_dim;
|
||||
|
||||
template< class T >
|
||||
constexpr T byteswap(const T n)
|
||||
{
|
||||
if (std::endian::native != _target_endian) {
|
||||
return std::byteswap<T>(n);
|
||||
} else {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t
|
||||
load_outline_char_bitmap_rect(const FT_Face face,
|
||||
const FT_Int32 load_flags,
|
||||
const FT_Render_Mode render_mode,
|
||||
const FT_ULong char_code,
|
||||
struct rect& rect)
|
||||
{
|
||||
FT_Error error;
|
||||
FT_UInt glyph_index = FT_Get_Char_Index(face, char_code);
|
||||
|
||||
error = FT_Load_Glyph(face, glyph_index, load_flags);
|
||||
if (error) {
|
||||
std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
error = FT_Render_Glyph(face->glyph, render_mode);
|
||||
if (error) {
|
||||
std::cerr << "FT_Render_Glyph " << FT_Error_String(error) << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
rect.char_code = char_code;
|
||||
rect.height = face->glyph->bitmap.rows;
|
||||
rect.width = face->glyph->bitmap.width;
|
||||
rect.x = -1;
|
||||
rect.y = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t
|
||||
load_outline_char(const FT_Face face,
|
||||
const FT_Int32 load_flags,
|
||||
const FT_Render_Mode render_mode,
|
||||
const uint32_t bits_per_pixel,
|
||||
const FT_ULong char_code,
|
||||
glyph * glyph,
|
||||
uint8_t * texture,
|
||||
uint32_t texture_width,
|
||||
struct rect& rect)
|
||||
{
|
||||
FT_Error error;
|
||||
FT_UInt glyph_index = FT_Get_Char_Index(face, char_code);
|
||||
|
||||
error = FT_Load_Glyph(face, glyph_index, load_flags);
|
||||
if (error) {
|
||||
std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
//std::cerr << "size " << face->glyph->bitmap.rows << ' ' << face->glyph->bitmap.width << '\n';
|
||||
|
||||
//assert(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE);
|
||||
|
||||
error = FT_Render_Glyph(face->glyph, render_mode);
|
||||
if (error) {
|
||||
std::cerr << "FT_Render_Glyph " << FT_Error_String(error) << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(face->glyph->bitmap.pitch > 0)) {
|
||||
assert(face->glyph->bitmap.width == 0);
|
||||
assert(face->glyph->bitmap.rows == 0);
|
||||
}
|
||||
|
||||
assert(face->glyph->bitmap.width == rect.width);
|
||||
assert(face->glyph->bitmap.rows == rect.height);
|
||||
|
||||
assert(bits_per_pixel == 8 || bits_per_pixel == 4 || bits_per_pixel == 2 || bits_per_pixel == 1);
|
||||
const uint32_t pixels_per_byte = 8 / bits_per_pixel;
|
||||
const uint32_t texture_stride = texture_width / pixels_per_byte;
|
||||
//std::cerr << "pixels per byte: " << pixels_per_byte << '\n';
|
||||
//std::cerr << "texture stride: " << texture_stride << '\n';
|
||||
|
||||
for (uint32_t y = 0; y < rect.height; y++) {
|
||||
for (uint32_t x = 0; x < rect.width; x++) {
|
||||
const uint32_t texture_ix = (rect.y + y) * texture_stride + (rect.x + x) / pixels_per_byte;
|
||||
const uint32_t texture_ix_mod = (rect.x + x) % pixels_per_byte;
|
||||
assert(texture_ix < max_texture_size);
|
||||
|
||||
uint8_t level;
|
||||
|
||||
//std::cerr << "rxy " << rect.x << ' ' << rect.y << '\n';
|
||||
//std::cerr << "rwh " << rect.width << ' ' << rect.height << '\n';
|
||||
|
||||
//std::cerr << "pixel_mode " << (int)face->glyph->bitmap.pixel_mode << '\n';
|
||||
switch (face->glyph->bitmap.pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
// [num_grays] is only used with FT_PIXEL_MODE_GRAY; it gives the number
|
||||
// of gray levels used in the bitmap.
|
||||
level = (face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + (x / 8)] >> (7 - (x % 8))) & 1;
|
||||
break;
|
||||
case FT_PIXEL_MODE_GRAY:
|
||||
//std::cerr << "num_grays " << face->glyph->bitmap.num_grays << '\n';
|
||||
//assert(face->glyph->bitmap.num_grays == 256);
|
||||
level = face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch + x];
|
||||
level >>= (8 - bits_per_pixel);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
texture[texture_ix] |= level << (bits_per_pixel * texture_ix_mod);
|
||||
}
|
||||
}
|
||||
|
||||
glyph_bitmap& bitmap = glyph->bitmap;
|
||||
bitmap.x = byteswap<uint16_t>(rect.x);
|
||||
bitmap.y = byteswap<uint16_t>(rect.y);
|
||||
bitmap.width = byteswap<uint16_t>(rect.width);
|
||||
bitmap.height = byteswap<uint16_t>(rect.height);
|
||||
|
||||
glyph_metrics& metrics = glyph->metrics;
|
||||
metrics.horiBearingX = byteswap<int32_t>(face->glyph->metrics.horiBearingX);
|
||||
metrics.horiBearingY = byteswap<int32_t>(face->glyph->metrics.horiBearingY);
|
||||
metrics.horiAdvance = byteswap<int32_t>(face->glyph->metrics.horiAdvance);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
start_hex = 1,
|
||||
end_hex = 2,
|
||||
pixel_size = 3,
|
||||
target_endian = 4,
|
||||
font_file_path = 5,
|
||||
output_file_path = 6,
|
||||
argv_length = 7
|
||||
};
|
||||
|
||||
struct window_curve_ix
|
||||
load_all_positions(const FT_Face face,
|
||||
const uint32_t start,
|
||||
const uint32_t end,
|
||||
glyph * glyphs,
|
||||
uint8_t * texture
|
||||
)
|
||||
{
|
||||
const uint32_t num_glyphs = (end - start) + 1;
|
||||
struct rect rects[num_glyphs];
|
||||
|
||||
FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_TARGET_MODE(FT_RENDER_MODE_SDF) | FT_LOAD_RENDER;
|
||||
FT_Render_Mode render_mode = FT_RENDER_MODE_SDF;
|
||||
|
||||
// first, load all rectangles
|
||||
for (uint32_t char_code = start; char_code <= end; char_code++) {
|
||||
load_outline_char_bitmap_rect(face,
|
||||
load_flags,
|
||||
render_mode,
|
||||
char_code,
|
||||
rects[char_code - start]);
|
||||
}
|
||||
|
||||
// calculate a 2-dimensional packing for the rectangles
|
||||
auto window_curve_ix = pack_all(rects, num_glyphs);
|
||||
|
||||
const uint32_t bits_per_pixel = 8;
|
||||
|
||||
// render all of the glyphs to the texture;
|
||||
for (uint32_t i = 0; i < num_glyphs; i++) {
|
||||
const uint32_t char_code = rects[i].char_code;
|
||||
int32_t err = load_outline_char(face,
|
||||
load_flags,
|
||||
render_mode,
|
||||
bits_per_pixel,
|
||||
char_code,
|
||||
&glyphs[char_code - start],
|
||||
texture,
|
||||
window_curve_ix.window.width,
|
||||
rects[i]);
|
||||
if (err < 0) assert(false);
|
||||
}
|
||||
|
||||
return window_curve_ix;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
FT_Library library;
|
||||
FT_Face face;
|
||||
FT_Error error;
|
||||
|
||||
if (argc != argv_length) {
|
||||
std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [pixel-size] [target-endian] [font-file-path] [output-file-path]\n\n";
|
||||
std::cerr << "ex. 1: " << argv[0] << " 3000 30ff 30 0 little ipagp.ttf font.bin\n";
|
||||
std::cerr << "ex. 2: " << argv[0] << " 20 7f 30 1 big DejaVuSans.ttf font.bin\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
error = FT_Init_FreeType(&library);
|
||||
if (error) {
|
||||
std::cerr << "FT_Init_FreeType\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
error = FT_New_Face(library, argv[font_file_path], 0, &face);
|
||||
if (error) {
|
||||
std::cerr << "FT_New_Face\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::stringstream ss3;
|
||||
int font_size;
|
||||
ss3 << std::dec << argv[pixel_size];
|
||||
ss3 >> font_size;
|
||||
std::cerr << "font_size: " << font_size << '\n';
|
||||
std::stringstream ss4;
|
||||
|
||||
error = FT_Set_Pixel_Sizes(face, 0, font_size);
|
||||
if (error) {
|
||||
std::cerr << "FT_Set_Pixel_Sizes: " << FT_Error_String(error) << error << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::string(argv[target_endian]).compare("little") == 0) {
|
||||
_target_endian = std::endian::little;
|
||||
} else if (std::string(argv[target_endian]).compare("big") == 0) {
|
||||
_target_endian = std::endian::big;
|
||||
} else {
|
||||
std::cerr << "unknown endian: " << argv[target_endian] << '\n';
|
||||
std::cerr << "expected one of: big, little\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
|
||||
std::stringstream ss1;
|
||||
ss1 << std::hex << argv[start_hex];
|
||||
ss1 >> start;
|
||||
std::stringstream ss2;
|
||||
ss2 << std::hex << argv[end_hex];
|
||||
ss2 >> end;
|
||||
|
||||
uint32_t num_glyphs = (end - start) + 1;
|
||||
glyph glyphs[num_glyphs];
|
||||
uint8_t texture[max_texture_size];
|
||||
memset(texture, 0x00, max_texture_size);
|
||||
|
||||
auto window_curve_ix = load_all_positions(face, start, end, glyphs, texture);
|
||||
|
||||
uint32_t texture_size;
|
||||
texture_size = window_curve_ix.window.width * window_curve_ix.window.height;
|
||||
|
||||
font font;
|
||||
font.first_char_code = byteswap<uint32_t>(start);
|
||||
font.last_char_code = byteswap<uint32_t>(end);
|
||||
font.face_metrics.height = byteswap<int32_t>(face->size->metrics.height);
|
||||
font.face_metrics.max_advance = byteswap<int32_t>(face->size->metrics.max_advance);
|
||||
font.glyph_count = byteswap<uint16_t>(num_glyphs);
|
||||
font.texture_width = byteswap<uint16_t>(window_curve_ix.window.width);
|
||||
font.texture_height = byteswap<uint16_t>(window_curve_ix.window.height);
|
||||
|
||||
std::cerr << "start: 0x" << std::hex << start << '\n';
|
||||
std::cerr << "end: 0x" << std::hex << end << '\n';
|
||||
std::cerr << "texture_width: " << std::dec << window_curve_ix.window.width << '\n';
|
||||
std::cerr << "texture_height: " << std::dec << window_curve_ix.window.height << '\n';
|
||||
|
||||
FILE * out = fopen(argv[output_file_path], "w");
|
||||
if (out == NULL) {
|
||||
perror("fopen(w)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fwrite(reinterpret_cast<void*>(&font), (sizeof (font)), 1, out);
|
||||
fwrite(reinterpret_cast<void*>(&glyphs[0]), (sizeof (glyph)), num_glyphs, out);
|
||||
fwrite(reinterpret_cast<void*>(&texture[0]), (sizeof (uint8_t)), texture_size, out);
|
||||
|
||||
fclose(out);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user