gFont.cpp 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "gfx/gFont.h"
  24. #include "core/resourceManager.h"
  25. #include "core/stream/fileStream.h"
  26. #include "core/strings/unicode.h"
  27. #include "core/strings/findMatch.h"
  28. #include "core/strings/stringFunctions.h"
  29. #include "core/util/endian.h"
  30. #include "core/util/safeDelete.h"
  31. #include "console/console.h"
  32. #include "console/engineAPI.h"
  33. #include "platform/threads/mutex.h"
  34. #include "zlib/zlib.h"
  35. GFX_ImplementTextureProfile(GFXFontTextureProfile,
  36. GFXTextureProfile::DiffuseMap,
  37. GFXTextureProfile::PreserveSize |
  38. GFXTextureProfile::Static |
  39. GFXTextureProfile::KeepBitmap |
  40. GFXTextureProfile::NoMipmap,
  41. GFXTextureProfile::NONE);
  42. template<> void *Resource<GFont>::create(const Torque::Path &path)
  43. {
  44. #ifdef TORQUE_DEBUG_RES_MANAGER
  45. Con::printf( "Resource<GFont>::create - [%s]", path.getFullPath().c_str() );
  46. #endif
  47. return GFont::load( path );
  48. }
  49. template<> ResourceBase::Signature Resource<GFont>::signature()
  50. {
  51. return MakeFourCC('f','o','n','t');
  52. }
  53. /// Used for repacking in GFont::importStrip.
  54. struct GlyphMap
  55. {
  56. U32 charId;
  57. GBitmap *bitmap;
  58. };
  59. static S32 QSORT_CALLBACK GlyphMapCompare(const void *a, const void *b)
  60. {
  61. S32 ha = ((GlyphMap *) a)->bitmap->getHeight();
  62. S32 hb = ((GlyphMap *) b)->bitmap->getHeight();
  63. return hb - ha;
  64. }
  65. const U32 GFont::csm_fileVersion = 3;
  66. String GFont::getFontCacheFilename(const String &faceName, U32 size)
  67. {
  68. return String::ToString("%s/%s %d (%s).uft",
  69. Con::getVariable("$GUI::fontCacheDirectory"), faceName.c_str(), size, getCharSetName(0));
  70. }
  71. GFont* GFont::load( const Torque::Path& path )
  72. {
  73. FileStream stream;
  74. stream.open( path.getFullPath(), Torque::FS::File::Read );
  75. if ( stream.getStatus() != Stream::Ok )
  76. return NULL;
  77. GFont *ret = new GFont;
  78. ret->mGFTFile = path;
  79. if(!ret->read(stream))
  80. {
  81. Con::errorf( "GFont::load - error reading '%s'", path.getFullPath().c_str() );
  82. SAFE_DELETE(ret);
  83. }
  84. else
  85. {
  86. PlatformFont *platFont = createPlatformFont(ret->getFontFaceName(), ret->getFontSize(), ret->getFontCharSet());
  87. if ( platFont == NULL )
  88. {
  89. Con::errorf( "GFont::load - error creating platform font for '%s'", path.getFullPath().c_str() );
  90. SAFE_DELETE(ret);
  91. }
  92. else
  93. ret->setPlatformFont(platFont);
  94. }
  95. return ret;
  96. }
  97. Resource<GFont> GFont::create(const String &faceName, U32 size, const char *cacheDirectory, U32 charset /* = TGE_ANSI_CHARSET */)
  98. {
  99. if( !cacheDirectory )
  100. cacheDirectory = Con::getVariable( "$GUI::fontCacheDirectory" );
  101. const Torque::Path path( String::ToString("%s/%s %d (%s).uft",
  102. cacheDirectory, faceName.c_str(), size, getCharSetName(charset)) );
  103. Resource<GFont> ret;
  104. // If the file already exists attempt to load it
  105. if (Torque::FS::IsFile(path.getFullPath().c_str()))
  106. {
  107. ret = ResourceManager::get().load(path);
  108. if (ret != NULL)
  109. {
  110. ret->mGFTFile = path;
  111. return ret;
  112. }
  113. }
  114. // Otherwise attempt to have the platform generate a new font
  115. PlatformFont *platFont = createPlatformFont(faceName, size, charset);
  116. if (platFont == NULL)
  117. {
  118. String fontName;
  119. #ifdef _XBOX
  120. //AssertFatal( false, "Font creation is not supported on the Xbox platform. Create the font files (*.uft) using the Windows/MacOS build of the project." );
  121. if(!faceName.equal("arial", String::NoCase) || size != 14)
  122. {
  123. return create("Arial", 14, cacheDirectory, charset);
  124. }
  125. return ret;
  126. #endif
  127. // Couldn't load the requested font. This probably will be common
  128. // since many unix boxes don't have arial or lucida console installed.
  129. // Attempt to map the font name into a font we're pretty sure exist
  130. // Lucida Console is a common code & console font on windows, and
  131. // Monaco is the recommended code & console font on mac.
  132. if (faceName.equal("arial", String::NoCase))
  133. fontName = "Helvetica";
  134. else if (faceName.equal("lucida console", String::NoCase))
  135. fontName = "Monaco";
  136. else if (faceName.equal("monaco", String::NoCase))
  137. fontName = "Courier";
  138. else
  139. return ret;
  140. return create(fontName, size, cacheDirectory, charset);
  141. }
  142. // Create the actual GFont and set some initial properties
  143. GFont *font = new GFont;
  144. font->mPlatformFont = platFont;
  145. font->mGFTFile = path;
  146. font->mFaceName = faceName;
  147. font->mSize = size;
  148. font->mCharSet = charset;
  149. font->mHeight = platFont->getFontHeight();
  150. font->mBaseline = platFont->getFontBaseLine();
  151. font->mAscent = platFont->getFontBaseLine();
  152. font->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine();
  153. // Flag it to save when we exit
  154. font->mNeedSave = true;
  155. // Load the newly created font into the ResourceManager
  156. ret.setResource(ResourceManager::get().load(path), font);
  157. return ret;
  158. }
  159. //-------------------------------------------------------------------------
  160. GFont::GFont()
  161. {
  162. VECTOR_SET_ASSOCIATION(mCharInfoList);
  163. VECTOR_SET_ASSOCIATION(mTextureSheets);
  164. std::fill_n(mRemapTable, Font_Table_MAX,-1);
  165. mCurX = mCurY = mCurSheet = -1;
  166. mPlatformFont = NULL;
  167. mSize = 0;
  168. mCharSet = 0;
  169. mHeight = 0;
  170. mBaseline = 0;
  171. mAscent = 0;
  172. mDescent = 0;
  173. mNeedSave = false;
  174. mMutex = Mutex::createMutex();
  175. }
  176. GFont::~GFont()
  177. {
  178. if(mNeedSave)
  179. {
  180. AssertFatal( mGFTFile.getFullPath().isNotEmpty(), "GFont::~GFont - path not set" );
  181. FileStream stream;
  182. stream.open(mGFTFile, Torque::FS::File::Write);
  183. if(stream.getStatus() == Stream::Ok)
  184. write(stream);
  185. stream.close();
  186. }
  187. S32 i;
  188. for(i = 0;i < mCharInfoList.size();i++)
  189. {
  190. SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData);
  191. }
  192. for(i=0; i<mTextureSheets.size(); i++)
  193. mTextureSheets[i] = NULL;
  194. SAFE_DELETE(mPlatformFont);
  195. Mutex::destroyMutex(mMutex);
  196. }
  197. void GFont::dumpInfo() const
  198. {
  199. // Number and extent of mapped characters?
  200. U32 mapCount = 0, mapBegin=0xFFFF, mapEnd=0;
  201. for(U32 i=0; i<0x10000; i++)
  202. {
  203. if(mRemapTable[i] != -1)
  204. {
  205. mapCount++;
  206. if(i<mapBegin) mapBegin = i;
  207. if(i>mapEnd) mapEnd = i;
  208. }
  209. }
  210. // Let's write out all the info we can on this font.
  211. Con::printf(" '%s' %dpt", mFaceName.c_str(), mSize);
  212. Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount);
  213. if(mapCount)
  214. Con::printf(" - Codepoints range from 0x%x to 0x%x.", mapBegin, mapEnd);
  215. else
  216. Con::printf(" - No mapped codepoints.");
  217. Con::printf(" - Platform font is %s.", (mPlatformFont ? "present" : "not present") );
  218. }
  219. //-----------------------------------------------------------------------------
  220. bool GFont::loadCharInfo(const UTF16 ch)
  221. {
  222. PROFILE_SCOPE(GFont_loadCharInfo);
  223. if(mRemapTable[ch] != -1)
  224. return true; // Not really an error
  225. if(mPlatformFont && mPlatformFont->isValidChar(ch))
  226. {
  227. Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes.
  228. PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch);
  229. if(ci.bitmapData)
  230. addBitmap(ci);
  231. mCharInfoList.push_back(ci);
  232. mRemapTable[ch] = mCharInfoList.size() - 1;
  233. mNeedSave = true;
  234. Mutex::unlockMutex(mMutex);
  235. return true;
  236. }
  237. return false;
  238. }
  239. void GFont::addBitmap(PlatformFont::CharInfo &charInfo)
  240. {
  241. U32 nextCurX = U32(mCurX + charInfo.width ); /*7) & ~0x3;*/
  242. U32 nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  243. // These are here for postmortem debugging.
  244. bool routeA = false, routeB = false;
  245. if(mCurSheet == -1 || nextCurY >= TextureSheetSize)
  246. {
  247. routeA = true;
  248. addSheet();
  249. // Recalc our nexts.
  250. nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
  251. nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  252. }
  253. if( nextCurX >= TextureSheetSize)
  254. {
  255. routeB = true;
  256. mCurX = 0;
  257. mCurY = nextCurY;
  258. // Recalc our nexts.
  259. nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
  260. nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  261. }
  262. // Check the Y once more - sometimes we advance to a new row and run off
  263. // the end.
  264. if(nextCurY >= TextureSheetSize)
  265. {
  266. routeA = true;
  267. addSheet();
  268. // Recalc our nexts.
  269. nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3;
  270. nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3;
  271. }
  272. charInfo.bitmapIndex = mCurSheet;
  273. charInfo.xOffset = mCurX;
  274. charInfo.yOffset = mCurY;
  275. mCurX = nextCurX;
  276. S32 x, y;
  277. GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap();
  278. AssertFatal(bmp->getFormat() == GFXFormatA8, "GFont::addBitmap - cannot added characters to non-greyscale textures!");
  279. for(y = 0;y < charInfo.height;y++)
  280. for(x = 0;x < charInfo.width;x++)
  281. *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x];
  282. mTextureSheets[mCurSheet].refresh();
  283. }
  284. void GFont::addSheet()
  285. {
  286. GBitmap *bitmap = new GBitmap(TextureSheetSize, TextureSheetSize, false, GFXFormatA8 );
  287. // Set everything to transparent.
  288. U8 *bits = bitmap->getWritableBits();
  289. dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize);
  290. GFXTexHandle handle = GFXTexHandle( bitmap, &GFXFontTextureProfile, true, avar("%s() - (line %d)", __FUNCTION__, __LINE__) );
  291. mTextureSheets.increment();
  292. mTextureSheets.last() = handle;
  293. mCurX = 0;
  294. mCurY = 0;
  295. mCurSheet = mTextureSheets.size() - 1;
  296. }
  297. //-----------------------------------------------------------------------------
  298. const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex)
  299. {
  300. PROFILE_SCOPE(GFont_getCharInfo);
  301. AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!");
  302. if(mRemapTable[in_charIndex] == -1)
  303. loadCharInfo(in_charIndex);
  304. AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character");
  305. return mCharInfoList[mRemapTable[in_charIndex]];
  306. }
  307. const PlatformFont::CharInfo &GFont::getDefaultCharInfo()
  308. {
  309. static PlatformFont::CharInfo c;
  310. // c is initialized by the CharInfo default constructor.
  311. return c;
  312. }
  313. //-----------------------------------------------------------------------------
  314. U32 GFont::getStrWidth(const UTF8* in_pString)
  315. {
  316. AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
  317. // If we ain't running debug...
  318. if (in_pString == NULL || *in_pString == '\0')
  319. return 0;
  320. return getStrNWidth(in_pString, dStrlen(in_pString));
  321. }
  322. U32 GFont::getStrWidthPrecise(const UTF8* in_pString)
  323. {
  324. AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined");
  325. // If we ain't running debug...
  326. if (in_pString == NULL)
  327. return 0;
  328. return getStrNWidthPrecise(in_pString, dStrlen(in_pString));
  329. }
  330. //-----------------------------------------------------------------------------
  331. U32 GFont::getStrNWidth(const UTF8 *str, U32 n)
  332. {
  333. // UTF8 conversion is expensive. Avoid converting in a tight loop.
  334. FrameTemp<UTF16> str16(n + 1);
  335. convertUTF8toUTF16N(str, str16, n + 1);
  336. return getStrNWidth(str16, dStrlen(str16));
  337. }
  338. U32 GFont::getStrNWidth(const UTF16 *str, U32 n)
  339. {
  340. AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
  341. if (str == NULL || str[0] == '\0' || n == 0)
  342. return 0;
  343. U32 totWidth = 0;
  344. UTF16 curChar;
  345. U32 charCount;
  346. for(charCount = 0; charCount < n; charCount++)
  347. {
  348. curChar = str[charCount];
  349. if(curChar == '\0')
  350. break;
  351. if(isValidChar(curChar))
  352. {
  353. const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
  354. totWidth += rChar.xIncrement;
  355. }
  356. else if (curChar == dT('\t'))
  357. {
  358. const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
  359. totWidth += rChar.xIncrement * TabWidthInSpaces;
  360. }
  361. }
  362. return(totWidth);
  363. }
  364. U32 GFont::getStrNWidthPrecise(const UTF8 *str, U32 n)
  365. {
  366. FrameTemp<UTF16> str16(n + 1);
  367. convertUTF8toUTF16N(str, str16, n + 1);
  368. return getStrNWidthPrecise(str16, dStrlen(str16));
  369. }
  370. U32 GFont::getStrNWidthPrecise(const UTF16 *str, U32 n)
  371. {
  372. AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL");
  373. if (str == NULL || str[0] == '\0' || n == 0)
  374. return(0);
  375. U32 totWidth = 0;
  376. UTF16 curChar;
  377. U32 charCount = 0;
  378. for(charCount = 0; charCount < n; charCount++)
  379. {
  380. curChar = str[charCount];
  381. if(curChar == '\0')
  382. break;
  383. if(isValidChar(curChar))
  384. {
  385. const PlatformFont::CharInfo& rChar = getCharInfo(curChar);
  386. totWidth += rChar.xIncrement;
  387. }
  388. else if (curChar == dT('\t'))
  389. {
  390. const PlatformFont::CharInfo& rChar = getCharInfo(dT(' '));
  391. totWidth += rChar.xIncrement * TabWidthInSpaces;
  392. }
  393. }
  394. UTF16 endChar = str[getMin(charCount,n-1)];
  395. if (isValidChar(endChar))
  396. {
  397. const PlatformFont::CharInfo& rChar = getCharInfo(endChar);
  398. if (rChar.width != rChar.xIncrement)
  399. totWidth += (rChar.width - rChar.xIncrement);
  400. }
  401. return(totWidth);
  402. }
  403. U32 GFont::getBreakPos(const UTF16 *str16, U32 slen, U32 width, bool breakOnWhitespace)
  404. {
  405. // Some early out cases.
  406. if(slen==0)
  407. return 0;
  408. U32 ret = 0;
  409. U32 lastws = 0;
  410. UTF16 c;
  411. U32 charCount = 0;
  412. for( charCount=0; charCount < slen; charCount++)
  413. {
  414. c = str16[charCount];
  415. if(c == '\0')
  416. break;
  417. if(c == dT('\t'))
  418. c = dT(' ');
  419. if(!isValidChar(c))
  420. {
  421. ret++;
  422. continue;
  423. }
  424. if(c == dT(' '))
  425. lastws = ret+1;
  426. const PlatformFont::CharInfo& rChar = getCharInfo(c);
  427. if(rChar.width > width || rChar.xIncrement > width)
  428. {
  429. if(lastws && breakOnWhitespace)
  430. return lastws;
  431. return ret;
  432. }
  433. width -= rChar.xIncrement;
  434. ret++;
  435. }
  436. return ret;
  437. }
  438. void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector<U32> &startLineOffset, Vector<U32> &lineLen)
  439. {
  440. // TODO: Is this error still true?
  441. //Con::errorf("GFont::wrapString(): Not yet converted to be UTF-8 safe");
  442. startLineOffset.clear();
  443. lineLen.clear();
  444. if (!txt || !txt[0] || lineWidth < getCharWidth('W')) //make sure the line width is greater then a single character
  445. return;
  446. U32 len = dStrlen(txt);
  447. U32 startLine;
  448. for (U32 i = 0; i < len;)
  449. {
  450. U32 wide = 0;
  451. startLine = i;
  452. startLineOffset.push_back(startLine);
  453. // loop until the string is too large
  454. bool needsNewLine = false;
  455. U32 lineStrWidth = 0;
  456. for (; i < len; i++)
  457. {
  458. if( txt[ i ] == '\n' )
  459. {
  460. needsNewLine = true;
  461. break;
  462. }
  463. else if(isValidChar(txt[i]))
  464. {
  465. lineStrWidth += getCharInfo(txt[i]).xIncrement;
  466. if(txt[i] < 0) // symbols which code > 127
  467. {
  468. wide++; i++;
  469. }
  470. if( lineStrWidth > lineWidth )
  471. {
  472. needsNewLine = true;
  473. break;
  474. }
  475. }
  476. }
  477. if (!needsNewLine)
  478. {
  479. // we are done!
  480. lineLen.push_back(i - startLine - wide);
  481. return;
  482. }
  483. S32 j;
  484. // Did we hit a hardwrap (newline character) in the string.
  485. bool hardwrap = ( txt[i] == '\n' );
  486. if ( hardwrap )
  487. {
  488. j = i;
  489. }
  490. // determine where to put the newline
  491. // we need to backtrack until we find a space character
  492. // we don't do this for hardwrap(s)
  493. else
  494. {
  495. for (j = i - 1; j >= startLine; j--)
  496. {
  497. if ( dIsspace(txt[j]) || txt[i] == '\n' )
  498. break;
  499. }
  500. if (j < startLine)
  501. {
  502. // the line consists of a single word!
  503. // So, just break up the word
  504. j = i - 1;
  505. }
  506. }
  507. lineLen.push_back(j - startLine - wide);
  508. i = j;
  509. // Now we need to increment through any space characters at the
  510. // beginning of the next line.
  511. // We don't skip spaces after a hardwrap because they were obviously intended.
  512. for (i++; i < len; i++)
  513. {
  514. if ( txt[i] == '\n' )
  515. continue;
  516. if ( dIsspace( txt[i] ) && !hardwrap )
  517. continue;
  518. break;
  519. }
  520. }
  521. }
  522. //-----------------------------------------------------------------------------
  523. bool GFont::read(Stream& io_rStream)
  524. {
  525. // Handle versioning
  526. U32 version;
  527. io_rStream.read(&version);
  528. if(version != csm_fileVersion)
  529. return false;
  530. char buf[256];
  531. io_rStream.readString(buf);
  532. mFaceName = buf;
  533. io_rStream.read(&mSize);
  534. io_rStream.read(&mCharSet);
  535. io_rStream.read(&mHeight);
  536. io_rStream.read(&mBaseline);
  537. io_rStream.read(&mAscent);
  538. io_rStream.read(&mDescent);
  539. U32 size = 0;
  540. io_rStream.read(&size);
  541. mCharInfoList.setSize(size);
  542. U32 i;
  543. for(i = 0; i < size; i++)
  544. {
  545. PlatformFont::CharInfo *ci = &mCharInfoList[i];
  546. io_rStream.read(&ci->bitmapIndex);
  547. io_rStream.read(&ci->xOffset);
  548. io_rStream.read(&ci->yOffset);
  549. io_rStream.read(&ci->width);
  550. io_rStream.read(&ci->height);
  551. io_rStream.read(&ci->xOrigin);
  552. io_rStream.read(&ci->yOrigin);
  553. io_rStream.read(&ci->xIncrement);
  554. ci->bitmapData = NULL;
  555. }
  556. U32 numSheets = 0;
  557. io_rStream.read(&numSheets);
  558. for(i = 0; i < numSheets; i++)
  559. {
  560. GBitmap *bmp = new GBitmap;
  561. /*String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet));
  562. if(!bmp->readBitmap("png", path))
  563. {
  564. delete bmp;
  565. return false;
  566. }*/
  567. U32 len;
  568. io_rStream.read(&len);
  569. if (!bmp->readBitmapStream("png", io_rStream, len))
  570. {
  571. delete bmp;
  572. return false;
  573. }
  574. GFXTexHandle handle = GFXTexHandle(bmp, &GFXFontTextureProfile, true, avar("%s() - Read Font Sheet for %s %d (line %d)", __FUNCTION__, mFaceName.c_str(), mSize, __LINE__));
  575. //handle.setFilterNearest();
  576. mTextureSheets.push_back(handle);
  577. }
  578. // Read last position info
  579. io_rStream.read(&mCurX);
  580. io_rStream.read(&mCurY);
  581. io_rStream.read(&mCurSheet);
  582. // Read the remap table.
  583. U32 minGlyph, maxGlyph;
  584. io_rStream.read(&minGlyph);
  585. io_rStream.read(&maxGlyph);
  586. if(maxGlyph >= minGlyph)
  587. {
  588. // Length of buffer..
  589. U32 buffLen;
  590. io_rStream.read(&buffLen);
  591. // Read the buffer.
  592. FrameTemp<S32> inBuff(buffLen);
  593. io_rStream.read(buffLen, inBuff);
  594. // Decompress.
  595. uLongf destLen = (maxGlyph-minGlyph+1)*sizeof(S32);
  596. uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen);
  597. AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!");
  598. // Make sure we've got the right endianness.
  599. for(i = minGlyph; i <= maxGlyph; i++)
  600. mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
  601. }
  602. return (io_rStream.getStatus() == Stream::Ok);
  603. }
  604. bool GFont::write(Stream& stream)
  605. {
  606. // Handle versioning
  607. stream.write(csm_fileVersion);
  608. // Write font info
  609. stream.write(mFaceName);
  610. stream.write(mSize);
  611. stream.write(mCharSet);
  612. stream.write(mHeight);
  613. stream.write(mBaseline);
  614. stream.write(mAscent);
  615. stream.write(mDescent);
  616. // Write char info list
  617. stream.write(U32(mCharInfoList.size()));
  618. U32 i;
  619. for(i = 0; i < mCharInfoList.size(); i++)
  620. {
  621. const PlatformFont::CharInfo *ci = &mCharInfoList[i];
  622. stream.write(ci->bitmapIndex);
  623. stream.write(ci->xOffset);
  624. stream.write(ci->yOffset);
  625. stream.write(ci->width);
  626. stream.write(ci->height);
  627. stream.write(ci->xOrigin);
  628. stream.write(ci->yOrigin);
  629. stream.write(ci->xIncrement);
  630. }
  631. stream.write(mTextureSheets.size());
  632. for (i = 0; i < mTextureSheets.size(); i++)
  633. {
  634. String path = String::ToString("%s/%s %d %d (%s).png", Con::getVariable("$GUI::fontCacheDirectory"), mFaceName.c_str(), mSize, i, getCharSetName(mCharSet));
  635. //mTextureSheets[i].getBitmap()->writeBitmap("png", path);
  636. mTextureSheets[i].getBitmap()->writeBitmapStream("png", stream);
  637. }
  638. stream.write(mCurX);
  639. stream.write(mCurY);
  640. stream.write(mCurSheet);
  641. // Get the min/max we have values for, and only write that range out.
  642. S32 minGlyph = S32_MAX, maxGlyph = 0;
  643. for(i = 0; i < 65536; i++)
  644. {
  645. if(mRemapTable[i] != -1)
  646. {
  647. if(i>maxGlyph) maxGlyph = i;
  648. if(i<minGlyph) minGlyph = i;
  649. }
  650. }
  651. stream.write(minGlyph);
  652. stream.write(maxGlyph);
  653. // Skip it if we don't have any glyphs to do...
  654. if(maxGlyph >= minGlyph)
  655. {
  656. // Put everything big endian, to be consistent. Do this inplace.
  657. for(i = minGlyph; i <= maxGlyph; i++)
  658. mRemapTable[i] = convertHostToBEndian(mRemapTable[i]);
  659. {
  660. // Compress.
  661. const U32 buffSize = 128 * 1024;
  662. FrameTemp<S32> outBuff(buffSize);
  663. uLongf destLen = buffSize * sizeof(S32);
  664. compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (maxGlyph-minGlyph+1)*sizeof(S32), 9);
  665. // Write out.
  666. stream.write((U32)destLen);
  667. stream.write(destLen, outBuff);
  668. }
  669. // Put us back to normal.
  670. for(i = minGlyph; i <= maxGlyph; i++)
  671. mRemapTable[i] = convertBEndianToHost(mRemapTable[i]);
  672. }
  673. return (stream.getStatus() == Stream::Ok);
  674. }
  675. void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning)
  676. {
  677. // Figure dimensions of our strip by iterating over all the char infos.
  678. U32 totalHeight = 0;
  679. U32 totalWidth = 0;
  680. S32 heightMin=0, heightMax=0;
  681. for(S32 i=0; i<mCharInfoList.size(); i++)
  682. {
  683. totalWidth += mCharInfoList[i].width + kerning + 2*padding;
  684. heightMin = getMin((S32)heightMin, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin);
  685. heightMax = getMax((S32)heightMax, (S32)getBaseline() - (S32)mCharInfoList[i].yOrigin + (S32)mCharInfoList[i].height);
  686. }
  687. totalHeight = heightMax - heightMin + 2*padding;
  688. // Make the bitmap.
  689. GBitmap gb(totalWidth, totalHeight, false, mTextureSheets[0].getBitmap()->getFormat());
  690. dMemset(gb.getWritableBits(), 0, sizeof(U8) * totalHeight * totalWidth );
  691. // Ok, copy some rects, taking into account padding, kerning, offset.
  692. U32 curWidth = kerning + padding;
  693. for(S32 i=0; i<mCharInfoList.size(); i++)
  694. {
  695. // Skip invalid stuff.
  696. if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
  697. continue;
  698. // Copy the rect.
  699. U32 bitmap = mCharInfoList[i].bitmapIndex;
  700. RectI ri(mCharInfoList[i].xOffset, mCharInfoList[i].yOffset, mCharInfoList[i].width, mCharInfoList[i].height );
  701. Point2I outRi(curWidth, padding + getBaseline() - mCharInfoList[i].yOrigin);
  702. gb.copyRect(mTextureSheets[bitmap].getBitmap(), ri, outRi);
  703. // Advance.
  704. curWidth += mCharInfoList[i].width + kerning + 2*padding;
  705. }
  706. // Done!
  707. gb.writeBitmap("png", fileName);
  708. }
  709. void GFont::setPlatformFont(PlatformFont *inPlatformFont)
  710. {
  711. AssertFatal( mPlatformFont == NULL, "Trying to set platform font which already exists");
  712. mPlatformFont = inPlatformFont;
  713. }
  714. void GFont::importStrip(const char *fileName, U32 padding, U32 kerning)
  715. {
  716. // Wipe our texture sheets, and reload bitmap data from the specified file.
  717. // Also deal with kerning.
  718. // Also, we may have to load RGBA instead of RGB.
  719. // Wipe our texture sheets.
  720. mCurSheet = mCurX = mCurY = 0;
  721. mTextureSheets.clear();
  722. // Now, load the font strip.
  723. Resource<GBitmap> strip = GBitmap::load(fileName);
  724. if(!strip)
  725. {
  726. Con::errorf("GFont::importStrip - could not load file '%s'!", fileName);
  727. return;
  728. }
  729. // And get parsing and copying - load up all the characters as separate
  730. // GBitmaps, sort, then pack. Not terribly efficient but this is basically
  731. // on offline task anyway.
  732. // Ok, snag some glyphs.
  733. Vector<GlyphMap> glyphList;
  734. glyphList.reserve(mCharInfoList.size());
  735. U32 curWidth = 0;
  736. for(S32 i=0; i<mCharInfoList.size(); i++)
  737. {
  738. // Skip invalid stuff.
  739. if(mCharInfoList[i].bitmapIndex == -1 || mCharInfoList[i].height == 0 || mCharInfoList[i].width == 0)
  740. continue;
  741. // Allocate a new bitmap for this glyph, taking into account kerning and padding.
  742. glyphList.increment();
  743. GlyphMap& lastGlyphMap = glyphList.last();
  744. lastGlyphMap.bitmap = new GBitmap(mCharInfoList[i].width + kerning + 2 * padding, mCharInfoList[i].height + 2 * padding, false, strip->getFormat());
  745. lastGlyphMap.charId = i;
  746. // Copy the rect.
  747. RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, lastGlyphMap.bitmap->getWidth(), lastGlyphMap.bitmap->getHeight());
  748. Point2I outRi(0,0);
  749. lastGlyphMap.bitmap->copyRect(strip, ri, outRi);
  750. // Update glyph attributes.
  751. mCharInfoList[i].width = lastGlyphMap.bitmap->getWidth();
  752. mCharInfoList[i].height = lastGlyphMap.bitmap->getHeight();
  753. mCharInfoList[i].xOffset -= kerning + padding;
  754. mCharInfoList[i].xIncrement += kerning;
  755. mCharInfoList[i].yOffset -= padding;
  756. // Advance.
  757. curWidth += ri.extent.x;
  758. }
  759. // Ok, we have a big list of glyphmaps now. So let's sort them, then pack them.
  760. dQsort(glyphList.address(), glyphList.size(), sizeof(GlyphMap), GlyphMapCompare);
  761. // They're sorted by height, so now we can do some sort of awesome packing.
  762. Point2I curSheetSize(256, 256);
  763. Vector<U32> sheetSizes;
  764. S32 curY = 0;
  765. S32 curX = 0;
  766. S32 curLnHeight = 0;
  767. S32 maxHeight = 0;
  768. for(U32 i = 0; i < glyphList.size(); i++)
  769. {
  770. PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
  771. if(ci->height > maxHeight)
  772. maxHeight = ci->height;
  773. if(curX + ci->width > curSheetSize.x)
  774. {
  775. curY += curLnHeight;
  776. curX = 0;
  777. curLnHeight = 0;
  778. }
  779. if(curY + ci->height > curSheetSize.y)
  780. {
  781. sheetSizes.push_back(curSheetSize.y);
  782. curX = 0;
  783. curY = 0;
  784. curLnHeight = 0;
  785. }
  786. if(ci->height > curLnHeight)
  787. curLnHeight = ci->height;
  788. ci->bitmapIndex = sheetSizes.size();
  789. ci->xOffset = curX;
  790. ci->yOffset = curY;
  791. curX += ci->width;
  792. }
  793. // Terminate the packing loop calculations.
  794. curY += curLnHeight;
  795. if(curY < 64)
  796. curSheetSize.y = 64;
  797. else if(curY < 128)
  798. curSheetSize.y = 128;
  799. sheetSizes.push_back(curSheetSize.y);
  800. if(getHeight() + padding * 2 > maxHeight)
  801. maxHeight = getHeight() + padding * 2;
  802. // Allocate texture pages.
  803. for(S32 i=0; i<sheetSizes.size(); i++)
  804. {
  805. GBitmap *bitmap = new GBitmap(TextureSheetSize, TextureSheetSize, false, strip->getFormat());
  806. // Set everything to transparent.
  807. U8 *bits = bitmap->getWritableBits();
  808. dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize * strip->getBytesPerPixel());
  809. GFXTexHandle handle = GFXTexHandle( bitmap, &GFXFontTextureProfile, true, avar("%s() - Font Sheet for %s (line %d)", __FUNCTION__, fileName, __LINE__) );
  810. mTextureSheets.increment();
  811. mTextureSheets.last() = handle;
  812. }
  813. // Alright, we're ready to copy bits!
  814. for(S32 i=0; i<glyphList.size(); i++)
  815. {
  816. // Copy each glyph into the appropriate place.
  817. PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId];
  818. U32 bi = ci->bitmapIndex;
  819. mTextureSheets[bi]->getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->getWidth(),glyphList[i].bitmap->getHeight()), Point2I(ci->xOffset, ci->yOffset));
  820. }
  821. // Ok, all done! Just refresh some textures and we're set.
  822. for(S32 i=0; i<sheetSizes.size(); i++)
  823. mTextureSheets[i].refresh();
  824. }
  825. DefineEngineFunction( populateFontCacheString, void, ( const char *faceName, S32 fontSize, const char *string ),,
  826. "Populate the font cache for the specified font with characters from the specified string.\n"
  827. "@param faceName The name of the font face.\n"
  828. "@param fontSize The size of the font in pixels.\n"
  829. "@param string The string to populate.\n"
  830. "@ingroup Font\n" )
  831. {
  832. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  833. if(f == NULL)
  834. {
  835. Con::errorf("populateFontCacheString - could not load font '%s %d'", faceName, fontSize);
  836. return;
  837. }
  838. if(!f->hasPlatformFont())
  839. {
  840. Con::errorf("populateFontCacheString - font '%s %d' has no platform font. Cannot generate more characters.", faceName, fontSize);
  841. return;
  842. }
  843. // This has the side effect of generating character info, including the bitmaps.
  844. f->getStrWidthPrecise( string );
  845. }
  846. DefineEngineFunction( populateFontCacheRange, void, ( const char *faceName, S32 fontSize, U32 rangeStart, U32 rangeEnd ),,
  847. "Populate the font cache for the specified font with Unicode code points in the specified range.\n"
  848. "@param faceName The name of the font face.\n"
  849. "@param fontSize The size of the font in pixels.\n"
  850. "@param rangeStart The start Unicode point.\n"
  851. "@param rangeEnd The end Unicode point.\n"
  852. "@note We only support BMP-0, so code points range from 0 to 65535.\n"
  853. "@ingroup Font\n" )
  854. {
  855. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  856. if(f == NULL)
  857. {
  858. Con::errorf("populateFontCacheRange - could not load font '%s %d'", faceName, fontSize);
  859. return;
  860. }
  861. if(rangeStart > rangeEnd)
  862. {
  863. Con::errorf("populateFontCacheRange - range start is after end");
  864. return;
  865. }
  866. if(!f->hasPlatformFont())
  867. {
  868. Con::errorf("populateFontCacheRange - font '%s %d' has no platform font Cannot generate more characters.", faceName, fontSize);
  869. return;
  870. }
  871. // This has the side effect of generating character info, including the bitmaps.
  872. for(U32 i=rangeStart; i<rangeEnd; i++)
  873. {
  874. if(f->isValidChar(i))
  875. f->getCharWidth(i);
  876. else
  877. Con::warnf("populateFontCacheRange - skipping invalid char 0x%x", i);
  878. }
  879. // All done!
  880. }
  881. DefineEngineFunction( dumpFontCacheStatus, void, (),,
  882. "Dumps to the console a full description of all cached fonts, along with "
  883. "info on the codepoints each contains.\n"
  884. "@ingroup Font\n" )
  885. {
  886. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  887. Con::printf("--------------------------------------------------------------------------");
  888. Con::printf(" Font Cache Usage Report");
  889. while( theFont != NULL )
  890. {
  891. theFont->dumpInfo();
  892. theFont = ResourceManager::get().nextResource();
  893. }
  894. }
  895. DefineEngineFunction( writeFontCache, void, (),,
  896. "Force all cached fonts to serialize themselves to the cache.\n"
  897. "@ingroup Font\n" )
  898. {
  899. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  900. Con::printf("--------------------------------------------------------------------------");
  901. Con::printf(" Writing font cache to disk");
  902. while( theFont != NULL )
  903. {
  904. const String fileName( theFont.getPath() );
  905. FileStream stream;
  906. stream.open(fileName, Torque::FS::File::Write);
  907. if(stream.getStatus() == Stream::Ok)
  908. {
  909. Con::printf(" o Writing '%s' to disk...", fileName.c_str());
  910. theFont->write(stream);
  911. }
  912. else
  913. {
  914. Con::errorf(" o Could not open '%s' for write", fileName.c_str());
  915. }
  916. theFont = ResourceManager::get().nextResource();
  917. }
  918. }
  919. DefineEngineFunction( populateAllFontCacheString, void, ( const char *string ),,
  920. "Populate the font cache for all fonts with characters from the specified string.\n"
  921. "@ingroup Font\n" )
  922. {
  923. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  924. Con::printf("Populating font cache with string '%s'", string);
  925. while( theFont != NULL )
  926. {
  927. if(theFont->hasPlatformFont())
  928. {
  929. // This has the side effect of generating character info, including the bitmaps.
  930. theFont->getStrWidthPrecise( string );
  931. }
  932. else
  933. {
  934. const String fileName( theFont.getPath() );
  935. Con::errorf("populateAllFontCacheString - font '%s' has no platform font. Cannot generate more characters.", fileName.c_str());
  936. }
  937. theFont = ResourceManager::get().nextResource();
  938. }
  939. }
  940. DefineEngineFunction( populateAllFontCacheRange, void, ( U32 rangeStart, U32 rangeEnd ),,
  941. "Populate the font cache for all fonts with Unicode code points in the specified range.\n"
  942. "@param rangeStart The start Unicode point.\n"
  943. "@param rangeEnd The end Unicode point.\n"
  944. "@note We only support BMP-0, so code points range from 0 to 65535.\n"
  945. "@ingroup Font\n" )
  946. {
  947. if(rangeStart > rangeEnd)
  948. {
  949. Con::errorf("populateAllFontCacheRange - range start is after end!");
  950. return;
  951. }
  952. Resource<GFont> theFont = ResourceManager::get().startResourceList( Resource<GFont>::signature() );
  953. Con::printf("Populating font cache with range 0x%x to 0x%x", rangeStart, rangeEnd);
  954. while( theFont != NULL )
  955. {
  956. const String fileName( theFont.getPath() );
  957. if(theFont->hasPlatformFont())
  958. {
  959. // This has the side effect of generating character info, including the bitmaps.
  960. Con::printf(" o Populating font '%s'", fileName.c_str());
  961. for(U32 i=rangeStart; i<rangeEnd; i++)
  962. {
  963. if(theFont->isValidChar(i))
  964. theFont->getCharWidth(i);
  965. else
  966. Con::warnf("populateAllFontCacheRange - skipping invalid char 0x%x", i);
  967. }
  968. }
  969. else
  970. {
  971. Con::errorf("populateAllFontCacheRange - font '%s' has no platform font. Cannot generate more characters.", fileName.c_str());
  972. }
  973. theFont = ResourceManager::get().nextResource();
  974. }
  975. }
  976. DefineEngineFunction( exportCachedFont, void,
  977. ( const char *faceName, S32 fontSize, const char *fileName, S32 padding, S32 kerning ),,
  978. "Export specified font to the specified filename as a PNG. The "
  979. "image can then be processed in Photoshop or another tool and "
  980. "reimported using importCachedFont. Characters in the font are "
  981. "exported as one long strip.\n"
  982. "@param faceName The name of the font face.\n"
  983. "@param fontSize The size of the font in pixels.\n"
  984. "@param fileName The file name and path for the output PNG.\n"
  985. "@param padding The padding between characters.\n"
  986. "@param kerning The kerning between characters.\n"
  987. "@ingroup Font\n" )
  988. {
  989. // Tell the font to export itself.
  990. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  991. if(f == NULL)
  992. {
  993. Con::errorf("exportCachedFont - could not load font '%s %d'", faceName, fontSize);
  994. return;
  995. }
  996. f->exportStrip(fileName, padding, kerning);
  997. }
  998. DefineEngineFunction( importCachedFont, void,
  999. ( const char *faceName, S32 fontSize, const char *fileName, S32 padding, S32 kerning ),,
  1000. "Import an image strip from exportCachedFont. Call with the "
  1001. "same parameters you called exportCachedFont.\n"
  1002. "@param faceName The name of the font face.\n"
  1003. "@param fontSize The size of the font in pixels.\n"
  1004. "@param fileName The file name and path for the input PNG.\n"
  1005. "@param padding The padding between characters.\n"
  1006. "@param kerning The kerning between characters.\n"
  1007. "@ingroup Font\n" )
  1008. {
  1009. // Tell the font to import itself.
  1010. Resource<GFont> f = GFont::create(faceName, fontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  1011. if(f == NULL)
  1012. {
  1013. Con::errorf("importCachedFont - could not load font '%s %d'", faceName, fontSize);
  1014. return;
  1015. }
  1016. f->importStrip(fileName, padding, kerning);
  1017. }
  1018. DefineEngineFunction( duplicateCachedFont, void,
  1019. ( const char *oldFontName, S32 oldFontSize, const char *newFontName ),,
  1020. "Copy the specified old font to a new name. The new copy will not have a "
  1021. "platform font backing it, and so will never have characters added to it. "
  1022. "But this is useful for making copies of fonts to add postprocessing effects "
  1023. "to via exportCachedFont.\n"
  1024. "@param oldFontName The name of the font face to copy.\n"
  1025. "@param oldFontSize The size of the font to copy.\n"
  1026. "@param newFontName The name of the new font face.\n"
  1027. "@ingroup Font\n" )
  1028. {
  1029. String newFontFile = GFont::getFontCacheFilename(newFontName, oldFontSize);
  1030. // Load the original font.
  1031. Resource<GFont> font = GFont::create(oldFontName, oldFontSize, Con::getVariable("$GUI::fontCacheDirectory"));
  1032. // Deal with inexplicably missing or failed to load fonts.
  1033. if (font == NULL)
  1034. {
  1035. Con::errorf(" o Couldn't find font : %s", oldFontName);
  1036. return;
  1037. }
  1038. // Ok, dump info!
  1039. FileStream stream;
  1040. stream.open( newFontFile, Torque::FS::File::Write );
  1041. if(stream.getStatus() == Stream::Ok)
  1042. {
  1043. Con::printf( " o Writing duplicate font '%s' to disk...", newFontFile.c_str() );
  1044. font->write(stream);
  1045. stream.close();
  1046. }
  1047. else
  1048. {
  1049. Con::errorf( " o Could not open '%s' for write", newFontFile.c_str() );
  1050. }
  1051. }