mirror of
https://github.com/spiffcode/hostile-takeover.git
synced 2025-12-23 06:57:23 +00:00
389 lines
8.1 KiB
C++
389 lines
8.1 KiB
C++
#include "ht.h"
|
|
|
|
namespace wi {
|
|
|
|
#define kcchFont 256
|
|
|
|
Font *LoadFont(char *pszFont)
|
|
{
|
|
Font *pfnt = new Font;
|
|
Assert(pfnt != NULL, "out of memory!");
|
|
if (pfnt == NULL)
|
|
return NULL;
|
|
if (!pfnt->Load(pszFont)) {
|
|
delete pfnt;
|
|
return NULL;
|
|
}
|
|
return pfnt;
|
|
}
|
|
|
|
Font::Font()
|
|
{
|
|
m_pfnth = NULL;
|
|
m_mpchpbCodeEven = NULL;
|
|
m_mpchpbCodeOdd = NULL;
|
|
m_nGlyphOverlap = 0;
|
|
m_nLineOverlap = 0;
|
|
m_cxEllipsis = 0;
|
|
}
|
|
|
|
Font::~Font()
|
|
{
|
|
if (m_pfnth != NULL)
|
|
gpakr.UnmapFile(&m_fmap);
|
|
|
|
for (int ch = 0; ch < kcchFont; ch++) {
|
|
if (m_mpchpbCodeEven != NULL) {
|
|
if (m_mpchpbCodeEven[ch] != NULL)
|
|
gmmgr.FreePtr(m_mpchpbCodeEven[ch]);
|
|
}
|
|
if (m_mpchpbCodeOdd != NULL) {
|
|
if (m_mpchpbCodeOdd[ch] != NULL)
|
|
gmmgr.FreePtr(m_mpchpbCodeOdd[ch]);
|
|
}
|
|
}
|
|
if (m_mpchpbCodeEven != NULL)
|
|
gmmgr.FreePtr(m_mpchpbCodeEven);
|
|
if (m_mpchpbCodeOdd != NULL)
|
|
gmmgr.FreePtr(m_mpchpbCodeOdd);
|
|
}
|
|
|
|
bool Font::Load(char *pszFont)
|
|
{
|
|
m_pfnth = (FontHeader *)gpakr.MapFile(pszFont, &m_fmap);
|
|
if (m_pfnth == NULL)
|
|
return false;
|
|
|
|
byte *apbT[kcchFont];
|
|
memset(apbT, 0, sizeof(apbT));
|
|
|
|
m_mpchpbCodeEven = (byte **)gmmgr.AllocPtr(sizeof(byte *) * kcchFont);
|
|
if (m_mpchpbCodeEven == NULL)
|
|
return false;
|
|
gmmgr.WritePtr(m_mpchpbCodeEven, 0, apbT, sizeof(apbT));
|
|
|
|
m_mpchpbCodeOdd = (byte **)gmmgr.AllocPtr(sizeof(byte *) * kcchFont);
|
|
if (m_mpchpbCodeOdd == NULL)
|
|
return false;
|
|
gmmgr.WritePtr(m_mpchpbCodeOdd, 0, apbT, sizeof(apbT));
|
|
|
|
m_cxEllipsis = GetTextExtent("...");
|
|
|
|
return true;
|
|
}
|
|
|
|
int Font::GetTextExtent(const char *psz)
|
|
{
|
|
int cx = 0;
|
|
while (*psz != 0) {
|
|
byte ch = *psz++;
|
|
cx += m_pfnth->acxChar[ch] - m_nGlyphOverlap;
|
|
}
|
|
|
|
// Shadow allows 1 pixel overlap but the last char doesn't overlap
|
|
|
|
return cx + m_nGlyphOverlap;
|
|
}
|
|
|
|
int Font::GetTextExtent(const char *psz, int cch)
|
|
{
|
|
int cx = 0;
|
|
while (cch-- > 0) {
|
|
byte ch = *psz++;
|
|
cx += m_pfnth->acxChar[ch] - m_nGlyphOverlap;
|
|
}
|
|
|
|
// Shadow allows 1 pixel overlap but the last char doesn't overlap
|
|
|
|
return cx + m_nGlyphOverlap;
|
|
}
|
|
|
|
int Font::CalcMultilineHeight(char *psz, int cxMultiline)
|
|
{
|
|
int cy = 0;
|
|
char *pszNext = psz;
|
|
while (pszNext != NULL) {
|
|
char *pszStart = pszNext;
|
|
CalcBreak(cxMultiline, &pszNext);
|
|
cy += BigWord(m_pfnth->cy) - m_nLineOverlap;
|
|
}
|
|
|
|
return cy;
|
|
}
|
|
|
|
void Font::DrawText(DibBitmap *pbm, char *psz, int x, int y, int cx, int cy,
|
|
bool fEllipsis)
|
|
{
|
|
int cyFont = BigWord(m_pfnth->cy) - m_nLineOverlap;
|
|
int cyT = cyFont;
|
|
char *pszNext = psz;
|
|
while (pszNext != NULL) {
|
|
if (cy != -1 && cyT > cy)
|
|
return;
|
|
char *pszStart = pszNext;
|
|
int cch = CalcBreak(cx, &pszNext);
|
|
if (fEllipsis && pszNext != NULL && cy != -1 && cyT + cyFont > cy) {
|
|
DrawTextWithEllipsis(pbm, pszStart, cch, x, y, cx, true);
|
|
} else if (fEllipsis && pszNext == NULL) {
|
|
DrawTextWithEllipsis(pbm, pszStart, cch, x, y, cx, false);
|
|
} else {
|
|
DrawText(pbm, pszStart, x, y, cch);
|
|
}
|
|
y += cyFont;
|
|
cyT += cyFont;
|
|
}
|
|
}
|
|
|
|
void Font::DrawTextWithEllipsis(DibBitmap *pbm, char *psz, int cch, int x,
|
|
int y, int cx, bool fForce)
|
|
{
|
|
if (!fForce) {
|
|
// If not being forced and text fits, draw it without ellipsis
|
|
if (GetTextExtent(psz) < cx) {
|
|
DrawText(pbm, psz, x, y, (int)strlen(psz));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Do a binary search to find out where the ellipsis is needed
|
|
|
|
char szT[256];
|
|
int imin = 0;
|
|
|
|
// Convert to index and ensure room for ellipsis
|
|
int imax = _min(cch - 1, (int)sizeof(szT) - 3 - 1);
|
|
if (imax < 0) {
|
|
imax = 0;
|
|
}
|
|
int icur = imax;
|
|
int ifit = imax;
|
|
while (true) {
|
|
int icurT = imin + (imax - imin) / 2;
|
|
if (icurT == icur) {
|
|
break;
|
|
}
|
|
icur = icurT;
|
|
int cxT = GetTextExtent(psz, icur + 1);
|
|
if (cxT >= cx - m_cxEllipsis) {
|
|
imax = icur - 1;
|
|
} else {
|
|
ifit = icur;
|
|
imin = icur + 1;
|
|
}
|
|
}
|
|
strncpyz(szT, psz, ifit + 2); // convert to count, add one for 0
|
|
strcat(szT, "..."); // this fits without checks
|
|
DrawText(pbm, szT, x, y, (int)strlen(szT));
|
|
}
|
|
|
|
#define IsBreakingChar(ch) ((ch) == ' ' || (ch) == '\t')
|
|
|
|
char *Font::FindNextNonBreakingChar(char *psz)
|
|
{
|
|
while (*psz != 0) {
|
|
if (!IsBreakingChar(*psz))
|
|
return psz;
|
|
psz++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int Font::CalcBreak(int cx, char **ppsz, bool fChop)
|
|
{
|
|
char *pchBreakAfter = NULL;
|
|
int cchBreak;
|
|
int cxT = 0;
|
|
char *pchT = *ppsz;
|
|
int cch = 0;
|
|
bool fFoundBreak = false;
|
|
|
|
while (*pchT != 0) {
|
|
char ch = *pchT;
|
|
|
|
// Handle any combo of \n, \r, \n\r, or \r\n
|
|
|
|
switch (ch) {
|
|
case '\n':
|
|
pchT++;
|
|
if (*pchT == '\r')
|
|
pchT++;
|
|
*ppsz = FindNextNonBreakingChar(pchT);
|
|
return cch;
|
|
|
|
case '\r':
|
|
pchT++;
|
|
if (*pchT == '\n')
|
|
pchT++;
|
|
*ppsz = FindNextNonBreakingChar(pchT);
|
|
return cch;
|
|
}
|
|
|
|
// Otherwise if breaking char, remember break point
|
|
|
|
if (IsBreakingChar(*pchT)) {
|
|
fFoundBreak = true;
|
|
pchBreakAfter = FindNextNonBreakingChar(pchT);
|
|
cchBreak = cch;
|
|
}
|
|
|
|
// At right edge yet?
|
|
|
|
int cxChar = m_pfnth->acxChar[(byte)*pchT] - m_nGlyphOverlap;
|
|
if (cxT + cxChar > cx) {
|
|
// If last break exists, use it. Return pointer skips past break char
|
|
|
|
if (fFoundBreak) {
|
|
*ppsz = pchBreakAfter;
|
|
return cchBreak;
|
|
}
|
|
|
|
// If no last break, sometimes callers want word chopping, sometimes not
|
|
|
|
if (!fChop)
|
|
return 0;
|
|
|
|
// No last break; Scan forward for a breaking char so we know where to start
|
|
// the next line
|
|
|
|
while (*pchT != 0) {
|
|
// Start the next past the break; only draw what fits in cx for this line
|
|
|
|
if (IsBreakingChar(*pchT)) {
|
|
*ppsz = FindNextNonBreakingChar(pchT);
|
|
return cch;
|
|
}
|
|
|
|
// If carriage return, start at next char
|
|
|
|
if (ch == '\n') {
|
|
*ppsz = pchT + 1;
|
|
return cch;
|
|
}
|
|
|
|
pchT++;
|
|
}
|
|
|
|
// At end of line with no break. Return length that fits in cx, and NULL to indicate done.
|
|
|
|
if (*pchT == 0) {
|
|
*ppsz = NULL;
|
|
return cch;
|
|
}
|
|
}
|
|
|
|
// Add char; next char
|
|
|
|
cxT += cxChar;
|
|
cch++;
|
|
pchT++;
|
|
}
|
|
|
|
// Done without hitting edge. Return length and NULL to indicate done
|
|
|
|
*ppsz = NULL;
|
|
return cch;
|
|
}
|
|
|
|
int Font::DrawText(DibBitmap *pbm, char *psz, int x, int y, int cch, dword *mpscaiclr)
|
|
{
|
|
#ifdef DEBUG
|
|
for (int ichT = 0; ichT < cch; ichT++)
|
|
Assert((word)psz[ichT] < (word)kcchFont);
|
|
#endif
|
|
|
|
if (cch == -1)
|
|
cch = (int)strlen(psz);
|
|
|
|
// Clip entire line of text first.
|
|
|
|
Size siz;
|
|
pbm->GetSize(&siz);
|
|
|
|
// Top clip
|
|
|
|
if (y < 0)
|
|
return 0;
|
|
if (y + BigWord(m_pfnth->cy) > siz.cy)
|
|
return 0;
|
|
|
|
// Don't include x clipped chars
|
|
// Left clip
|
|
|
|
int ich = 0;
|
|
int xT = x;
|
|
char *pszT;
|
|
for (pszT = psz; pszT - psz < cch; pszT++) {
|
|
if (xT < 0) {
|
|
ich++;
|
|
xT += m_pfnth->acxChar[(byte)*pszT] - m_nGlyphOverlap;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
int xDst = xT;
|
|
|
|
// Right clip
|
|
|
|
int cchT = 0;
|
|
for (; pszT - psz < cch; pszT++) {
|
|
int cx = m_pfnth->acxChar[(byte)*pszT] - m_nGlyphOverlap;
|
|
if (xT + cx <= siz.cx) {
|
|
cchT++;
|
|
xT += cx;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (cchT == 0)
|
|
return 0;
|
|
|
|
cch = cchT;
|
|
|
|
// Draw
|
|
|
|
xT = xDst;
|
|
byte *pbDst = pbm->GetBits() + (long)y * siz.cx + xT;
|
|
char *pszMax = &psz[ich + cch];
|
|
|
|
for (pszT = &psz[ich]; pszT < pszMax; pszT++) {
|
|
char ch = *pszT;
|
|
int cxChar = m_pfnth->acxChar[(byte)ch];
|
|
if (cxChar == 0)
|
|
continue;
|
|
|
|
byte *pbDraw;
|
|
if (xT & 1) {
|
|
pbDraw = m_mpchpbCodeOdd[(byte)ch];
|
|
} else {
|
|
pbDraw = m_mpchpbCodeEven[(byte)ch];
|
|
}
|
|
|
|
if (pbDraw == NULL) {
|
|
ScanData *psd = (ScanData *)(((byte *)m_pfnth) +
|
|
BigWord(m_pfnth->mpchibsd[(byte)*pszT]));
|
|
word cb = Compile8Thunk(gpbScratch, psd, xT & 1);
|
|
byte *pbT = (byte *)gmmgr.AllocPtr(cb);
|
|
if (pbT != NULL) {
|
|
gmmgr.WritePtr(pbT, 0, gpbScratch, cb);
|
|
if (xT & 1) {
|
|
gmmgr.WritePtr(m_mpchpbCodeOdd, (byte)ch * sizeof(byte *),
|
|
&pbT, sizeof(byte *));
|
|
} else {
|
|
gmmgr.WritePtr(m_mpchpbCodeEven, (byte)ch * sizeof(byte *),
|
|
&pbT, sizeof(byte *));
|
|
}
|
|
}
|
|
pbDraw = gpbScratch;
|
|
}
|
|
|
|
DrawDispatchThunk(pbDraw, NULL, pbDst, siz.cx - cxChar, mpscaiclr,
|
|
gmpiclriclrShadow);
|
|
pbDst += cxChar - m_nGlyphOverlap;
|
|
xT += cxChar - m_nGlyphOverlap;
|
|
}
|
|
|
|
return xT - xDst;
|
|
}
|
|
|
|
} // namespace wi
|