#include "CFontTool.h" #include "IXMLWriter.h" using namespace irr; const int fontsizes[] = {4,6,8,9,10,11,12,14,16,18,20,22,24,26,28,36,48,56,68,72,0}; inline u32 getTextureSizeFromSurfaceSize(u32 size) { u32 ts = 0x01; while(ts < size) ts <<= 1; return ts; } // windows specific #ifdef _IRR_WINDOWS_ const DWORD charsets[] = { ANSI_CHARSET, DEFAULT_CHARSET, OEM_CHARSET, BALTIC_CHARSET, GB2312_CHARSET, CHINESEBIG5_CHARSET, EASTEUROPE_CHARSET, GREEK_CHARSET, HANGUL_CHARSET, MAC_CHARSET, RUSSIAN_CHARSET, SHIFTJIS_CHARSET, SYMBOL_CHARSET, TURKISH_CHARSET, VIETNAMESE_CHARSET, JOHAB_CHARSET, ARABIC_CHARSET, HEBREW_CHARSET, THAI_CHARSET, 0}; const wchar_t *setnames[] = {L"ANSI", L"All Available", L"OEM", L"Baltic", L"Chinese Simplified", L"Chinese Traditional", L"Eastern European", L"Greek", L"Hangul", L"Macintosh", L"Russian", L"Japanese", L"Symbol", L"Turkish", L"Vietnamese", L"Johab", L"Arabic", L"Hebrew", L"Thai", 0}; // callback for adding font names int CALLBACK EnumFontFamExProc( ENUMLOGFONTEX *lpelfe, NEWTEXTMETRICEX *lpntme, DWORD FontType, LPARAM lParam) { CFontTool* t = (CFontTool*) lParam; t->FontNames.push_back( core::stringw(lpelfe->elfFullName)); return 1; } // // Constructor // CFontTool::CFontTool(IrrlichtDevice* device) : FontSizes(fontsizes), Device(device), UseAlphaChannel(false), // win specific dc(0) { // init display context dc = CreateDC(L"DISPLAY", L"DISPLAY", 0 ,0 ); // populate list of available character set names for (int i=0; setnames[i] != 0; ++i) CharSets.push_back( core::stringw(setnames[i])); selectCharSet(0); } void CFontTool::selectCharSet(u32 currentCharSet) { if ( currentCharSet >= CharSets.size() ) return; LOGFONTW lf; lf.lfFaceName[0] = L'\0'; lf.lfCharSet = (BYTE) charsets[currentCharSet]; // HRESULT hr; // no error checking(!) // clear font list FontNames.clear(); // create list of available fonts EnumFontFamiliesExW( dc, (LPLOGFONTW) &lf, (FONTENUMPROCW) EnumFontFamExProc, (LPARAM) this, 0); } bool CFontTool::makeBitmapFont(u32 fontIndex, u32 charsetIndex, s32 fontSize, u32 textureWidth, u32 textureHeight, bool bold, bool italic, bool aa, bool alpha) { if (fontIndex >= FontNames.size() || charsetIndex >= CharSets.size() ) return false; UseAlphaChannel = alpha; u32 currentImage = 0; // create the font HFONT font = CreateFontW( -MulDiv(fontSize, GetDeviceCaps(dc, LOGPIXELSY), 72), 0, 0,0, bold ? FW_BOLD : 0, italic, 0,0, charsets[charsetIndex], 0,0, aa ? ANTIALIASED_QUALITY : 0, 0, FontNames[fontIndex].c_str() ); if (!font) return false; SelectObject(dc, font); SetTextAlign (dc,TA_LEFT | TA_TOP | TA_NOUPDATECP); // get rid of the current textures/images for (u32 i=0; idrop(); currentTextures.clear(); for (u32 i=0; idrop(); currentImages.clear(); // clear current image mappings CharMap.clear(); // clear array Areas.clear(); // get information about this font's unicode ranges. s32 size = GetFontUnicodeRanges( dc, 0); c8 *buf = new c8[size]; LPGLYPHSET glyphs = (LPGLYPHSET)buf; GetFontUnicodeRanges( dc, glyphs); // s32 TotalCharCount = glyphs->cGlyphsSupported; s32 currentx=0, currenty=0, maxy=0; for (u32 range=0; range < glyphs->cRanges; range++) { WCRANGE* current = &glyphs->ranges[range]; //maxy=0; // loop through each glyph and write its size and position for (s32 ch=current->wcLow; ch< current->wcLow + current->cGlyphs; ch++) { wchar_t currentchar = ch; if ( IsDBCSLeadByte((BYTE) ch)) continue; // surrogate pairs unsupported // get the dimensions SIZE size; ABC abc; GetTextExtentPoint32W(dc, ¤tchar, 1, &size); SFontArea fa; fa.underhang = 0; fa.overhang = 0; if (GetCharABCWidthsW(dc, currentchar, currentchar, &abc)) // for unicode fonts, get overhang, underhang, width { size.cx = abc.abcB; // full font width (ignoring padding/underhang ) fa.underhang = abc.abcA; // underhang/padding left - can also be negative (in which case it's overhang left) fa.overhang = abc.abcC; // overhang/padding right - can also be negative (in which case it's underhand right) if (abc.abcB-abc.abcA+abc.abcC<1) continue; // nothing of width 0 } if (size.cy < 1) continue; //GetGlyphOutline(dc, currentchar, GGO_METRICS, &gm, 0, 0, 0); //size.cx++; size.cy++; // wrap around? if (currentx + size.cx > (s32) textureWidth) { currenty += maxy; currentx = 0; if ((u32)(currenty + maxy) > textureHeight) { currentImage++; // increase Image count currenty=0; } maxy = 0; } // add this char dimension to the current map fa.rectangle = core::rect(currentx, currenty, currentx + size.cx, currenty + size.cy); fa.sourceimage = currentImage; CharMap.insert(currentchar, Areas.size()); Areas.push_back( fa ); currentx += size.cx +1; if (size.cy+1 > maxy) maxy = size.cy+1; } } currenty += maxy; u32 lastTextureHeight = getTextureSizeFromSurfaceSize(currenty); // delete the glyph set delete [] buf; currentImages.set_used(currentImage+1); currentTextures.set_used(currentImage+1); for (currentImage=0; currentImage < currentImages.size(); ++currentImage) { core::stringc logmsg = "Creating image "; logmsg += (s32) (currentImage+1); logmsg += " of "; logmsg += (s32) currentImages.size(); Device->getLogger()->log(logmsg.c_str()); // no need for a huge final texture u32 texHeight = textureHeight; if (currentImage == currentImages.size()-1 ) texHeight = lastTextureHeight; // make a new bitmap HBITMAP bmp = CreateCompatibleBitmap(dc, textureWidth, texHeight); HDC bmpdc = CreateCompatibleDC(dc); LOGBRUSH lbrush; lbrush.lbColor = RGB(0,0,0); lbrush.lbHatch = 0; lbrush.lbStyle = BS_SOLID; HBRUSH brush = CreateBrushIndirect(&lbrush); HPEN pen = CreatePen(PS_NULL, 0, 0); HGDIOBJ oldbmp = SelectObject(bmpdc, bmp); HGDIOBJ oldbmppen = SelectObject(bmpdc, pen); HGDIOBJ oldbmpbrush = SelectObject(bmpdc, brush); HGDIOBJ oldbmpfont = SelectObject(bmpdc, font); SetTextColor(bmpdc, RGB(255,255,255)); Rectangle(bmpdc, 0,0,textureWidth,texHeight); SetBkMode(bmpdc, TRANSPARENT); // draw the letters... // iterate through the tree core::map::Iterator it = CharMap.getIterator(); while (!it.atEnd()) { s32 currentArea = (*it).getValue(); wchar_t wch = (*it).getKey(); // sloppy but I couldn't be bothered rewriting it if (Areas[currentArea].sourceimage == currentImage) { // draw letter s32 sx = Areas[currentArea].rectangle.UpperLeftCorner.X - Areas[currentArea].underhang; TextOutW(bmpdc, sx, Areas[currentArea].rectangle.UpperLeftCorner.Y, &wch, 1); // if ascii font... //SetPixel(bmpdc, Areas[currentArea].rectangle.UpperLeftCorner.X, Areas[currentArea].rectangle.UpperLeftCorner.Y, RGB(255,255,0));// left upper corner mark } it++; } // copy the font bitmap into a new irrlicht image BITMAP b; PBITMAPINFO pbmi; WORD cClrBits; u32 cformat; // Retrieve the bitmap color format, width, and height. GetObject(bmp, sizeof(BITMAP), (LPSTR)&b); // Convert the color format to a count of bits. cClrBits = (WORD)(b.bmPlanes * b.bmBitsPixel); if (cClrBits <= 8) // we're not supporting these cformat = -1; else if (cClrBits <= 16) cformat = video::ECF_A1R5G5B5; else if (cClrBits <= 24) cformat = video::ECF_R8G8B8; else cformat = video::ECF_A8R8G8B8; pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)); // Initialize the fields in the BITMAPINFO structure. pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = b.bmWidth; pbmi->bmiHeader.biHeight = b.bmHeight; pbmi->bmiHeader.biPlanes = b.bmPlanes; pbmi->bmiHeader.biBitCount = b.bmBitsPixel; // If the bitmap is not compressed, set the BI_RGB flag. pbmi->bmiHeader.biCompression = BI_RGB; // Compute the number of bytes in the array of color // indices and store the result in biSizeImage. // For Windows NT, the width must be DWORD aligned unless // the bitmap is RLE compressed. This example shows this. // For Windows 95/98/Me, the width must be WORD aligned unless the // bitmap is RLE compressed. pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8 * pbmi->bmiHeader.biHeight; // Set biClrImportant to 0, indicating that all of the // device colors are important. pbmi->bmiHeader.biClrImportant = 0; LPBYTE lpBits; // memory pointer PBITMAPINFOHEADER pbih = (PBITMAPINFOHEADER) pbmi; lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage); GetDIBits(dc, bmp, 0, (WORD) pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS); // DEBUG- copy to clipboard //OpenClipboard(hWnd); //EmptyClipboard(); //SetClipboardData(CF_BITMAP, bmp); //CloseClipboard(); // flip bitmap s32 rowsize = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8; c8 *row = new c8[rowsize]; for (s32 i=0; i < (pbih->biHeight/2); ++i) { // grab a row memcpy(row, lpBits + (rowsize * i), rowsize); // swap row memcpy(lpBits + (rowsize * i), lpBits + ((pbih->biHeight-1 -i) * rowsize ) , rowsize); memcpy(lpBits + ((pbih->biHeight-1 -i) * rowsize ), row , rowsize); } bool ret = false; if (cformat == video::ECF_A8R8G8B8) { // in this case the font should have an alpha channel, but since windows doesn't draw one // we have to set one manually by going through all the pixels.. *sigh* u8* m; for (m = lpBits; m < lpBits + pbih->biSizeImage; m+=4) { if (UseAlphaChannel) { if (m[0] > 0) // pixel has colour { m[3]=m[0]; // set alpha m[0]=m[1]=m[2] = 255; // everything else is full } } else m[3]=255; // all pixels are full alpha } } else if (cformat == video::ECF_A1R5G5B5) { u8* m; for (m = lpBits; m < lpBits + pbih->biSizeImage; m+=2) { WORD *p = (WORD*)m; if (m[0] > 0 || !UseAlphaChannel) // alpha should be set *p |= 0x8000; // set alpha bit } } else { cformat = -1; } // make a texture from the image if (cformat != -1) { // turn mip-mapping off bool b = Device->getVideoDriver()->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); currentImages[currentImage] = Device->getVideoDriver()->createImageFromData((video::ECOLOR_FORMAT)cformat, core::dimension2d(textureWidth,texHeight), (void*)lpBits); Device->getVideoDriver()->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,b); } else { Device->getLogger()->log("Couldn't create font, your pixel format is unsupported."); } // free memory and windows resources // sloppy I know, but I only intended to create one image at first. delete [] row; LocalFree(pbmi); GlobalFree(lpBits); DeleteDC(bmpdc); DeleteObject(brush); DeleteObject(pen); DeleteObject(bmp); if (currentImages[currentImage]) { // add texture currentTextures[currentImage] = Device->getVideoDriver()->addTexture("GUIFontImage",currentImages[currentImage]); currentTextures[currentImage]->grab(); } else { Device->getLogger()->log("Something went wrong, aborting."); // drop all images DeleteObject(font); return false; } } // looping through each texture DeleteObject(font); return true; } #else CFontTool::CFontTool(IrrlichtDevice *device) : FontSizes(fontsizes), Device(device), UseAlphaChannel(false) { if (!XftInitFtLibrary()) { core::stringc logmsg = "XFT not found\n"; Device->getLogger()->log(logmsg.c_str()); exit(EXIT_FAILURE); } /* Get a list of the font foundries, storing them in a set to sort */ std::set foundries; Display* display = (Display*)Device->getVideoDriver()->getExposedVideoData().OpenGLLinux.X11Display; XftFontSet* fonts = XftListFonts(display, DefaultScreen(display), 0, XFT_FOUNDRY, 0); for (int i = 0; i < fonts->nfont; i++) { char *foundry; XftPatternGetString(fonts->fonts[i], XFT_FOUNDRY, 0, &foundry); core::stringw tmp(foundry); foundries.insert(tmp); } XftFontSetDestroy(fonts); /* Copy the sorted list into the array */ CharSets.clear(); for (std::set::iterator i = foundries.begin(); i != foundries.end(); i++) CharSets.push_back((*i).c_str()); selectCharSet(0); } /* Note: There must be some trick for using strings as pattern parameters to XftListFonts because no matter how I specify a string, I end up with an intermittent segfault. Since XftFontList is just calling FcFontList, that's what I'll do too since that works OK */ void CFontTool::selectCharSet(u32 currentCharSet) { /* Get a list of the font families, storing them in a set to sort */ char foundry[256]; sprintf(&foundry[0],"%ls",CharSets[currentCharSet].c_str()); std::set families; XftPattern *pattern = FcPatternCreate(); XftPatternAddString(pattern, FC_FOUNDRY, &foundry[0]); XftObjectSet *objectset = FcObjectSetCreate(); XftObjectSetAdd(objectset, XFT_FOUNDRY); XftObjectSetAdd(objectset, XFT_FAMILY); FcFontSet *fonts = FcFontList(NULL, pattern, objectset); for (int i = 0; i < fonts->nfont; i++) { char* ptr; XftPatternGetString(fonts->fonts[i], XFT_FAMILY, 0, &ptr); core::stringw family(ptr); families.insert(family); } XftPatternDestroy(pattern); FcObjectSetDestroy(objectset); /* Copy the sorted list into the array */ FontNames.clear(); for (std::set::iterator i = families.begin(); i != families.end(); i++) FontNames.push_back((*i).c_str()); } bool CFontTool::makeBitmapFont(u32 fontIndex, u32 charsetIndex, s32 fontSize, u32 textureWidth, u32 textureHeight, bool bold, bool italic, bool aa, bool alpha) { if (fontIndex >= FontNames.size() || charsetIndex >= CharSets.size() ) return false; Display *display = (Display*) Device->getVideoDriver()->getExposedVideoData().OpenGLLinux.X11Display; u32 screen = DefaultScreen(display); Window win = RootWindow(display, screen); Visual *visual = DefaultVisual(display, screen); UseAlphaChannel = alpha; u32 currentImage = 0; XftResult result; XftPattern *request = XftPatternCreate(); char foundry[256], family[256]; sprintf(&foundry[0],"%ls",CharSets[charsetIndex].c_str()); sprintf(&family[0],"%ls",FontNames[fontIndex].c_str()); XftPatternAddString(request, XFT_FOUNDRY, &foundry[0]); XftPatternAddString(request, XFT_FAMILY, &family[0]); XftPatternAddInteger(request, XFT_PIXEL_SIZE, fontSize); XftPatternAddInteger(request, XFT_WEIGHT, bold ? XFT_WEIGHT_BLACK : XFT_WEIGHT_LIGHT); XftPatternAddInteger(request, XFT_SLANT, italic ? XFT_SLANT_ITALIC : XFT_SLANT_ROMAN); XftPatternAddBool(request, XFT_ANTIALIAS, aa); /* Find the closest font that matches the user choices and open it and check if the returned font has anti aliasing enabled by default, even if it wasn't requested */ FcBool aaEnabled; XftPattern *found = XftFontMatch(display, DefaultScreen(display), request, &result); XftPatternGetBool(found, XFT_ANTIALIAS, 0, &aaEnabled); aa = aaEnabled; XftFont *font = XftFontOpenPattern(display, found); // get rid of the current textures/images for (u32 i=0; idrop(); currentTextures.clear(); for (u32 i=0; idrop(); currentImages.clear(); CharMap.clear(); Areas.clear(); /* Calculate the max height of the font. Annoyingly, it seems that the height property of the font is the maximum height of any single character, but a string of characters, aligned along their baselines, can exceed this figure. Because I don't know any better way of doing it, I'm going to have to use the brute force method. Note: There will be a certain number of charters in a font, however they may not be grouped consecutively, and could in fact be spread out with many gaps */ u32 maxY = 0; u32 charsFound = 0; for (FT_UInt charCode = 0; charsFound < FcCharSetCount(font->charset); charCode++) { if (!XftCharExists(display, font, charCode)) continue; charsFound++; XGlyphInfo extents; XftTextExtents32(display, font, &charCode, 1, &extents); if ((extents.xOff <= 0) && (extents.height <= 0)) continue; /* Calculate the width and height, adding 1 extra pixel if anti aliasing is enabled */ u32 chWidth = extents.xOff + (aa ? 1 : 0); u32 chHeight = (font->ascent - extents.y + extents.height) + (aa ? 1 : 0); if (chHeight > maxY) maxY = chHeight; /* Store the character details here */ SFontArea fontArea; fontArea.rectangle = core::rect(0, 0, chWidth, chHeight); CharMap.insert(charCode, Areas.size()); Areas.push_back(fontArea); } core::stringc logmsg = "Found "; logmsg += (s32) (CharMap.size() + 1); logmsg += " characters"; Device->getLogger()->log(logmsg.c_str()); /* Get the size of the chars and allocate them a position on a texture. If the next character that is added would be outside the width or height of the texture, then a new texture is added */ u32 currentX = 0, currentY = 0, rowY = 0; for (core::map::Iterator it = CharMap.getIterator(); !it.atEnd(); it++) { s32 currentArea = (*it).getValue(); SFontArea *fontArea = &Areas[currentArea]; u32 chWidth = fontArea->rectangle.LowerRightCorner.X; u32 chHeight = fontArea->rectangle.LowerRightCorner.Y; /* If the width of this char will exceed the textureWidth then start a new row */ if ((currentX + chWidth) > textureWidth) { currentY += rowY; currentX = 0; /* If the new row added to the texture exceeds the textureHeight then start a new texture */ if ((currentY + rowY) > textureHeight) { currentImage++; currentY = 0; } rowY = 0; } /* Update the area with the current x and y and texture */ fontArea->rectangle = core::rect(currentX, currentY, currentX + chWidth, currentY + chHeight); fontArea->sourceimage = currentImage; currentX += chWidth + 1; if (chHeight + 1 > rowY) rowY = chHeight + 1; } /* The last row of chars and the last texture weren't accounted for in the loop, so add them here */ currentY += rowY; u32 lastTextureHeight = getTextureSizeFromSurfaceSize(currentY); currentImages.set_used(currentImage + 1); currentTextures.set_used(currentImage + 1); /* Initialise colours */ XftColor colFore, colBack; XRenderColor xFore = {0xffff, 0xffff, 0xffff, 0xffff}; XRenderColor xBack = {0x0000, 0x0000, 0x0000, 0xffff}; XftColorAllocValue(display, DefaultVisual(display, screen), DefaultColormap(display, screen), &xFore, &colFore); XftColorAllocValue(display, DefaultVisual(display, screen), DefaultColormap(display, screen), &xBack, &colBack); /* Create a pixmap that is large enough to hold any character in the font */ Pixmap pixmap = XCreatePixmap(display, win, textureWidth, maxY, DefaultDepth(display, screen)); XftDraw *draw = XftDrawCreate(display, pixmap, visual, DefaultColormap(display, screen)); /* Render the chars */ for (currentImage = 0; currentImage < currentImages.size(); ++currentImage) { core::stringc logmsg = "Creating image "; logmsg += (s32) (currentImage+1); logmsg += " of "; logmsg += (s32) currentImages.size(); Device->getLogger()->log(logmsg.c_str()); /* The last texture that is saved is vertically shrunk to fit the characters drawn on it */ u32 texHeight = textureHeight; if (currentImage == currentImages.size() - 1) texHeight = lastTextureHeight; /* The texture that holds this "page" of characters */ currentImages[currentImage] = Device->getVideoDriver()->createImage(video::ECF_A8R8G8B8, core::dimension2du(textureWidth, texHeight)); currentImages[currentImage]->fill(video::SColor(alpha ? 0 : 255,0,0,0)); for (core::map::Iterator it = CharMap.getIterator(); !it.atEnd(); it++) { FcChar32 wch = (*it).getKey(); s32 currentArea = (*it).getValue(); if (Areas[currentArea].sourceimage == currentImage) { SFontArea *fontArea = &Areas[currentArea]; u32 chWidth = fontArea->rectangle.LowerRightCorner.X - fontArea->rectangle.UpperLeftCorner.X; u32 chHeight = fontArea->rectangle.LowerRightCorner.Y - fontArea->rectangle.UpperLeftCorner.Y; /* Draw the glyph onto the pixmap */ XGlyphInfo extents; XftDrawRect(draw, &colBack, 0, 0, chWidth, chHeight); XftTextExtents32(display, font, &wch, 1, &extents); XftDrawString32(draw, &colFore, font, extents.x, extents.y, &wch, 1); /* Convert the pixmap into an image, then copy it onto the Irrlicht texture, pixel by pixel. There's bound to be a faster way, but this is adequate */ u32 xDest = fontArea->rectangle.UpperLeftCorner.X; u32 yDest = fontArea->rectangle.UpperLeftCorner.Y + font->ascent - extents.y; XImage *image = XGetImage(display, pixmap, 0, 0, chWidth, chHeight, 0xffffff, XYPixmap); if (image) { for (u32 ySrc = 0; ySrc < chHeight; ySrc++) for (u32 xSrc = 0; xSrc < chWidth; xSrc++) { /* Get the pixel colour and break it down into rgb components */ u32 col = XGetPixel(image, xSrc, ySrc); u32 a = 255; u32 r = col & visual->red_mask; u32 g = col & visual->green_mask; u32 b = col & visual->blue_mask; while (r > 0xff) r >>= 8; while (g > 0xff) g >>= 8; while (b > 0xff) b >>= 8; /* To make the background transparent, set the colour to 100% white and the alpha to the average of the three rgb colour components to maintain the anti-aliasing */ if (alpha) { a = (r + g + b) / 3; r = 255; g = 255; b = 255; } currentImages[currentImage]->setPixel(xDest + xSrc,yDest + ySrc,video::SColor(a,r,g,b)); } image->f.destroy_image(image); } } } /* Add the texture to the list */ currentTextures[currentImage] = Device->getVideoDriver()->addTexture("GUIFontImage",currentImages[currentImage]); currentTextures[currentImage]->grab(); } XftColorFree (display, visual, DefaultColormap(display, screen), &colFore); XftColorFree (display, visual, DefaultColormap(display, screen), &colBack); XftFontClose(display,font); XftPatternDestroy(request); XftDrawDestroy(draw); XFreePixmap(display, pixmap); return true; } #endif CFontTool::~CFontTool() { #ifdef _IRR_WINDOWS_ // destroy display context if (dc) DeleteDC(dc); #endif // drop textures+images for (u32 i=0; idrop(); currentTextures.clear(); for (u32 i=0; idrop(); currentImages.clear(); } bool CFontTool::saveBitmapFont(const c8 *filename, const c8* format) { if (currentImages.size() == 0) { Device->getLogger()->log("No image data to write, aborting."); return false; } core::stringc fn = filename; core::stringc imagename = filename; fn += ".xml"; io::IXMLWriter *writer = Device->getFileSystem()->createXMLWriter(fn.c_str()); // header and line breaks writer->writeXMLHeader(); writer->writeLineBreak(); // write information writer->writeElement(L"font", false, L"type", L"bitmap"); writer->writeLineBreak(); writer->writeLineBreak(); // write images and link to them for (u32 i=0; igetVideoDriver()->writeImageToFile(currentImages[i],imagename.c_str()); writer->writeElement(L"Texture", true, L"index", core::stringw(i).c_str(), L"filename", core::stringw(imagename.c_str()).c_str(), L"hasAlpha", UseAlphaChannel ? L"true" : L"false"); writer->writeLineBreak(); } writer->writeLineBreak(); // write each character core::map::Iterator it = CharMap.getIterator(); while (!it.atEnd()) { SFontArea &fa = Areas[(*it).getValue()]; wchar_t c[2]; c[0] = (*it).getKey(); c[1] = L'\0'; core::stringw area, under, over, image; area = core::stringw(fa.rectangle.UpperLeftCorner.X); area += L", "; area += fa.rectangle.UpperLeftCorner.Y; area += L", "; area += fa.rectangle.LowerRightCorner.X; area += L", "; area += fa.rectangle.LowerRightCorner.Y; core::array names; core::array values; names.clear(); values.clear(); // char names.push_back(core::stringw(L"c")); values.push_back(core::stringw(c)); // image number if (fa.sourceimage != 0) { image = core::stringw(fa.sourceimage); names.push_back(core::stringw(L"i")); values.push_back(image); } // rectangle names.push_back(core::stringw(L"r")); values.push_back(area); if (fa.underhang != 0) { under = core::stringw(fa.underhang); names.push_back(core::stringw(L"u")); values.push_back(under); } if (fa.overhang != 0) { over = core::stringw(fa.overhang); names.push_back(core::stringw(L"o")); values.push_back(over); } writer->writeElement(L"c", true, names, values); writer->writeLineBreak(); it++; } writer->writeClosingTag(L"font"); writer->drop(); Device->getLogger()->log("Bitmap font saved."); return true; }