551 lines
15 KiB
C++
551 lines
15 KiB
C++
#include "stdafx.h"
|
|
#include "TextureFont.h"
|
|
#include "Utils.h"
|
|
|
|
#include <fstream>
|
|
#include <math.h>
|
|
#include <cassert>
|
|
|
|
TextureFont::TextureFont()
|
|
{
|
|
m_iCharDescent = 0;
|
|
m_iCharLeftOverlap = 0;
|
|
m_iCharRightOverlap = 0;
|
|
m_iCharBaseline = 0;
|
|
m_iCharTop = 0;
|
|
m_iCharVertSpacing = 0;
|
|
}
|
|
|
|
TextureFont::~TextureFont()
|
|
{
|
|
}
|
|
|
|
void TextureFont::FormatFontPages()
|
|
{
|
|
for( unsigned i = 0; i < m_apPages.size(); ++i )
|
|
delete m_apPages[i];
|
|
m_apPages.clear();
|
|
for( map<wchar_t, HBITMAP>::iterator i = m_Characters.begin(); i != m_Characters.end(); ++i )
|
|
{
|
|
if( i->second == NULL )
|
|
continue;
|
|
int b = DeleteObject( i->second );
|
|
ASSERT( b );
|
|
}
|
|
m_Characters.clear();
|
|
|
|
m_sError = m_sWarnings = "";
|
|
|
|
/*
|
|
* Create the system font.
|
|
*/
|
|
LOGFONT font;
|
|
memset( &font, 0, sizeof(font) );
|
|
strncpy( &font.lfFaceName[0], (const char *) m_sFamily, 31 );
|
|
font.lfFaceName[31] = 0;
|
|
font.lfCharSet = DEFAULT_CHARSET;
|
|
if( m_bBold )
|
|
font.lfWeight = FW_BOLD;
|
|
if( m_bItalic )
|
|
font.lfItalic = TRUE;
|
|
|
|
// (fPoints / 72.0f) * 90
|
|
font.lfHeight = (LONG) -m_fFontSizePixels;
|
|
font.lfQuality = m_bAntiAlias? ANTIALIASED_QUALITY: NONANTIALIASED_QUALITY;
|
|
font.lfPitchAndFamily = DEFAULT_PITCH;
|
|
|
|
HFONT hFont = CreateFontIndirect( &font );
|
|
if( hFont == NULL )
|
|
{
|
|
m_sError = "Font isn't available";
|
|
return;
|
|
}
|
|
|
|
HDC hDC = CreateCompatibleDC( NULL );
|
|
HGDIOBJ hOldFont = SelectObject( hDC, hFont );
|
|
|
|
/*
|
|
* Read high-level text metrics.
|
|
*/
|
|
TEXTMETRIC TextMetrics;
|
|
GetTextMetrics( hDC, &TextMetrics );
|
|
|
|
m_iCharBaseline = TextMetrics.tmAscent;
|
|
m_iCharDescent = TextMetrics.tmDescent;
|
|
m_iCharVertSpacing = TextMetrics.tmHeight + TextMetrics.tmExternalLeading;
|
|
m_iCharTop = TextMetrics.tmInternalLeading;
|
|
m_iCharLeftOverlap = m_iCharRightOverlap = 0;
|
|
m_BoundingRect.top = m_BoundingRect.left = 0;
|
|
m_BoundingRect.bottom = m_BoundingRect.right = 0;
|
|
|
|
|
|
int n = GetKerningPairs( hDC, 0, NULL );
|
|
KERNINGPAIR *kp = new KERNINGPAIR[n];
|
|
GetKerningPairs( hDC, n, kp );
|
|
|
|
for( int i = 0; i < n; ++i )
|
|
{
|
|
if( kp[i].wFirst == 'A' && kp[i].wSecond == 'A' )
|
|
int q = 1;
|
|
if( kp[i].wFirst == 'A' && kp[i].wSecond == 'j' )
|
|
int q = 1;
|
|
if( kp[i].wFirst == 'j' && kp[i].wSecond == 'j' )
|
|
int q = 1;
|
|
}
|
|
|
|
m_RealBounds.clear();
|
|
|
|
for( unsigned i = 0; i < m_PagesToGenerate.size(); ++i )
|
|
{
|
|
m_apPages.push_back( new FontPage );
|
|
FormatFontPage( i, hDC );
|
|
}
|
|
|
|
/* OUTLINETEXTMETRIC *tm = NULL;
|
|
int i = GetOutlineTextMetrics( hDC, 0, NULL );
|
|
if( i )
|
|
{
|
|
tm = (OUTLINETEXTMETRIC *) new char[i];
|
|
GetOutlineTextMetrics( hDC, i, tm );
|
|
}
|
|
delete [] tm;
|
|
*/
|
|
|
|
SelectObject( hDC, hOldFont );
|
|
DeleteObject( hFont );
|
|
DeleteDC( hDC );
|
|
}
|
|
|
|
void TextureFont::FormatCharacter( wchar_t c, HDC hDC )
|
|
{
|
|
if( m_Characters.find(c) != m_Characters.end() )
|
|
return;
|
|
|
|
WORD gi;
|
|
if( GetGlyphIndicesW(hDC, &c, 1, &gi, GGI_MARK_NONEXISTING_GLYPHS) && gi == 0xFFFF )
|
|
return;
|
|
|
|
/* int ii = GetFontUnicodeRanges( hDC, NULL );
|
|
GLYPHSET *gs = (GLYPHSET *) alloca(ii);
|
|
GetFontUnicodeRanges( hDC, gs );
|
|
|
|
GLYPHMETRICS gm;
|
|
MAT2 mat;
|
|
memset( &mat, 0, sizeof(mat) );
|
|
mat.eM11.value = 1;
|
|
mat.eM22.value = 1;
|
|
|
|
ii = GetGlyphOutline( hDC, c, GGO_BEZIER, &gm, 0, NULL, &mat );
|
|
DWORD *data = (DWORD *) alloca(ii);
|
|
ii = GetGlyphOutline( hDC, c, GGO_BEZIER, &gm, ii, data, &mat );
|
|
*/
|
|
|
|
// lol what
|
|
if(c == L'j')
|
|
int q = 1;
|
|
|
|
ABC &abc = m_ABC[c];
|
|
{
|
|
/* Use GetCharABCWidthsFloatW, since it works on all types of fonts; GetCharABCWidths
|
|
* only works on TrueType fonts. */
|
|
ABCFLOAT abcf;
|
|
GetCharABCWidthsFloatW( hDC, c, c, &abcf );
|
|
|
|
abc.abcA = lrintf( abcf.abcfA );
|
|
abc.abcB = lrintf( abcf.abcfB );
|
|
abc.abcC = lrintf( abcf.abcfC );
|
|
}
|
|
|
|
/*
|
|
* If the A or C widths are positive, it's simply extra space to add to either side.
|
|
* Move this into the B width. The only time this actually matters is to be able to
|
|
* omit the A width from the first letter of a string which is left-aligned, or the
|
|
* last C width from a right-aligned string. The exported fonts don't support explicit
|
|
* ABC widths, instead representing overhang and underhang with global m_iCharLeftOverlap
|
|
* and m_iCharRightOverlap settings.
|
|
*
|
|
* After making this adjustment, the glyphs' B region is left-aligned in the bitmap.
|
|
*/
|
|
if( abc.abcA > 0 )
|
|
{
|
|
abc.abcB += abc.abcA;
|
|
abc.abcA = 0;
|
|
}
|
|
if( abc.abcC > 0 )
|
|
{
|
|
abc.abcB += abc.abcC;
|
|
abc.abcC = 0;
|
|
}
|
|
|
|
/* Render the character into an empty bitmap. Since we don't know how
|
|
* large the character will be, this is somewhat oversized. */
|
|
HBITMAP hBitmap;
|
|
{
|
|
HDC hTempDC = GetDC(NULL);
|
|
hBitmap = CreateCompatibleBitmap( hTempDC, abc.abcB, 128 );
|
|
ReleaseDC( NULL, hTempDC );
|
|
}
|
|
HGDIOBJ hOldBitmap = SelectObject( hDC, hBitmap );
|
|
|
|
SetTextColor( hDC, RGB(0xFF,0xFF,0xFF) );
|
|
SetBkColor( hDC, RGB(0,0,0) );
|
|
SetBkMode( hDC, OPAQUE );
|
|
|
|
TextOutW( hDC, -abc.abcA, 0, &c, 1 );
|
|
|
|
/* Determine the real bounding box: */
|
|
RECT &realbounds = m_RealBounds[c];
|
|
realbounds.top = 0;
|
|
realbounds.bottom = 10;
|
|
|
|
/* if(c == L'j')
|
|
{
|
|
Surface surf;
|
|
BitmapToSurface( hBitmap, &surf );
|
|
GrayScaleToAlpha( &surf );
|
|
|
|
FILE *f = fopen( "c:/foo5.png", "w+b" );
|
|
char szErrorbuf[1024];
|
|
SavePNG( f, szErrorbuf, &surf );
|
|
fclose( f );
|
|
}
|
|
*/
|
|
{
|
|
Surface surf;
|
|
BitmapToSurface( hBitmap, &surf );
|
|
GetBounds( &surf, &realbounds );
|
|
}
|
|
realbounds.left = 0;
|
|
realbounds.right = abc.abcB;
|
|
|
|
|
|
/* {
|
|
// Graphics blit( pBitmap );
|
|
// blit.DrawLine(&pen, abc.abcA, 5, abc.abcA+abc.abcB, 5);
|
|
const SolidBrush solidBrush1(Color(128, 255, 0, 255));
|
|
Pen pen( &solidBrush1, 1 );
|
|
graphics.DrawRectangle( &pen, 0, 0, (-abc.abcA) - 1, 10 );
|
|
|
|
pen.SetColor(Color(128, 0, 255, 255));
|
|
graphics.DrawRectangle( &pen, 0, 0, abc.abcB - 1, 10 );
|
|
|
|
pen.SetColor(Color(128, 255, 255, 0));
|
|
graphics.DrawRectangle( &pen, abc.abcA + abc.abcB, 10, abc.abcC - 1, 20 );
|
|
}
|
|
*/
|
|
|
|
/*
|
|
* The bitmap is probably too big. Resize it: remove empty space on
|
|
* the left, right and bottom. Don't move it up; that'll confuse offsets.
|
|
*/
|
|
BitBlt( hDC, 0, 0, realbounds.right, realbounds.bottom,
|
|
hDC, 0, 0, SRCCOPY );
|
|
SelectObject( hDC, hOldBitmap );
|
|
|
|
|
|
m_Characters[c] = hBitmap;
|
|
|
|
if( realbounds.left != realbounds.right && realbounds.top != realbounds.bottom )
|
|
{
|
|
m_BoundingRect.top = min( m_BoundingRect.top, (LONG) realbounds.top );
|
|
m_BoundingRect.left = min( m_BoundingRect.left, (LONG) realbounds.left );
|
|
m_BoundingRect.right = max( m_BoundingRect.right, (LONG) realbounds.right );
|
|
m_BoundingRect.bottom = max( m_BoundingRect.bottom, (LONG) realbounds.bottom );
|
|
if( m_BoundingRect.left == m_BoundingRect.right && m_BoundingRect.top == m_BoundingRect.bottom )
|
|
m_BoundingRect = realbounds;
|
|
}
|
|
|
|
m_iCharLeftOverlap = max( m_iCharLeftOverlap, -int(abc.abcA) );
|
|
m_iCharRightOverlap = max( m_iCharRightOverlap, int(abc.abcC) - int(abc.abcB) );
|
|
|
|
// const SolidBrush solidBrush(Color(128, 255, 0, 255));
|
|
// Pen pen(&solidBrush, 1);
|
|
// graphics.DrawRectangle(&pen, bounds);
|
|
|
|
// Graphics blit( pBitmap );
|
|
// blit.DrawLine(&pen, abc.abcA, 5, abc.abcA+abc.abcB, 5);
|
|
// blit.DrawRectangle( &pen, 1, 10, abc.abcA, 10 );
|
|
|
|
// pen.SetColor(Color(255, 0, 255, 0));
|
|
// graphics.DrawRectangle(&pen, realbounds);
|
|
}
|
|
|
|
/* Return the number of pixels the characters are shifted downwards in the final
|
|
* page for m_iPadding. */
|
|
int TextureFont::GetTopPadding() const
|
|
{
|
|
return m_iPadding/2;
|
|
}
|
|
|
|
void TextureFont::FormatFontPage( int iPage, HDC hDC )
|
|
{
|
|
const FontPageDescription &Desc = m_PagesToGenerate[iPage];
|
|
|
|
/* First, generate bitmaps for all characters in this page. */
|
|
for( unsigned i = 0; i < Desc.chars.size(); ++i )
|
|
FormatCharacter( Desc.chars[i], hDC );
|
|
|
|
FontPage *pPage = m_apPages[iPage];
|
|
pPage->m_iFrameWidth = (m_BoundingRect.right - m_BoundingRect.left) + m_iPadding;
|
|
pPage->m_iFrameHeight = (m_BoundingRect.bottom - m_BoundingRect.top) + m_iPadding;
|
|
int iDimensionMultiple = 4; // TODO: This only needs to be 4 for doubleres textures. It could be 2 otherwise and use less space
|
|
pPage->m_iFrameWidth = (int)ceil( pPage->m_iFrameWidth /(double)iDimensionMultiple ) * iDimensionMultiple;
|
|
pPage->m_iFrameHeight = (int)ceil( pPage->m_iFrameHeight /(double)iDimensionMultiple ) * iDimensionMultiple;
|
|
|
|
pPage->m_iNumFramesX = (int) ceil( powf( (float) Desc.chars.size(), 0.5f ) );
|
|
pPage->m_iNumFramesY = (int) ceil( (float) Desc.chars.size() / pPage->m_iNumFramesX );
|
|
|
|
pPage->Create( pPage->m_iNumFramesX*pPage->m_iFrameWidth, pPage->m_iNumFramesY*pPage->m_iFrameHeight );
|
|
|
|
HGDIOBJ hOldBitmap = SelectObject( hDC, pPage->m_hPage );
|
|
|
|
HDC hSrcDC = CreateCompatibleDC( NULL );
|
|
|
|
int iRow = 0, iCol = 0;
|
|
for( unsigned CurChar = 0; CurChar < Desc.chars.size(); ++CurChar )
|
|
{
|
|
const wchar_t c = Desc.chars[CurChar];
|
|
const ABC &abc = m_ABC[c];
|
|
|
|
/* The current frame is at fOffsetX/fOffsetY. Center the character
|
|
* horizontally in the frame. We can align it however we want
|
|
* vertically, as long as we align the baselines. */
|
|
float fOffsetX = (float) pPage->m_iFrameWidth*iCol; /* origin -> frame top-left */
|
|
fOffsetX += pPage->m_iFrameWidth/2.0f; /* frame top-left -> frame center */
|
|
fOffsetX -= (abc.abcA+abc.abcB+abc.abcC)/2.0f;
|
|
fOffsetX += abc.abcA;
|
|
|
|
/* Truncate, so we don't blit to half a pixel: */
|
|
fOffsetX = float(int(fOffsetX));
|
|
|
|
float fOffsetY = (float) pPage->m_iFrameHeight*iRow;
|
|
fOffsetY += GetTopPadding();
|
|
|
|
if( m_Characters[c] != NULL )
|
|
{
|
|
HBITMAP hCharacterBitmap = m_Characters[c];
|
|
HGDIOBJ hOldSrcBitmap = SelectObject( hSrcDC, hCharacterBitmap );
|
|
|
|
const RECT &realbounds = m_RealBounds[c];
|
|
BitBlt( hDC, int(fOffsetX), int(fOffsetY),
|
|
m_ABC[c].abcB, realbounds.bottom,
|
|
hSrcDC, 0, 0, SRCCOPY );
|
|
|
|
SelectObject( hSrcDC, hOldSrcBitmap );
|
|
}
|
|
|
|
++iCol;
|
|
if( iCol == pPage->m_iNumFramesX )
|
|
{
|
|
iCol = 0;
|
|
++iRow;
|
|
}
|
|
}
|
|
|
|
DeleteDC( hSrcDC );
|
|
SelectObject( hDC, hOldBitmap );
|
|
}
|
|
|
|
/* UTF-8 encode ch and append to out. */
|
|
void wchar_to_utf8( wchar_t ch, string &out )
|
|
{
|
|
if( ch < 0x80 ) { out.append( 1, (char) ch ); return; }
|
|
|
|
int cbytes = 0;
|
|
if( ch < 0x800 ) cbytes = 1;
|
|
else if( ch < 0x10000 ) cbytes = 2;
|
|
else if( ch < 0x200000 ) cbytes = 3;
|
|
else if( ch < 0x4000000 ) cbytes = 4;
|
|
else cbytes = 5;
|
|
|
|
{
|
|
int shift = cbytes*6;
|
|
const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
|
|
out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) );
|
|
}
|
|
|
|
for( int i = 0; i < cbytes; ++i )
|
|
{
|
|
int shift = (cbytes-i-1)*6;
|
|
out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) );
|
|
}
|
|
}
|
|
|
|
#include <iomanip>
|
|
|
|
static bool IsNumberChar( wchar_t c )
|
|
{
|
|
return c >= 0x0030 && c <= 0x0039;
|
|
}
|
|
|
|
void TextureFont::Save( CString sBasePath, CString sBitmapAppendBeforeExtension, bool bSaveMetrics, bool bSaveBitmaps, bool bExportStrokeTemplates )
|
|
{
|
|
if( m_sError != "" )
|
|
return;
|
|
|
|
const CString inipath = sBasePath + ".ini";
|
|
|
|
ofstream f;
|
|
|
|
if( bSaveMetrics )
|
|
{
|
|
f.open(inipath.GetString());
|
|
|
|
/* Write global properties: */
|
|
f << "[common]\n";
|
|
|
|
f << "Baseline=" << m_iCharBaseline + GetTopPadding() << "\n";
|
|
f << "Top=" << m_iCharTop + GetTopPadding() << "\n";
|
|
f << "LineSpacing=" << m_iCharVertSpacing << "\n";
|
|
f << "DrawExtraPixelsLeft=" << m_iCharLeftOverlap << "\n";
|
|
f << "DrawExtraPixelsRight=" << m_iCharRightOverlap << "\n";
|
|
f << "AdvanceExtraPixels=0\n";
|
|
}
|
|
|
|
for( unsigned i = 0; i < m_apPages.size(); ++i )
|
|
{
|
|
const FontPageDescription &desc = m_PagesToGenerate[i];
|
|
ASSERT( m_apPages[i]->m_hPage );
|
|
FontPage &page = *m_apPages[i];
|
|
|
|
if( bSaveMetrics )
|
|
{
|
|
f << "\n" << "[" << desc.name.GetString() << "]\n";
|
|
|
|
{
|
|
int iWidth = 1;
|
|
if( desc.chars.size() / page.m_iNumFramesX > 10 )
|
|
iWidth = 2;
|
|
|
|
unsigned iChar = 0;
|
|
unsigned iLine = 0;
|
|
while( iChar < desc.chars.size() )
|
|
{
|
|
f << "Line " << setw(iWidth) << iLine << "=";
|
|
f << setw(1);
|
|
for( int iX = 0; iX < page.m_iNumFramesX && iChar < desc.chars.size(); ++iX, ++iChar )
|
|
{
|
|
const wchar_t c = desc.chars[iChar];
|
|
string sUTF8;
|
|
wchar_to_utf8( c, sUTF8 );
|
|
f << sUTF8.c_str();
|
|
}
|
|
f << "\n";
|
|
++iLine;
|
|
}
|
|
}
|
|
|
|
f << "\n";
|
|
|
|
|
|
/* export character widths. "numbers" page has fixed with for all number characters. */
|
|
vector<int> viCharWidth;
|
|
int iMaxNumberCharWidth = 0;
|
|
for( unsigned j = 0; j < desc.chars.size(); ++j )
|
|
{
|
|
/* This is the total width to advance for the whole character, which is the
|
|
* sum of the ABC widths. */
|
|
const wchar_t c = desc.chars[j];
|
|
ABC &abc = m_ABC[c];
|
|
int iCharWidth = abc.abcA + int(abc.abcB) + int(abc.abcC);
|
|
viCharWidth.push_back( iCharWidth );
|
|
|
|
if( IsNumberChar( c ) )
|
|
iMaxNumberCharWidth = max( iMaxNumberCharWidth, iCharWidth );
|
|
}
|
|
for( unsigned j = 0; j < desc.chars.size(); ++j )
|
|
{
|
|
const wchar_t c = desc.chars[j];
|
|
int iCharWidth = viCharWidth[j];
|
|
if( desc.name == "numbers" && IsNumberChar( c ) )
|
|
iCharWidth = iMaxNumberCharWidth;
|
|
f << j << "=" << iCharWidth << "\n";
|
|
}
|
|
}
|
|
|
|
if( bSaveBitmaps )
|
|
{
|
|
Surface surf;
|
|
BitmapToSurface( m_apPages[i]->m_hPage, &surf );
|
|
|
|
GrayScaleToAlpha( &surf );
|
|
|
|
for( int j=0; j<2; j++ )
|
|
{
|
|
CString sPageName = m_PagesToGenerate[i].name.GetString();
|
|
switch( j )
|
|
{
|
|
case 0:
|
|
break;
|
|
case 1:
|
|
if( !bExportStrokeTemplates )
|
|
continue;
|
|
sPageName += "-stroke";
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
CString sFile;
|
|
sFile.Format( "%s [%s] %ix%i%s.png",
|
|
sBasePath.GetString(),
|
|
sPageName.GetString(),
|
|
m_apPages[i]->m_iNumFramesX,
|
|
m_apPages[i]->m_iNumFramesY,
|
|
sBitmapAppendBeforeExtension.GetString() );
|
|
|
|
FILE *f = fopen( sFile, "w+b" );
|
|
char szErrorbuf[1024];
|
|
SavePNG( f, szErrorbuf, &surf );
|
|
fclose( f );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FontPage::FontPage()
|
|
{
|
|
m_hPage = NULL;
|
|
m_iFrameWidth = 0;
|
|
m_iFrameHeight = 0;
|
|
}
|
|
|
|
FontPage::~FontPage()
|
|
{
|
|
DeleteObject( m_hPage );
|
|
}
|
|
|
|
void FontPage::Create( unsigned width, unsigned height )
|
|
{
|
|
DeleteObject( m_hPage );
|
|
HDC hDC = GetDC(NULL);
|
|
m_hPage = CreateCompatibleBitmap( hDC, width, height );
|
|
ReleaseDC( NULL, hDC );
|
|
}
|
|
|
|
/*
|
|
* Copyright (c) 2003-2007 Glenn Maynard
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, and/or sell copies of the Software, and to permit persons to
|
|
* whom the Software is furnished to do so, provided that the above
|
|
* copyright notice(s) and this permission notice appear in all copies of
|
|
* the Software and that both the above copyright notice(s) and this
|
|
* permission notice appear in supporting documentation.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
|
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
|
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
* PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|