#define DEBUG_FONT_RENDERING 1
#if DEBUG_FONT_RENDERING
int fixedFontSize = 10;
bool drawAAText = true;
bool drawFill = true;
bool drawOutline = false;
bool drawWholeOutline = false;
bool drawControlPoints = false;
std::string fontOpt = "Arial";
#else
const bool drawAAText = true;
const bool drawFill = true;
#endif
void DrawCurveC(PixelBuffer* a_target, uint32_t a_color, const vec2i& p1, const vec2i& p2, const vec2i& p3)
{
int dx = p1.x - p3.x;
int dy = p1.y - p3.y;
if (dx*dx > dy*dy) {
dx = (dx < 0) ? -dx : dx;
} else {
dx = (dy < 0) ? -dy : dy;
}
dx *= 2;
// Fixed precision version
for (int i = 0; i < dx; i++)
{
vec2i pos1 = bezierCurveFixed(p1, p2, p3, (i * 65536) / dx);
vec2i pos2 = bezierCurveFixed(p1, p2, p3, ((i+1) * 65536) / dx);
DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
}
}
void DrawRectangleB(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst += a_color;//0x2000000;
//*dst = a_color;//0x2000000;
}
}
}
void DrawRectangleC(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;//0x2000000;
//*dst = a_color;//0x2000000;
}
}
}
void DrawCurveB(PixelBuffer* a_target, uint32_t a_color,
const vec2i& p1, const vec2i& p2, const vec2i& p3, // 3 control points
bool firstInContour, bool lastInContour, int firstAngle) // connectivity flags
{
// if firstInContour - always output the first point
// if lastInContour - don't output the last point
// TODO: what if the angle from p1->p2->p3 makes a 'v' or '^' shape?
// if so, at the apex of this bezier curve, it won't have
// output 2 points as required.
// Probably some checking of angle between p1->p2 and p2->p3 are needed
// Definately some edge cases there.
int dy = p1.y - p3.y;
//int dx = p1.x - p3.x;
int thisAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
//if (firstInContour)
// firstAngle = thisAngle;
static int lastAngle = 0;
bool drawFirst = true;
bool drawLast = true;
if (!firstInContour)
drawFirst = ((thisAngle != 0) && ((thisAngle + lastAngle) == 0))
//|| ( (thisAngle == 0) && (dx > 0))
//|| ( (thisAngle > 0) && (lastAngle == 0))
;//lastAngle != 0) );
if (lastInContour)
drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
// if (firstInContour)
// drawFirst = true;
if (lastInContour) {
//drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
//if (thisAngle != 0 && thisAngle == firstAngle)
// drawLast = true;//false;
}
lastAngle = (thisAngle) ? thisAngle : (1 - lastAngle);
dy = (dy < 0) ? -dy : dy; // dy = |dy|
dy *= 2; // over-sampling -> we try to sample
// the bezier with an even distribution of 't' values but this
// doesn't mean the resulting positions are evenly spaced along
// the curve, but because we want one sample for each y value,
// we need to over-sample the bezier curve to actually ensure we
// get what we want in an efficient way. Less efficient would be
// to try to solve the linear equation to figure out the value
// of 't' that gives a given y value. But doing the forward
// calculation twice instead is quite efficient and practical.
if (!dy) {
dy = 0;//1024;//512;
}
// Fixed precision version
if (dy)
{
//dy+=5;
int lastX = -1;
int lastY = -1;
for (int i = 0; i <= dy;)// i++)
{
vec2i pos1;
bool final = false;
do {
pos1 = bezierCurveFixed(p1, p2, p3, (i << 16) / dy);
final = pos1.y == p3.y && pos1.x == p3.x;
i++;
} while (!final && (pos1.y == lastY));
bool first = pos1.y == p1.y && pos1.x == p1.x;
//bool final = pos1.y == p3.y && pos1.x == p3.x;
bool canDraw = /*(final && lastInContour) ||*/ pos1.y != lastY; // do we need to output something
if (first && !drawFirst)
canDraw = false;
if (final && !drawLast)
canDraw = false;
if (canDraw)
DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
//if (lastInContour && pos1.y == p3.y)
// DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
//else {
// DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
// DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
//}
lastY = pos1.y;
lastX = pos1.x;
if (final)
break;
}
if (drawLast) {
DrawRectangleB(a_target, a_color, p3.x, p3.y, 1, 1);
DrawRectangleB(a_target, a_color, p3.x, p3.y, 1, 1);
}
}
else
{
if (p1.y != p3.y)
exit(0);
//if (p1.x == p3.x)
//exit(0);
// if (drawFirst)
DrawRectangleC(a_target, a_color, p1.x, p1.y, 1, 1);
// else if (p3.x > (p1.x + 2)) {
//DrawRectangleC(a_target, a_color, p1.x + 1, p3.y, 1, 1);
//DrawRectangleC(a_target, a_color, p3.x - 1, p3.y, 1, 1);
//}
//if (!lastInContour && drawLast)
DrawRectangleC(a_target, a_color, p3.x, p3.y, 1, 1);
//else //if (p1.x != p3.x)
//DrawRectangleB(a_target, a_color, p3.x - 1, p3.y, 1, 1);
}
}
void RasterizeCharacter(PixelBuffer* a_target, uint32_t a_color, int a_size, int a_x, int a_y, Glyph::Outline& outline)
{
int aaShift = 3;
int bias = 512;
if (drawAAText)
a_size <<= aaShift;
Rectangle rect = glyphBounds(outline);
rect.m_width = ((rect.m_width * a_size + bias) >> 10) + 3;
rect.m_height = (rect.m_height * a_size + bias) >> 10;
PixelBuffer buf = {
new uint32_t[rect.m_width*rect.m_height],
rect.m_width*4,
rect.m_width,
rect.m_height,
PF_ARGB8888
};
for (int i = 0; i < rect.m_width*rect.m_height; i++)
buf.m_pixels[i] = 0x00C0C0C0;
vec2i lastPos{ -1, -1 };
vec2i firstPos{ -1, -1 };
int firstAngle = 0;
bool firstInContour = true;
bool lastInContour = true;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
Glyph::Outline::Curve& b = outline.m_lines[i];
{
vec2i pnts[3];
for (int c = 0; c < 3; c++)
pnts[c] = (vec2i){ int((((b.m_controlPoints[c].m_x - rect.m_x) * a_size) + bias) >> 10),
rect.m_height - int((((b.m_controlPoints[c].m_y - rect.m_y) * a_size) + bias) >> 10) };
const vec2i &p1 = pnts[0], &p2 = pnts[1], &p3 = pnts[2];
firstInContour = lastInContour;
if (firstInContour)
{
int dy = p1.y - p3.y;
firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
firstPos = p1;
}
lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
if (1) // alternative way to determine first
{
if (p1.x != lastPos.x || p1.y != lastPos.y)
{
assert(firstInContour);
}
lastPos = p3;
}
DrawCurveB(&buf, 0x02000000, p1, p2, p3, firstInContour, lastInContour, firstAngle);
}
}
if (drawFill) {
for (int j = 0; j < rect.m_height; j++)
{
uint8_t state = 0; // 0 off 1 off->on 2 on 3 on->off
for (int i = 0; i < rect.m_width; i++)
{
int count = buf.m_pixels[j*rect.m_width + i] >> 25;
do
{
if (count)
{
buf.m_pixels[j*rect.m_width + i] = 0x80C0C0C0 | a_color;
state = 1 - state;
count--;
}
else
{
if (state)
buf.m_pixels[j*rect.m_width + i] = 0xFF000000 | a_color;
}
}
while (count);
}
}
}
#if DEBUG_FONT_RENDERING
if (drawControlPoints) {
lastPos = vec2i{ -1, -1 };
firstPos = vec2i{ -1, -1 };
firstInContour = true;
lastInContour = true;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
Glyph::Outline::Curve& b = outline.m_lines[i];
{
vec2i pnts[3];
for (int c = 0; c < 3; c++)
pnts[c] = (vec2i){ int((((b.m_controlPoints[c].m_x - rect.m_x) * a_size) + bias) >> 10),
rect.m_height - int((((b.m_controlPoints[c].m_y - rect.m_y) * a_size) + bias) >> 10) };
const vec2i &p1 = pnts[0], &p2 = pnts[1], &p3 = pnts[2];
firstInContour = lastInContour;
if (firstInContour)
{
int dy = pnts[0].y - pnts[2].y;
firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
firstPos = pnts[0];
}
lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
if (firstInContour)//p1.x != lastPos.x || p1.y != lastPos.y)
{
DrawRectangle(&buf, 0xFF00FF00, p1.x-3, p1.y-3, 5, 5);
DrawRectangle(&buf, 0xFF0000FF, p2.x-3, p2.y-3, 5, 5);
DrawRectangle(&buf, 0xFF0000FF, p3.x-3, p3.y-3, 5, 5);
}
else if (lastInContour)
{
DrawRectangleAlpha(&buf, 0x1FFF7F00, p1.x-3, p1.y-3, 5, 5);
DrawRectangle(&buf, 0xFFFF4444, p2.x-3, p2.y-3, 5, 5);
DrawRectangleAlpha(&buf, 0x1F1FFF1F, p3.x-3, p3.y-3, 5, 5);
}
else
{
DrawRectangleAlpha(&buf, 0x3F808044, p1.x-1, p1.y-1, 3, 3);
DrawRectangleAlpha(&buf, 0x3FFF4444, p2.x-1, p2.y-1, 3, 3);
DrawRectangleAlpha(&buf, 0x3FFF4444, p3.x-1, p3.y-1, 3, 3);
}
lastPos = p3;
}
}
}
if (drawOutline) {
lastPos = vec2i{ -1, -1 };
firstPos = vec2i{ -1, -1 };
firstInContour = true;
lastInContour = true;
firstAngle = 0;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
Glyph::Outline::Curve& b = outline.m_lines[i];
{
vec2i pnts[3];
for (int c = 0; c < 3; c++)
pnts[c] = (vec2i){ int((((b.m_controlPoints[c].m_x - rect.m_x) * a_size) + bias) >> 10),
rect.m_height - int((((b.m_controlPoints[c].m_y - rect.m_y) * a_size) + bias) >> 10) };
firstInContour = lastInContour;
if (firstInContour)
{
int dy = pnts[0].y - pnts[2].y;
firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
firstPos = pnts[0];
}
lastInContour = pnts[2].x == firstPos.x && pnts[2].y == firstPos.y;
DrawCurveB(&buf, 0xFF007F7F, pnts[0], pnts[1], pnts[2], firstInContour, lastInContour, firstAngle);
}
}
}
#endif // DEBUG_FONT_RENDERING
if (drawAAText)
{
int w = rect.m_width >> aaShift;
int h = rect.m_height >> aaShift;
PixelBuffer downScaled = { new uint32_t[w*h], w*4, w, h, PF_ARGB8888 };
if (SmoothDownSample(downScaled.m_pixels, w, h, buf.m_pixels, rect.m_width, rect.m_height))
{
a_size >>= aaShift;
//int fy = (((rect.m_y+511)*a_size)>>10);
int fy = (rect.m_y*a_size + bias) >> 10;
int fh = rect.m_height >> aaShift;
int yOff = a_y + a_size - fh - fy;
DrawPixelsAlpha(a_target, downScaled.m_pixels, 5 + a_x + ((rect.m_x*a_size)>>10),
a_y + 2 + yOff, w, h, 0, 0, w, h);
}
delete[] downScaled.m_pixels;
}
else
{
int yOff = a_size - rect.m_height;
DrawPixels(a_target, buf.m_pixels, a_x + ((rect.m_x*a_size)>>10),
a_y + yOff - ((rect.m_y*a_size)>>10), buf.m_width, buf.m_height, 0, 0, buf.m_width, buf.m_height);
}
delete[] buf.m_pixels;
}