#include "pch.h" ////////////////////////////////////////////////////////////////////////////// // // Fonts // ////////////////////////////////////////////////////////////////////////////// class FontImpl : public IEngineFont { private: ////////////////////////////////////////////////////////////////////////////// // // Types // ////////////////////////////////////////////////////////////////////////////// class CharData { public: WinPoint m_size; int m_offset; }; ////////////////////////////////////////////////////////////////////////////// // // Data members // ////////////////////////////////////////////////////////////////////////////// int m_height; int m_width; CharData m_data[256]; BYTE* m_pdata; int m_dataSize; ////////////////////////////////////////////////////////////////////////////// // // Private members // ////////////////////////////////////////////////////////////////////////////// void FillFontData(HFONT hfont) { int length = 0; int index; // // figure out the total data size // for (index = 0; index < 256; index++) { int xsize = m_data[index].m_size.X(); int ysize = m_data[index].m_size.Y(); m_data[index].m_offset = length; length += ((xsize + 7) / 8) * ysize; } // // Allocate the data // m_dataSize = (length + 3) & (~3); m_pdata = new BYTE[m_dataSize]; // // Create a dib section to get the characters with // class MyHeader { public: BITMAPINFOHEADER bmih; RGBQUAD colors[256]; } bmih; bmih.bmih.biSize = sizeof(BITMAPINFOHEADER); bmih.bmih.biWidth = m_width; bmih.bmih.biHeight = -m_height; bmih.bmih.biPlanes = 1; bmih.bmih.biBitCount = 8; bmih.bmih.biCompression = BI_RGB; bmih.bmih.biSizeImage = 0; bmih.bmih.biXPelsPerMeter = 0; bmih.bmih.biYPelsPerMeter = 0; bmih.bmih.biClrUsed = 0; bmih.bmih.biClrImportant = 0; for (index = 0; index < 256; index++) { bmih.colors[index].rgbBlue = index; bmih.colors[index].rgbGreen = index; bmih.colors[index].rgbRed = index; bmih.colors[index].rgbReserved = 0; } BYTE* pbits; HBITMAP hbitmap = ::CreateDIBSection( NULL, (BITMAPINFO*)&bmih, DIB_RGB_COLORS, (void**)&pbits, NULL, 0 ); ZAssert(hbitmap != NULL); HDC hdcBitmap = ::CreateCompatibleDC(NULL); ZAssert(hdcBitmap != NULL); HBITMAP hbitmapOld = (HBITMAP)::SelectObject(hdcBitmap, hbitmap); ZAssert(hbitmapOld != NULL); HFONT hfontOld = (HFONT)::SelectObject(hdcBitmap, hfont); ZVerify(hfontOld != NULL); ZVerify(::SetBkMode(hdcBitmap, TRANSPARENT)); ZVerify(::SetTextColor(hdcBitmap, Color::White().MakeCOLORREF()) != CLR_INVALID); ZVerify(::SetBkColor (hdcBitmap, Color::Black().MakeCOLORREF()) != CLR_INVALID); // // Get the character bitmaps // BYTE* pdata = m_pdata; int scanBytes = ((m_width + 3) / 4) * 4; for (index = 0; index < 256; index++) { int xsize = m_data[index].m_size.X(); int ysize = m_data[index].m_size.Y(); unsigned char ch = index; ZVerify(::FillRect( hdcBitmap, &(RECT)WinRect(0, 0, m_width, m_height), (HBRUSH)::GetStockObject(BLACK_BRUSH) )); ZVerify(::TextOut(hdcBitmap, 0, 0, (PCC)&ch, 1)); // // pull out the data // int xbytes = (xsize + 7) / 8; for (int yindex = 0; yindex < ysize; yindex++) { for (int xbyte = 0; xbyte < xbytes; xbyte++) { WORD word = 0; for (int xbit = 0; xbit < 8; xbit++) { if (pbits[yindex * scanBytes + xbyte * 8 + xbit] == 0xff) { word |= 0x100; } word >>= 1; } *pdata = (BYTE)word; pdata++; } } } ZAssert(pdata == m_pdata + length); // // Release the DC we created // ZVerify(::SelectObject(hdcBitmap, hfontOld)); ZVerify(::SelectObject(hdcBitmap, hbitmapOld)); ZVerify(::DeleteDC(hdcBitmap)); ZVerify(::DeleteObject(hbitmap)); } ////////////////////////////////////////////////////////////////////////////// // // // ////////////////////////////////////////////////////////////////////////////// void GetSizes(HFONT hfont) { HDC hdc = ::GetDC(NULL); ZVerify(hdc != NULL); HFONT hfontOld = (HFONT)::SelectObject(hdc, hfont); ZVerify(hfontOld != NULL); // // Get the character widths, offsets // m_height = 0; m_width = 0; int index; for (index = 0; index < 256; index++) { SIZE size; unsigned char ch = index; ZVerify(::GetTextExtentPoint( hdc, (PCC)&ch, 1, &size )); m_data[index].m_size = WinPoint(size.cx, size.cy); m_height = max(m_height, size.cy); m_width = max(m_width, size.cx); } // // release the hdc // ZVerify(::SelectObject(hdc, hfontOld)); ZVerify(::ReleaseDC(NULL, hdc)); // // Make that all of the value GDI returned to us are good // for (index = 0; index < 256; index++) { ZAssert(m_data[index].m_size.X() > 0 ); ZAssert(m_data[index].m_size.X() < m_height * 4); ZAssert(m_data[index].m_size.Y() == m_height ); } } ////////////////////////////////////////////////////////////////////////////// // // // ////////////////////////////////////////////////////////////////////////////// public: FontImpl(HFONT hfont) : m_pdata(NULL) { GetSizes(hfont); FillFontData(hfont); ZVerify(::DeleteObject(hfont)); } FontImpl(IBinaryReaderSite* psite) { m_height = psite->GetDWORD(); m_width = psite->GetDWORD(); m_dataSize = psite->GetDWORD(); memcpy(m_data, psite->GetPointer(), sizeof(m_data)); psite->MovePointer(sizeof(m_data)); m_pdata = new BYTE[m_dataSize]; memcpy(m_pdata, psite->GetPointer(), m_dataSize); psite->MovePointer(m_dataSize); } ~FontImpl() { if (m_pdata) { delete m_pdata; } } ////////////////////////////////////////////////////////////////////////////// // // members // ////////////////////////////////////////////////////////////////////////////// void Write(IMDLBinaryFile* pmdlFile) { pmdlFile->WriteReference("ImportFont"); TRef pfile = pmdlFile->WriteBinary(); pfile->Write(m_height); pfile->Write(m_width); pfile->Write(m_dataSize); pfile->Write(m_data, sizeof(m_data)); pfile->Write(m_pdata, m_dataSize); } ////////////////////////////////////////////////////////////////////////////// // // IEngineFont members // ////////////////////////////////////////////////////////////////////////////// int GetHeight() { return m_height; } WinPoint GetTextExtent(const ZString& str) { int count = str.GetLength(); int width = 0; for (int index = 0; index < count; index++) { if (str[index] == START_COLOR_CODE) index += 10 - 1; else if (str[index] == END_COLOR_CODE) index += 2 - 1; else width += m_data[(BYTE)str[index]].m_size.X(); } return WinPoint(width, m_height); } WinPoint GetTextExtent(const char* sz) { int count = strlen(sz); int width = 0; for (int index = 0; index < count; index++) { if (sz[index] == START_COLOR_CODE) index += 10 - 1; else if (sz[index] == END_COLOR_CODE) index += 2 - 1; else width += m_data[(BYTE)sz[index]].m_size.X(); } return WinPoint(width, m_height); } int GetMaxTextLength(const ZString& str, int maxWidth, bool bLeft) { int count = str.GetLength(); int width = 0; for (int index = 0; index < count; index++) { int ichar = bLeft ? index : count - (index + 1); // note that this method is not adjusted to account for colored text, since it // may work backwards across the string, resulting in a difficult case that I // don't think is causing problems right now, ergo no need to fix it. width += m_data[(BYTE)str[ichar]].m_size.X(); if (width > maxWidth) return index; } return count; } void DrawClippedChar( const WinRect& rectClip, byte ch, int ytop, int ysize, int x, BYTE* pdestTop, WORD pixel, int pitchDest ) { const CharData& charData = m_data[ch]; int xsizeChar = charData.m_size.X(); // // figure out the left and right clipping // int xleft; if (x < rectClip.XMin()) { xleft = rectClip.XMin() - x; } else { xleft = 0; } int xright; if (x + xsizeChar > rectClip.XMax()) { xright = rectClip.XMax() - x; } else { xright = xsizeChar; } // // pointers // int bytesSource = (xsizeChar + 7) / 8; BYTE* psourceStart = m_pdata + charData.m_offset + bytesSource * ytop; BYTE* pdestStart = pdestTop + (x + xleft) * 2; // // copy bits between xleft and xright // for (int scans = ysize; scans > 0; scans--) { BYTE* psource = psourceStart; BYTE* pdest = pdestStart; int ibit = 0; BYTE byte = *psource; psource++; // // skip clipped left bits // while (ibit < xleft) { byte = byte >> 1; ibit++; if (ibit & 0x7 == 0) { byte = *psource; psource++; } } // // render bits between xleft and xright // while (ibit < xright) { if (byte & 1) { *(WORD*)pdest = pixel; } pdest += 2; byte = byte >> 1; ibit += 1; if (ibit & 0x7 == 0) { byte = *psource; psource++; } } psourceStart += bytesSource; pdestStart += pitchDest; } } void DrawString( Surface* psurface, const WinPoint& point, const WinRect& rectClip, const ZString& str, const Color& color ) { if (str.GetLength() == 0) { return; } // // are we clipped in the y direction? // int ytop = 0; int ybottom = m_height; // // is the top clipped? // if (point.Y() < rectClip.YMin()) { ytop = rectClip.YMin() - point.Y(); } // // is the bottom clipped? // if (point.Y() + m_height > rectClip.YMax()) { ybottom = rectClip.YMax() - point.Y(); } // // figure out the total number of scan lines to draw // int ysize = ybottom - ytop; if (ysize <= 0) { // // clipped in y, nothing to draw // return; } // // Get a pointer to the destination memory // BYTE* pdestTop = psurface->GetWritablePointer() + (point.Y() + ytop) * psurface->GetPitch(); // // All access to the surface needs to be inside an exception handler // __try { int ichar = 0; int x = point.X (); DrawStringHandled( psurface, x, rectClip, str, color, pdestTop, ytop, ybottom, ysize, ichar ); } __except (true) { // // DDraw took away our access to some video memory. Ignore the exception // and continue. // } psurface->ReleasePointer(); } int HexDigitToInt (int hexDigit) { if (isdigit (hexDigit)) return hexDigit - '0'; else if (isxdigit (hexDigit)) return (tolower (hexDigit) - 'a') + 10; else return -1; } int ReadHexPair (const ZString& str, int& ichar) { int highNibble = HexDigitToInt (str[ichar++]); int lowNibble = HexDigitToInt (str[ichar++]); assert ((highNibble >= 0) && (lowNibble >= 0)); return ((highNibble << 4) | lowNibble); } Color ReadColor (const ZString& str, int& ichar) { float red = ReadHexPair (str, ichar) / 255.0f; float green = ReadHexPair (str, ichar) / 255.0f; float blue = ReadHexPair (str, ichar) / 255.0f; float alpha = ReadHexPair (str, ichar) / 255.0f; return Color (red, green, blue, alpha); } void DrawColorCodedString( Surface* psurface, int& x, const WinRect& rectClip, const ZString& str, BYTE* pdestTop, int ytop, int ybottom, int ysize, int& ichar ) { // we started here because we encountered a color code, // so the first thing is to skip over the color code and // the required following character ichar += 2; // next we need to read the color, in the form rrggbbaa, // where each pair of digits is Color nextColor = ReadColor (str, ichar); DrawStringHandled (psurface, x, rectClip, str, nextColor, pdestTop, ytop, ybottom, ysize, ichar); } void DrawStringHandled( Surface* psurface, int& x, const WinRect& rectClip, const ZString& str, const Color& color, BYTE* pdestTop, int ytop, int ybottom, int ysize, int& ichar ) { int length = str.GetLength(); // // get all the surface info // PixelFormat* ppf = psurface->GetPixelFormat(); int pixelBytes = ppf->PixelBytes(); WinPoint sizeDest = psurface->GetSize(); int pitchDest = psurface->GetPitch(); WORD pixel = (WORD)(ppf->MakePixel(color).Value()); ZAssert(pixelBytes == 2); // // loop through characters that are clipped on the left // bool loop = true; while (loop) { BYTE ch = str[ichar]; switch (ch) { case START_COLOR_CODE: DrawColorCodedString (psurface, x, rectClip, str, pdestTop, ytop, ybottom, ysize, ichar); if (ichar >= length) return; break; case END_COLOR_CODE: ichar += 2; return; default: { const CharData& charData = m_data[ch]; int xsizeChar = charData.m_size.X(); if ((x + xsizeChar) > rectClip.XMin()) { loop = false; break; } if (++ichar >= length) return; x += xsizeChar; } break; } } // // there can be one character clipped on the left // if (x < rectClip.XMin()) { DrawClippedChar(rectClip, str[ichar], ytop, ysize, x, pdestTop, pixel, pitchDest); if (++ichar >= length) return; x += m_data[(BYTE)str[ichar]].m_size.X(); } // // loop through unclipped characters // loop = true; while (loop) { BYTE ch = str[ichar]; switch (ch) { case START_COLOR_CODE: DrawColorCodedString (psurface, x, rectClip, str, pdestTop, ytop, ybottom, ysize, ichar); if (ichar >= length) return; break; case END_COLOR_CODE: ichar += 2; return; default: { const CharData& charData = m_data[ch]; int xsizeChar = charData.m_size.X(); if ((x + xsizeChar) > rectClip.XMax()) { loop = false; break; } int scans = ysize; int bytesSource = (xsizeChar + 7) / 8; BYTE* psource = m_pdata + charData.m_offset + (bytesSource * ytop); BYTE* pdest = pdestTop + x * 2; // // transparent blt 1bpp -> 16bpp // _asm { mov esi, psource mov ax, pixel mov ecx, pdest yloop: mov edi, ecx mov ebx, [bytesSource] byteLoop: mov dl, [esi] inc esi shr dl, 1 jnc skipbit1 mov [edi + 0], ax skipbit1: shr dl, 1 jnc skipbit2 mov [edi + 2], ax skipbit2: shr dl, 1 jnc skipbit3 mov [edi + 4], ax skipbit3: shr dl, 1 jnc skipbit4 mov [edi + 6], ax skipbit4: shr dl, 1 jnc skipbit5 mov [edi + 8], ax skipbit5: shr dl, 1 jnc skipbit6 mov [edi + 10], ax skipbit6: shr dl, 1 jnc skipbit7 mov [edi + 12], ax skipbit7: shr dl, 1 jnc skipbit8 mov [edi + 14], ax skipbit8: add edi, 16 dec ebx jne byteLoop add ecx, [pitchDest] dec [scans] jne yloop } if (++ichar >= length) return; x += xsizeChar; } break; } } // // one character can be clipped on the right // if (x < rectClip.XMax()) DrawClippedChar(rectClip, str[ichar], ytop, ysize, x, pdestTop, pixel, pitchDest); } }; TRef CreateEngineFont(HFONT hfont) { return new FontImpl(hfont); } TRef CreateEngineFont(IBinaryReaderSite* psite) { return new FontImpl(psite); } ////////////////////////////////////////////////////////////////////////////// // // Color code utility function // ////////////////////////////////////////////////////////////////////////////// ZString ConvertColorToString (const Color& color) { char buffer[9] = {0}; sprintf (buffer, "%02x%02x%02x%02x", int (color.R () * 255.0f), int (color.G () * 255.0f), int (color.B () * 255.0f), int (color.A () * 255.0f) ); return ZString (buffer); } ////////////////////////////////////////////////////////////////////////////// // // Fonts // ////////////////////////////////////////////////////////////////////////////// ZString GetString(int indent, TRef pfont) { ZUnimplemented(); return "font"; } void Write(IMDLBinaryFile* pmdlFile, TRef pfont) { FontImpl* pimpl; CastTo(pimpl, pfont); pimpl->Write(pmdlFile); } ZString GetFunctionName(const TRef& value) { return "ImportFont"; } ////////////////////////////////////////////////////////////////////////////// // // Alternate blting implementations // ////////////////////////////////////////////////////////////////////////////// /* version 1 _asm { mov edi, pdest mov esi, psource mov ax, pixel yloop: mov ebx, bytesSource and ebx, ebx je extraLoop byteLoop: mov dl, [esi] inc esi mov ecx, 8 bitloop1: shr dl, 1 jnc bitloop1Skip mov [edi], ax bitloop1Skip: add edi, 2 dec ecx jne bitloop1 dec ebx jne byteLoop extraLoop: mov ecx, extraSource and ecx, ecx je end mov dl, [esi] inc esi bitloop2: shr dl, 1 jnc bitloop2Skip mov [edi], ax bitloop2Skip: add edi, 2 dec ecx jne bitloop2 end: add edi, [scanDeltaDest] dec [ysizeSource] jne yloop } */ /* this doesn't work yet. Try to use a jump table dependant on the number of bits static void* bitJumps[8] = { bits8, bits1, bits2, bits3, bits4, bits5, bits6, bits7 }; _asm { mov esi, psource mov ax, pixel mov ecx, pdest ayloop: mov edi, ecx // // eax pixel // ebx temporary // ecx pdestScanLine // dl bits // edi destination // esi source // ebp // byteLoop: mov dl, [esi] inc esi mov ebx, [xsize] cmp ebx, 8 jg bits8 and ebx, 0x7 jmp [bitJumps + ecx] bits8: shl dl, 1 jnc bits7 mov [edi + 14], ax bits7: shl dl, 1 jnc bits6 mov [edi + 12], ax bits6: shl dl, 1 jnc bits5 mov [edi + 10], ax bits5: shl dl, 1 jnc bits4 mov [edi + 8], ax bits4: shl dl, 1 jnc bits3 mov [edi + 6], ax bits3: shl dl, 1 jnc bits2 mov [edi + 4], ax bits2: shl dl, 1 jnc bits1 mov [edi + 2], ax bits1: shl dl, 1 jnc bits1 mov [edi + 0], ax add edi, 16 dec [xsize] jne byteLoop add ecx, [pitchDest] dec [ysizeSource] jne ayloop } */ /* do { // // Copy the byte sized chunks // { for (int iBytes = bytesSource; iBytes > 0; iBytes--) { BYTE byte = *psource; psource++; for (int index = 8; index > 0; index--) { if (byte & 1) { *(WORD*)pdest = pixel; } byte >>= 1; pdest += 2; } } } // // Copy the last few bits // if (extraSource != 0) { BYTE byte = *psource; psource++; for (int index = extraSource; index > 0; index--) { if (byte & 1) { *(WORD*)pdest = pixel; } byte >>= 1; pdest += 2; } } // // Next scan line // pdest += scanDeltaDest; psource += scanDeltaSource; ysizeSource -= 1; } while (ysizeSource > 0); */