Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

787 lines
21 KiB

  1. /*++
  2. Copyright (c) 1996 - 1999 Microsoft Corporation
  3. Module Name:
  4. oldfont.c
  5. Abstract:
  6. Implementation of the functions to use NT4.0 font format.
  7. Environment:
  8. Windows NT Unidrv driver
  9. Revision History:
  10. 06/02/97 -eigos-
  11. Created
  12. --*/
  13. #include "font.h"
  14. //
  15. // Macro
  16. //
  17. #define ADDR_CONV(x) ((BYTE *)pFDH + pFDH->x)
  18. ULONG UlCharsetToCodepage(
  19. BYTE ubCharSet)
  20. {
  21. CHARSETINFO CharsetInfo;
  22. //
  23. // Initialize CharsetInfo
  24. //
  25. CharsetInfo.ciCharset = 0;
  26. CharsetInfo.ciACP = 1252;
  27. CharsetInfo.fs.fsUsb[0] = 0x01;
  28. CharsetInfo.fs.fsUsb[1] = CharsetInfo.fs.fsUsb[2] = CharsetInfo.fs.fsUsb[3] = 0;
  29. CharsetInfo.fs.fsCsb[0] = 0x01;
  30. CharsetInfo.fs.fsCsb[1] = 0;
  31. PrdTranslateCharsetInfo((UINT)ubCharSet, &CharsetInfo, TCI_SRCCHARSET);
  32. return CharsetInfo.ciACP;
  33. }
  34. BOOL
  35. BGetOldFontInfo(
  36. FONTMAP *pfm,
  37. BYTE *pRes
  38. )
  39. /*++
  40. Routine Description:
  41. Fill in the FONTMAP data using the NT format data passed to us.
  42. There is not too much for us to do, since the NT data is
  43. all in the desired format. However, we do have to update some
  44. addresses.
  45. Arguments:
  46. pfm - Pointer to FONTMAP.
  47. pRes - Pointer to Font Resource.
  48. Return Value:
  49. TRUE - for success
  50. FALSE - for failure
  51. Note:
  52. 12-05-96: Created it -ganeshp-
  53. --*/
  54. {
  55. FI_DATA_HEADER *pFDH;
  56. FONTMAP_DEV *pfmdev;
  57. ASSERT(pfm != NULL &&
  58. pRes != NULL &&
  59. pfm->dwFontType == FMTYPE_DEVICE &&
  60. pfm->flFlags & FM_IFIVER40);
  61. pfmdev = pfm->pSubFM;
  62. pfmdev->pvFontRes = pRes;
  63. //
  64. // Old Format Data
  65. //
  66. pFDH = (FI_DATA_HEADER *)pRes;
  67. //
  68. // Verify that there is some semblance of correctness
  69. //
  70. if( pFDH->cjThis != sizeof( FI_DATA_HEADER ) )
  71. {
  72. ERR(( "BGetOldFontInfo: invalid FI_DATA_HEADER\n" ));
  73. return FALSE;
  74. }
  75. //
  76. // Mark this data as being in a resource
  77. //
  78. pfm->flFlags |= (FM_IFIRES | FM_FONTCMD);
  79. pfm->pIFIMet = (IFIMETRICS *)ADDR_CONV( dwIFIMet );
  80. if (!(pfm->flFlags & FM_SOFTFONT))
  81. {
  82. if( pFDH->dwCDSelect )
  83. {
  84. pfmdev->cmdFontSel.pCD = (CD *)ADDR_CONV( dwCDSelect );
  85. ASSERT(pfmdev->cmdFontSel.pCD);
  86. }
  87. if( pFDH->dwCDDeselect )
  88. {
  89. pfmdev->cmdFontDesel.pCD = (CD *)ADDR_CONV( dwCDDeselect );
  90. ASSERT(pfmdev->cmdFontDesel.pCD);
  91. }
  92. }
  93. if( pFDH->dwETM )
  94. {
  95. pfmdev->pETM = (EXTTEXTMETRIC *)ADDR_CONV( dwETM );
  96. }
  97. if( pFDH->dwWidthTab )
  98. {
  99. pfmdev->W.psWidth = (short *)ADDR_CONV( dwWidthTab );
  100. pfm->flFlags |= FM_WIDTHRES; /* Width vector too! */
  101. }
  102. /*
  103. * Miscellaneous odds & ends.
  104. */
  105. pfmdev->ulCodepage = UlCharsetToCodepage(pfm->pIFIMet->jWinCharSet);
  106. pfmdev->sCTTid = pFDH->u.sCTTid;
  107. pfmdev->fCaps = pFDH->fCaps;
  108. pfmdev->wDevFontType = pFDH->wFontType;
  109. pfm->wXRes = pFDH->wXRes;
  110. pfm->wYRes = pFDH->wYRes;
  111. pfmdev->sYAdjust = pFDH->sYAdjust;
  112. pfmdev->sYMoved = pFDH->sYMoved;
  113. return TRUE;
  114. }
  115. BOOL
  116. BRLEOutputGlyph(
  117. TO_DATA *pTod
  118. )
  119. /*++
  120. Routine Description:
  121. Send printer commands to print the glyph passed in. Basically
  122. we do the translation from ANSI to the printer's representation,
  123. Arguments:
  124. hg HGLYPH of interest
  125. Return Value:
  126. TRUE for success and FALSE for failure.FALSE being a failure of Spool
  127. Note:
  128. 1/22/1997 -ganeshp-
  129. Created it.
  130. --*/
  131. {
  132. PDEV *pPDev; // UNIDRV PDEV
  133. FONTPDEV *pFontPDev; // Font PDEV
  134. FONTMAP_DEV *pFMDev; // Device font PDEV
  135. FONTMAP *pFM; // Fontmap data structure
  136. NT_RLE *pntrle; // Access to data to send to printer
  137. COMMAND *pCmd; // Command Pointer
  138. PGLYPHPOS pgp;
  139. POINTL ptlRem;
  140. HGLYPH hg;
  141. UHG uhg; // Various flavours of HGLYPH contents
  142. INT iLen; // Length of string
  143. INT iIndex; // Index from glyph to width table
  144. INT cGlyphs;
  145. INT iX, iY, iXInc, iYInc;
  146. BYTE *pb; // Determining length for above
  147. BOOL bRet; // Returned to caller
  148. BOOL bSetCursorForEachGlyph;
  149. ASSERT(pTod);
  150. pPDev = pTod->pPDev;
  151. pFontPDev = pPDev->pFontPDev;
  152. pFM = pTod->pfm;
  153. pFMDev = pFM->pSubFM;
  154. pntrle = pFMDev->pvNTGlyph;
  155. cGlyphs = pTod->cGlyphsToPrint;
  156. pgp = pTod->pgp;
  157. ASSERT(pPDev && pFontPDev && pFM && pFMDev && pntrle && pgp);
  158. bSetCursorForEachGlyph = SET_CURSOR_FOR_EACH_GLYPH(pTod->flAccel);
  159. if (!bSetCursorForEachGlyph)
  160. VSetCursor( pPDev, pgp->ptl.x, pgp->ptl.y, MOVE_ABSOLUTE, &ptlRem);
  161. pTod->flFlags |= TODFL_FIRST_GLYPH_POS_SET;
  162. bRet = FALSE; /* Default case */
  163. iX = iY = 0;
  164. while (cGlyphs --)
  165. {
  166. hg = uhg.hg = pgp->hg; /* Lets us look at it however we want */
  167. iX = pgp->ptl.x;
  168. iY = pgp->ptl.y;
  169. //
  170. // Move to the next character's position
  171. //
  172. if (bSetCursorForEachGlyph)
  173. VSetCursor( pPDev, iX, iY, MOVE_ABSOLUTE, &ptlRem);
  174. if( pntrle )
  175. {
  176. /* The normal case - a standard device font */
  177. switch( pntrle->wType )
  178. {
  179. case RLE_DIRECT: /* Up to 2 bytes of data */
  180. iLen = uhg.rd.b1 ? 2 : 1;
  181. iIndex = uhg.rd.wIndex;
  182. bRet = WriteSpoolBuf( pPDev, &uhg.rd.b0, iLen ) == iLen;
  183. break;
  184. case RLE_PAIRED: /* Two glyphs (1 byte), overstruck */
  185. /*
  186. * First, try to use cursor push/pop escapes to
  187. * overlay the 2 characters. If they are not
  188. * available, try the backspace. If it doesn't exist
  189. * either, ignore the second character.
  190. */
  191. pCmd = COMMANDPTR(pPDev->pDriverInfo, CMD_PUSHCURSOR);
  192. if ( uhg.rd.b1 && (pCmd != NULL) )
  193. {
  194. /* Pushed the position; output ch1, pop position, ch2 */
  195. bRet = WriteSpoolBuf( pPDev, &uhg.rd.b0, 1 ) == 1;
  196. WriteChannel( pPDev, pCmd );
  197. bRet = WriteSpoolBuf( pPDev, &uhg.rd.b1, 1 ) == 1;
  198. }
  199. else
  200. {
  201. pCmd = COMMANDPTR(pPDev->pDriverInfo, CMD_BACKSPACE);
  202. bRet = WriteSpoolBuf( pPDev, &uhg.rd.b0, 1 ) == 1;
  203. if( uhg.rd.b1 && (pFontPDev->flFlags & FDV_BKSP_OK) )
  204. {
  205. WriteChannel( pPDev, pCmd );
  206. bRet = WriteSpoolBuf( pPDev, &uhg.rd.b1, 1 ) == 1;
  207. }
  208. }
  209. iIndex = uhg.rd.wIndex;
  210. break;
  211. case RLE_LI_OFFSET: /* Compact format of offset mode */
  212. if( uhg.rli.bLength <= 2 )
  213. {
  214. /* Compact format: the data is in the offset field */
  215. pb = &uhg.rlic.b0;
  216. }
  217. else
  218. {
  219. /* Standard format: the offset points to the data */
  220. pb = (BYTE *)pntrle + uhg.rli.wOffset;
  221. }
  222. iLen = uhg.rli.bLength;
  223. iIndex = uhg.rli.bIndex;
  224. bRet = WriteSpoolBuf(pPDev, pb, iLen ) == iLen;
  225. break;
  226. case RLE_L_OFFSET: /* Arbitrary length strings */
  227. /*
  228. * The HGLYPH contains a 3 byte offset from the beginning of
  229. * the memory area, and a 1 byte length field.
  230. */
  231. pb = (BYTE *)pntrle + (hg & 0xffffff);
  232. iLen = (hg >> 24) & 0xff;
  233. iIndex = *((WORD *)pb);
  234. pb += sizeof( WORD );
  235. bRet = WriteSpoolBuf(pPDev, pb, iLen ) == iLen;
  236. break;
  237. default:
  238. ERR(( "Rasdd!bOutputGlyph: Unknown HGLYPH format %d\n",
  239. pntrle->wType ));
  240. SetLastError( ERROR_INVALID_DATA );
  241. break;
  242. }
  243. }
  244. //
  245. // After drawing the character, in the printer, the cursor position
  246. // moves. Update the UNIDRV internal value to reduce the amount of
  247. // command to send.
  248. //
  249. if (bSetCursorForEachGlyph)
  250. {
  251. if( pFMDev->W.psWidth)
  252. {
  253. iXInc = pFMDev->W.psWidth[iIndex];
  254. iXInc = iXInc * pPDev->ptGrxRes.x / pFM->wXRes;
  255. }
  256. else
  257. iXInc = ((IFIMETRICS *)(pFM->pIFIMet))->fwdMaxCharInc;
  258. if( pFM->flFlags & FM_SCALABLE )
  259. {
  260. iXInc = LMulFloatLong(&pFontPDev->ctl.eXScale,iXInc);
  261. }
  262. if (pTod->flAccel & SO_VERTICAL)
  263. {
  264. iYInc = iXInc;
  265. iXInc = 0;
  266. }
  267. else
  268. {
  269. iYInc = 0;
  270. }
  271. VSetCursor( pPDev,
  272. iXInc,
  273. iYInc,
  274. MOVE_RELATIVE|MOVE_UPDATE,
  275. &ptlRem);
  276. }
  277. pgp ++;
  278. }
  279. /*
  280. * If the output succeeded, update our view of the printer's
  281. * cursor position. Typically, this will be to move along the
  282. * width of the glyph just printed.
  283. */
  284. if( bRet && pFM)
  285. {
  286. //
  287. // Output may have succeeded, so update the position for default
  288. // placement.
  289. //
  290. if( !bSetCursorForEachGlyph)
  291. {
  292. if( pFMDev->W.psWidth )
  293. {
  294. /*
  295. * Proportional font - so use the width table. Note that
  296. * it will also need scaling, since the fontwidths are stored
  297. * in the text resolution units.
  298. */
  299. /* This also scales correctly for downloaded fonts */
  300. iXInc = pFMDev->W.psWidth[iIndex];
  301. iXInc = iXInc * pPDev->ptGrxRes.x / pFM->wXRes;
  302. }
  303. else
  304. {
  305. /*
  306. * Fixed pitch font - metrics contains the information. NOTE
  307. * that scaling is NOT required here, since the metrics data
  308. * has already been scaled.
  309. */
  310. iXInc = ((IFIMETRICS *)(pFM->pIFIMet))->fwdMaxCharInc;
  311. }
  312. if( pFM->flFlags & FM_SCALABLE )
  313. {
  314. iXInc = LMulFloatLong(&pFontPDev->ctl.eXScale,iXInc);
  315. }
  316. if (pTod->flAccel & SO_VERTICAL)
  317. {
  318. iYInc = iXInc;
  319. iXInc = 0;
  320. }
  321. else
  322. {
  323. iYInc = 0;
  324. }
  325. VSetCursor( pPDev,
  326. (iX + iXInc) - pTod->pgp->ptl.x,
  327. (iY + iYInc) - pTod->pgp->ptl.y,
  328. MOVE_RELATIVE | MOVE_UPDATE,
  329. &ptlRem);
  330. }
  331. }
  332. else
  333. bRet = FALSE;
  334. return bRet;
  335. }
  336. BOOL
  337. BRLESelectFont(
  338. PDEV *pPDev,
  339. PFONTMAP pFM,
  340. POINTL *pptl)
  341. {
  342. FONTMAP_DEV *pfmdev = pFM->pSubFM;
  343. CD *pCD;
  344. ASSERT(pPDev && pfmdev);
  345. if (!(pCD = pfmdev->cmdFontSel.pCD))
  346. return FALSE;
  347. pfmdev->pfnDevSelFont(pPDev,
  348. pCD->rgchCmd,
  349. pCD->wLength,
  350. pptl);
  351. return TRUE;
  352. }
  353. BOOL
  354. BSelectNonScalableFont(
  355. PDEV *pPDev,
  356. BYTE *pbCmd,
  357. INT iCmdLength,
  358. POINTL *pptl)
  359. {
  360. if( iCmdLength > 0 && pbCmd &&
  361. WriteSpoolBuf( pPDev, pbCmd, iCmdLength ) != iCmdLength)
  362. {
  363. return FALSE;
  364. }
  365. return TRUE;
  366. }
  367. BOOL
  368. BSelectPCLScalableFont(
  369. PDEV *pPDev,
  370. BYTE *pbCmd,
  371. INT iCmdLength,
  372. POINTL *pptl)
  373. {
  374. INT iIn, iConv, iOut, iLen;
  375. BYTE aubLocal[80];
  376. ASSERT(pPDev && pbCmd && pptl);
  377. iOut = 0;
  378. for( iIn = 0; (iIn < iCmdLength) && (iOut < CCHOF (aubLocal)); iIn++ )
  379. {
  380. if( pbCmd[ iIn ] == '#')
  381. {
  382. //
  383. // The next byte tells us what information is required.
  384. //
  385. switch ( pbCmd[ iIn + 1 ] )
  386. {
  387. case 'v':
  388. case 'V': /* Want the font's height */
  389. iConv = pptl->y;
  390. break;
  391. case 'h':
  392. case 'H': /* Want the pitch */
  393. iConv = pptl->x;
  394. break;
  395. default: /* This should not happen! */
  396. ERR(( "UniFont!BSelScalableFont(): Invalid command format\n"));
  397. return FALSE; /* Bad news */
  398. }
  399. iLen = IFont100toStr( &aubLocal[ iOut ], CCHOF(aubLocal) - iOut, iConv );
  400. if ( (iLen < 0) || iLen > ( (INT) CCHOF(aubLocal) - iOut ) )
  401. {
  402. ERR(( "UniFont!BSelectPCLScalableFont(): Error. Command may be too big\n"));
  403. return FALSE; /* Bad news */
  404. }
  405. iOut += iLen;
  406. }
  407. else
  408. aubLocal[iOut++] = pbCmd[iIn];
  409. }
  410. WriteSpoolBuf( pPDev, aubLocal, iOut);
  411. return TRUE;
  412. }
  413. BOOL
  414. BSelectCapslScalableFont(
  415. PDEV *pPDev,
  416. BYTE *pbCmd,
  417. INT iCmdLength,
  418. POINTL *pptl)
  419. {
  420. INT iIn, iConv, iOut, iLen;
  421. BYTE aubLocal[80];
  422. ASSERT(pPDev && pbCmd && pptl);
  423. iOut = 0;
  424. for( iIn = 0; iIn < iCmdLength && (iOut < CCHOF (aubLocal) ) ; iIn++ )
  425. {
  426. if( pbCmd[ iIn ] == '#')
  427. {
  428. //
  429. // The next byte tells us what information is required.
  430. //
  431. switch ( pbCmd[ iIn + 1 ] )
  432. {
  433. case 'v':
  434. case 'V':
  435. iConv = pptl->y * 300 / 72;
  436. break;
  437. case 'h':
  438. case 'H':
  439. iConv = pptl->x;
  440. break;
  441. default:
  442. ERR(( "Invalid command format\n"));
  443. return FALSE;
  444. }
  445. iIn ++;
  446. iLen = iDrvPrintfSafeA(&aubLocal[iOut], CCHOF(aubLocal)-iOut, "%d", (iConv + 50)/100);
  447. if ( iLen <= 0 || iLen > (INT)CCHOF(aubLocal)-iOut )
  448. {
  449. ERR( ("Invalid command format. Command maybe too big\n"));
  450. return FALSE;
  451. }
  452. iOut += iLen;
  453. }
  454. else
  455. aubLocal[iOut++] = pbCmd[iIn];
  456. }
  457. WriteSpoolBuf( pPDev, aubLocal, iOut);
  458. return TRUE;
  459. }
  460. BOOL
  461. BSelectPPDSScalableFont(
  462. PDEV *pPDev,
  463. BYTE *pbCmd,
  464. INT iCmdLength,
  465. POINTL *pptl)
  466. {
  467. INT iIn, iOut, iConv;
  468. BYTE aubLocal[80];
  469. ASSERT(pPDev && pbCmd && pptl);
  470. iOut = 0;
  471. for( iIn = 0; iIn < iCmdLength && (iOut < CCHOF (aubLocal) ); iIn++ )
  472. {
  473. if (pbCmd[ iIn ] == '\x0B' && pbCmd[ iIn + 1] == '#')
  474. //
  475. // Height param for PPDS
  476. //
  477. {
  478. if ( iOut + 8 < CCHOF (aubLocal) ) //8 chars to be written into aubLocal
  479. {
  480. aubLocal[ iOut++ ] = '\x0B';
  481. aubLocal[ iOut++ ] = '\x06';
  482. iConv = pptl->y;
  483. //
  484. // Due to restriction of PPDS cmds, param must be sent in
  485. // xxx.xx format !
  486. //
  487. if ( ( iDrvPrintfSafeA(&aubLocal[ iOut ], CCHOF(aubLocal)-iOut, "%05d",iConv ) ) != 5 )
  488. return FALSE; /* Bad news */
  489. //
  490. // insert the decimal point
  491. //
  492. aubLocal[ iOut+5 ] = aubLocal[ iOut+4 ];
  493. aubLocal[ iOut+4 ] = aubLocal[ iOut+3 ];
  494. aubLocal[ iOut+3 ] = '.';
  495. iOut += 6; // xxx.xx ( ie 6 incl decimal pt
  496. iIn++;
  497. }
  498. else
  499. {
  500. ERR( ("Invalid command format. Command maybe too big\n"));
  501. return FALSE;
  502. }
  503. }
  504. else if (pbCmd[ iIn ] == '\x0E' && pbCmd[ iIn + 1] == '#')
  505. //
  506. // Pitch param for GPC_TECH_PPDS
  507. //
  508. {
  509. if ( iOut + 9 < CCHOF (aubLocal) ) //9 chars to be written into aubLocal
  510. {
  511. aubLocal[ iOut++ ] = '\x0E';
  512. aubLocal[ iOut++ ] = '\x07';
  513. aubLocal[ iOut++ ] = '\x30'; // special byte required
  514. iConv = pptl->x;
  515. if ( ( iDrvPrintfSafeA(&aubLocal[ iOut ], CCHOF(aubLocal)-iOut, "%05d",iConv ) ) != 5 )
  516. return FALSE;
  517. //
  518. // insert the decimal point
  519. //
  520. aubLocal[ iOut+5 ] = aubLocal[ iOut+4 ];
  521. aubLocal[ iOut+4 ] = aubLocal[ iOut+3 ];
  522. aubLocal[ iOut+3 ] = '.';
  523. iOut += 6; // xxx.xx ( ie 6 incl decimal pt
  524. iIn++;
  525. }
  526. else
  527. {
  528. ERR( ("Invalid command format. Command maybe too big\n"));
  529. return FALSE;
  530. }
  531. }
  532. else
  533. //
  534. // No translation necessary
  535. //
  536. aubLocal[ iOut++ ] = pbCmd[ iIn ];
  537. }
  538. WriteSpoolBuf( pPDev, aubLocal, iOut );
  539. return TRUE;
  540. }
  541. BOOL
  542. BRLEDeselectFont(
  543. PDEV *pPDev,
  544. PFONTMAP pFM)
  545. {
  546. PFONTMAP_DEV pfmdev;
  547. CD *pCD;
  548. BOOL bRet = TRUE;
  549. ASSERT(pPDev && pFM);
  550. pfmdev = pFM->pSubFM;
  551. pCD = pfmdev->cmdFontDesel.pCD;
  552. if (pCD &&
  553. pCD->wLength != 0 &&
  554. pCD->rgchCmd &&
  555. pCD->wLength != WriteSpoolBuf(pPDev, pCD->rgchCmd, pCD->wLength))
  556. bRet = FALSE;
  557. return bRet;
  558. }
  559. INT
  560. IGetIFIGlyphWidth(
  561. PDEV *pPDev,
  562. FONTMAP *pFM,
  563. HGLYPH hg)
  564. {
  565. FONTMAP_DEV *pfmdev;
  566. NT_RLE *pntrle; // The RLE stuff - may be needed
  567. UHG uhg; // Defined access to HGLYPH contents
  568. INT iWide = 0;
  569. ASSERT(pPDev && pFM);
  570. pfmdev = pFM->pSubFM;
  571. pntrle = pfmdev->pvNTGlyph;
  572. ASSERT(pfmdev && pntrle);
  573. if( pfmdev->W.psWidth )
  574. {
  575. /* Proportional font - width varies per glyph */
  576. uhg.hg = (HGLYPH)hg;
  577. /*
  578. * We need the index value from the HGLYPH. The
  579. * index is the offset in the width table. For all
  580. * but the >= 24 bit offset types, the index is
  581. * included in the HGLYPH. For the 24 bit offset,
  582. * the first WORD of the destination is the index,
  583. * while for the 32 bit offset, it is the second WORD
  584. * at the offset.
  585. */
  586. switch( pntrle->wType )
  587. {
  588. case RLE_DIRECT:
  589. case RLE_PAIRED:
  590. iWide = uhg.rd.wIndex;
  591. break;
  592. case RLE_LI_OFFSET:
  593. iWide = uhg.rli.bIndex;
  594. break;
  595. case RLE_L_OFFSET:
  596. iWide = (DWORD)uhg.hg & 0x00ffffff;
  597. iWide = *((WORD *)((BYTE *)pntrle + iWide));
  598. break;
  599. case RLE_OFFSET:
  600. iWide = (DWORD)uhg.hg + sizeof( WORD );
  601. iWide = *((WORD *)((BYTE *)pntrle + iWide));
  602. break;
  603. }
  604. iWide = pfmdev->W.psWidth[iWide];
  605. //
  606. // If this is a proportionally spaced font,
  607. // we need to adjust the width table entries
  608. // to the current resolution. The width tables are NOT
  609. // converted for lower resolutions, so we add the factor in now.
  610. // Fixed pitch fonts must not be adjusted, since the width is converted
  611. // in the font metrics.
  612. //
  613. iWide = iWide * pPDev->ptGrxRes.x / pFM->wXRes;
  614. }
  615. else
  616. {
  617. //
  618. // Fixed pitch fonts come from IFIMETRICS
  619. //
  620. iWide = ((IFIMETRICS *)(pFM->pIFIMet))->fwdMaxCharInc;
  621. }
  622. return iWide;
  623. }