/
YFont.cpp
209 lines (163 loc) · 5.69 KB
/
YFont.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
* THE UNICODE TEST SUITE FOR CINDER: https://github.com/arielm/Unicode
* COPYRIGHT (C) 2013, ARIEL MALKA ALL RIGHTS RESERVED.
*
* THE FOLLOWING SOURCE-CODE IS DISTRIBUTED UNDER THE MODIFIED BSD LICENSE:
* https://github.com/arielm/Unicode/blob/master/LICENSE.md
*/
#include "YFont.h"
#include "hb-ft.h"
#include "cinder/Utilities.h"
using namespace std;
using namespace ci;
/*
See http://www.microsoft.com/typography/otspec/name.htm for a list of some
possible platform-encoding pairs. We're interested in 0-3 aka 3-1 - UCS-2.
Otherwise, fail. If a font has some unicode map, but lacks UCS-2 - it is a
broken or irrelevant font. What exactly Freetype will select on face load
(it promises most wide unicode, and if that will be slower that UCS-2 -
left as an excercise to check.)
*/
static FT_Error force_ucs2_charmap(FT_Face face)
{
for (int i = 0; i < face->num_charmaps; i++)
{
auto platform_id = face->charmaps[i]->platform_id;
auto encoding_id = face->charmaps[i]->encoding_id;
if (((platform_id == 0) && (encoding_id == 3)) || ((platform_id == 3) && (encoding_id == 1)))
{
return FT_Set_Charmap(face, face->charmaps[i]);
}
}
return -1;
}
static int nextPowerOfTwo(int x)
{
int result = 1;
while (result < x)
{
result <<= 1;
}
return result;
}
YGlyph::YGlyph(unsigned char *data, int width, int height)
{
if (width * height > 0)
{
int textureWidth = nextPowerOfTwo(width);
int textureHeight = nextPowerOfTwo(height);
auto textureData = (unsigned char*)calloc(textureWidth * textureHeight, 1); // WE NEED A ZERO-FILLED AREA
for (int iy = 0; iy < height; iy++)
{
for (int ix = 0; ix < width; ix++)
{
textureData[iy * textureWidth + ix] = data[iy * width + ix];
}
}
gl::Texture::Format format;
format.setInternalFormat(GL_ALPHA);
texture = gl::Texture::create(textureData, GL_ALPHA, textureWidth, textureHeight, format);
free(textureData);
}
}
YFont::YFont(shared_ptr<FreetypeHelper> ftHelper, const FontDescriptor &descriptor, float size)
:
ftHelper(ftHelper)
{
FT_Error error = FT_New_Face(ftHelper->getLib(), descriptor.source->getFilePath().c_str(), descriptor.faceIndex, &face);
if (error)
{
throw runtime_error("FREETYPE: ERROR " + toString(error));
}
if (force_ucs2_charmap(face))
{
throw runtime_error("HARFBUZZ: FONT IS BROKEN OR IRRELEVANT");
}
// ---
/*
* USING A MATRIX WITH A MULTIPLIER ALLOWS FOR FRACTIONAL VALUES
* TRICK FROM http://code.google.com/p/freetype-gl/
*
* - WITHOUT A FRACTIONAL ADVANCE: CHARACTER SPACING LOOKS DUMB
* - WITHOUT A FRACTIONAL HEIGHT: SOME CHARACTERS WON'T BE PERFECTLY ALIGNED ON THE BASELINE
*/
int res = 64;
int dpi = 72;
scale = Vec2f::one() / Vec2f(res, res) / 64;
FT_Set_Char_Size(face, size * 64, 0, dpi * res, dpi * res);
FT_Matrix matrix =
{
int((1.0 / res) * 0x10000L),
int((0.0) * 0x10000L),
int((0.0) * 0x10000L),
int((1.0 / res) * 0x10000L)
};
FT_Set_Transform(face, &matrix, NULL);
leading = face->size->metrics.height * scale.y;
ascent = face->size->metrics.ascender * scale.y;
descent = -face->size->metrics.descender * scale.y;
// ---
hbFont = hb_ft_font_create(face, NULL);
hbBuffer = hb_buffer_create();
}
YFont::~YFont()
{
hb_buffer_destroy(hbBuffer);
hb_font_destroy(hbFont);
FT_Done_Face(face);
}
void YFont::drawSpan(const TextSpan &span, float x, float y) const
{
hb_buffer_reset(hbBuffer);
hb_buffer_set_direction(hbBuffer, span.direction);
hb_buffer_set_script(hbBuffer, span.script);
hb_buffer_set_language(hbBuffer, hb_language_from_string(span.lang.data(), -1));
size_t textSize = span.text.size();
hb_buffer_add_utf8(hbBuffer, span.text.data(), textSize, 0, textSize);
hb_shape(hbFont, hbBuffer, NULL, 0);
unsigned int glyphCount;
hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(hbBuffer, &glyphCount);
hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(hbBuffer, &glyphCount);
// ---
glPushMatrix();
glTranslatef(x, y, 0);
for (int i = 0; i < glyphCount; i++)
{
const hb_glyph_position_t &pos = glyph_pos[i];
Vec2f offset(pos.x_offset, -pos.y_offset);
Vec2f advance(pos.x_advance, pos.y_advance);
YGlyph *glyph = createGlyph(glyph_info[i].codepoint);
if (glyph)
{
if (glyph->texture)
{
gl::draw(glyph->texture, glyph->offset + offset * scale);
}
delete glyph;
}
gl::translate(advance * scale);
}
glPopMatrix();
}
YGlyph* YFont::createGlyph(uint32_t codepoint) const
{
if (codepoint > 0)
{
FT_Error error = FT_Load_Glyph(face, codepoint, FT_LOAD_DEFAULT | FT_LOAD_FORCE_AUTOHINT);
if (!error)
{
FT_GlyphSlot slot = face->glyph;
FT_Glyph glyph;
error = FT_Get_Glyph(slot, &glyph);
if (!error)
{
FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
YGlyph *g = new YGlyph(slot->bitmap.buffer, slot->bitmap.width, slot->bitmap.rows);
g->offset = Vec2f(slot->bitmap_left, -slot->bitmap_top);
FT_Done_Glyph(glyph);
return g;
}
}
}
return NULL;
}