home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Text⁄Files / MakeWrite / MakeWrite Folder / MWTextToWrite.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-02-16  |  17.6 KB  |  726 lines  |  [TEXT/KAHL]

  1. /*
  2.  * Convert text file to MacWrite document, using formatting
  3.  * specifications contained in current map.
  4.  *
  5.  * Possibly add:
  6.  *    Text compression
  7.  */
  8.  
  9. # include    "MWMaca.h"
  10. # include    "MWFileStuff.h"
  11. # include    "MakeWrite.h"
  12.  
  13. # ifdef debug
  14. # include    "TransDisplay.h"
  15. # endif
  16.  
  17.  
  18. # define    maxTBufSize    1024    /* text file input buffer size */
  19. # define    maxCBufSize    4000    /* MacWrite limit on chars in paragraph */
  20.  
  21. /*
  22.  * MacWrite's maximum main doc text para count is something over
  23.  * 2000.  I choose 2000 as a reasonable limit.
  24.  */
  25.  
  26. # define        maxMWParas    2000
  27.  
  28.  
  29. /*
  30.  * Vars for text file streaming
  31.  */
  32.  
  33. static short    fText;                /* input text file reference number */
  34. static char    tFileBuf[maxTBufSize];    /* file buffer */
  35. static long    tFileSize;                /* file size */
  36. static long    tFileRead;                /* # chars read */
  37. static long    tFileLeft;                /* # chars still to read */
  38. static long    tBufSize;                /* number of chars currently in buffer */
  39. static short    tBufPos;            /* current text buffer position */
  40. static short    percent;            /* percent of input read so far */
  41.  
  42.  
  43. /*
  44.  * fWord - output MacWrite file reference number
  45.  * pushBack - text file input pushback queue
  46.  * outBuf - output paragraph buffer
  47.  * outChars - number of chars into current output paragraph so far
  48.  * blankCount - number of blanks to put out for line joining
  49.  * fmtCount - number of formats in the current paragraph so far
  50.  * fmtBuf - format information storage array
  51.  * maxFormats - number of formats the format array handle can hold
  52.  *    before needing expansion
  53.  * needFormat - true if need to generate a format when the next output
  54.  *    char is added to current paragraph
  55.  * paraInfo - paragraph information storage array
  56.  * maxParas - number of elements paraInfo can hold before needing expansion
  57.  * paraCount - current number of paragraphs (doesn't count initial ruler)
  58.  *
  59.  * font, size, style - current format
  60.  */
  61.  
  62. static short    fWord;
  63. static Str255    pushBack;
  64. static char        outBuf[maxCBufSize];
  65. static short    outChars;
  66. static short    blankCount;
  67.  
  68. static FAHandle    fmtBuf;
  69. static short    fmtCount;
  70. static short    maxFormats;
  71. static Boolean    needFormat;
  72.  
  73. static PIAHandle    paraInfo;
  74. static short        maxParas;
  75. static short        paraCount;
  76.  
  77. static short    font;
  78. static short    size;
  79. static short    style;
  80.  
  81.  
  82. /*
  83.  * Initialize text streaming variables
  84.  */
  85.  
  86. static void
  87. InitTextStream (short f)
  88. {
  89.     (void) GetEOF (f, &tFileSize);    /* set up streaming variables */
  90.     tFileLeft = tFileSize;
  91.     tFileRead = 0;
  92.     tBufSize = 0;
  93.     tBufPos = 0;
  94.     percent = -1;
  95. }
  96.  
  97.  
  98. /*
  99.  * Get next char from text file.  This has to take account of the
  100.  * state of the pushback stack.  File is read from disk a block
  101.  * at a time and dribbled out to avoid making a file system call
  102.  * for every character.
  103.  *
  104.  * Return false on end of file or file read error.
  105.  */
  106.  
  107. static Boolean
  108. ReadTextChar (char *c)
  109. {
  110. short        newPercent;
  111.  
  112.     if (pushBack[0] > 0)                /* pushback stack not empty */
  113.     {
  114.         *c = pushBack[pushBack[0]--];    /* return top char */
  115.         return (true);
  116.     }
  117.  
  118.     if (tBufPos >= tBufSize)    /* need to read in a new block */
  119.     {
  120.         tBufSize = tFileLeft;
  121.         if (tBufSize == 0)                /* end of file */
  122.             return (false);
  123.         if (tBufSize > maxTBufSize)        /* don't read more than a block */
  124.             tBufSize = maxTBufSize;
  125.         tFileLeft -= maxTBufSize;
  126.         if (tFileLeft < 0)
  127.             tFileLeft = 0;
  128.  
  129.         if (!FileRead (fText, tFileBuf, (long) tBufSize))
  130.             return (false);                /* read error */
  131.         tBufPos = 0;
  132.     }
  133.     *c = tFileBuf[tBufPos++];
  134.  
  135. # ifdef    debug
  136.     DisplayChar (*c);
  137. # endif
  138.  
  139.     newPercent = (++tFileRead * 100) / tFileSize;
  140.     if (newPercent != percent)
  141.     {
  142.         percent = newPercent;
  143.         SetMeterPercent (percent);
  144.     }
  145.     return (true);
  146. }
  147.  
  148.  
  149. /*
  150.  * Figure out how many blanks need to be added to the output buffer
  151.  * if another line is joined on.  Default is one.  If smart join is
  152.  * on, then two spaces if something like a period is at the end now.
  153.  * Remember to account for spaces that may already be on the end.  If
  154.  * a tab is found at the end, then throw hands up in disgust, because
  155.  * who knows how wide it is?
  156.  */
  157.  
  158. static void
  159. SetBlankCount (void)
  160. {
  161. short    i, endBlanks, needBlanks = 1;
  162. char    c;
  163.  
  164.     i = outChars;
  165.     while (i > 0)    /* count up spaces on end currently */
  166.     {
  167.         if ((c = outBuf[i-1]) == '\t')
  168.         {
  169.             blankCount = 0;
  170.             return;                    /* tab found - give up */
  171.         }
  172.         if (c != ' ')
  173.             break;
  174.         --i;
  175.     }
  176.     endBlanks = outChars - i;
  177.  
  178.     if (paraStyle.pSmartJoin)    /* smart join - try to be intelligent */
  179.     {
  180.         while (i > 0)
  181.         {
  182.             c = outBuf[i-1];
  183.             if (InString (quoteStr, c))    /* quote char - ignore */
  184.                 --i;
  185.             else            /* not quote char - is it period char? */
  186.             {
  187.                 if (InString (periodStr, c))
  188.                     needBlanks = 2;        /* yes - need two chars */
  189.                 break;
  190.             }
  191.         }
  192.     }
  193.  
  194.     if (i == 0)                    /* empty buffer - nothing needed */
  195.         needBlanks = 0;
  196.  
  197.     needBlanks -= endBlanks;    /* account for those already there */
  198.     if (needBlanks < 0)
  199.         needBlanks = 0;
  200.     blankCount = needBlanks;
  201. }
  202.  
  203.  
  204. /*
  205.  * Push saved text back onto the pushback stack
  206.  */
  207.  
  208. static void
  209. PushSavedText (StringPtr s)
  210. {
  211.     while (s[0] > 0)
  212.     {
  213.         pushBack[++pushBack[0]] = s[s[0]--];
  214.     }
  215. }
  216.  
  217.  
  218. static Boolean
  219. NewFormat (void)
  220. {
  221. Format    *f;
  222.  
  223. # ifdef    debug
  224.     DisplayString ("\p {NewFormat}");
  225. # endif
  226.  
  227.     if (fmtCount >= maxFormats)    /* need to make format array bigger */
  228.     {
  229.         if (!ExpandHandle ((Handle) fmtBuf, 1024L))
  230.         {
  231.             Message1 ("\pOut of memory, can't continue (NewFormat)");
  232.             return (false);
  233.         }
  234.         maxFormats = GetHandleSize ((Handle) fmtBuf) / sizeof (Format);
  235.     }
  236.  
  237.     f = &((**fmtBuf)[fmtCount++]);
  238.     f->fmtPos = outChars;            /* starting pos of format */
  239.     f->fmtFont = font;                /* current font */
  240.     f->fmtSize = size;                /* current size */
  241.     f->fmtStyle = style;            /* current style */
  242.     return (true);
  243. }
  244.  
  245.  
  246. static Boolean
  247. AddChar (char c)
  248. {
  249. Str255    s;
  250.  
  251.     if (needFormat)
  252.     {
  253.         needFormat = false;
  254.         if (!NewFormat ())
  255.             return (false);        /* couldn't get memory for format */
  256.     }
  257.     if (outChars >= maxCBufSize)
  258.     {
  259.         NumToString ((long) maxCBufSize, s);
  260.         Message ("\pParagraph contains more than ", s,
  261.                     "\p chars (MacWrite limit exceeded).",
  262.                     "\p  Check your Paragraph Style settings.");
  263.         return (false);
  264.     }
  265.     outBuf[outChars++] = c;
  266.     return (true);
  267. }
  268.  
  269.  
  270. static void
  271. ChangeCurFormat (MapSpec *m)
  272. {
  273. Str255    infoStr;
  274.  
  275.     if (m->font != sameFont && m->font != font)
  276.     {
  277.         font = m->font;
  278.         needFormat = true;
  279.     }
  280.     if (m->size != sameSize && m->size != size)
  281.     {
  282.         size = m->size;
  283.         needFormat = true;
  284.     }
  285.     if (m->style != sameStyle && m->style != style)
  286.     {
  287.         style = m->style;
  288.         needFormat = true;
  289.     }
  290.  
  291. # ifdef    debug
  292.     if (needFormat)
  293.     {
  294.         DisplayChar ('{');
  295.         FontToStr (font, infoStr);
  296.         DisplayString (infoStr);
  297.         DisplayChar ('/');
  298.         DisplayShort (size);
  299.         DisplayChar ('/');
  300.         StyleToStr (style, infoStr);
  301.         DisplayString (infoStr);
  302.         DisplayString ("\p} ");
  303.     }
  304. # endif
  305. }
  306.  
  307.  
  308. /*
  309.  * Write stored paragraph contents to disk:
  310.  *    number bytes of text (word)
  311.  *    the text
  312.  *    zero pad if necessary to align to word boundary
  313.  *    number bytes of format information
  314.  *    the format information
  315.  *
  316.  * Construct a new element in the paragraph information array
  317.  * as well, and reset the paragraph state vars.
  318.  *
  319.  * The data position pointer in the para info element is set
  320.  * to the current file position, since that's where the data
  321.  * will be written.
  322.  */
  323.  
  324. static Boolean
  325. FlushPara (void)
  326. {
  327. short    fmtLen, dataLen;
  328. Boolean    result;
  329. Str255    s;
  330.  
  331. # ifdef    debug
  332.     DisplayString ("\p{FlushPara}");
  333. # endif
  334.  
  335.     if (paraCount >= maxMWParas)
  336.     {
  337.         NumToString ((long) maxMWParas, s);
  338.         Message3 ("\pYour document contains more than ",
  339.                         s, "\p paragraphs.  Split the input file and try again.");
  340.         return (false);
  341.     }
  342.         
  343.     if (paraCount >= maxParas)    /* need to make para info array bigger */
  344.     {
  345.         if (!ExpandHandle ((Handle) paraInfo, 1024L))
  346.         {
  347.             Message1 ("\pOut of memory, can't continue (FlushPara)");
  348.             return (false);
  349.         }
  350.         maxParas = GetHandleSize ((Handle) paraInfo) / sizeof (ParaInfo6);
  351.     }
  352.  
  353.     fmtLen = fmtCount * sizeof (Format);
  354.     dataLen = sizeof (short)            /* word for length of text */
  355.             + ((outChars + 1) & ~1)    /* text ( + pad if necessary) */
  356.             + sizeof (short)            /* word for length of formats */
  357.             + fmtLen;                    /* formats */
  358.     HLock ((Handle) paraInfo);
  359.     SetParaInfo (&((**paraInfo)[paraCount++]),
  360.                     16, 0, FilePos (fWord), dataLen, 0);
  361.     HUnlock ((Handle) paraInfo);
  362.     HLock ((Handle) fmtBuf);
  363.     result =
  364.            WriteInteger (fWord, outChars)
  365.         && FileWrite (fWord, outBuf, (long) outChars)
  366.         && ZeroPad (fWord, 0)
  367.         && WriteInteger (fWord, fmtLen)
  368.         && FileWrite (fWord, (Ptr) *fmtBuf, (long) fmtLen);
  369.     HUnlock ((Handle) fmtBuf);
  370.     outChars = 0;            /* reset para state vars */
  371.     fmtCount = 0;
  372.     needFormat = true;
  373.     blankCount = 0;            /* don't need blanks for line joining now */
  374.     return (result);
  375. }
  376.  
  377.  
  378. /*
  379.  * Flush current paragraph, if there's any text in it
  380.  */
  381.  
  382. static Boolean
  383. DoBreak (void)
  384. {
  385.     return (outChars == 0 || (AddChar (cr) && FlushPara ()));
  386. }
  387.  
  388.  
  389. /*
  390.  * Flush current paragraph, regardless of whether there's anything
  391.  * in it or not.
  392.  */
  393.  
  394. static Boolean
  395. DoSpace (void)
  396. {
  397.     return (AddChar (cr) && FlushPara ());
  398. }
  399.  
  400.  
  401. /*
  402.  * FormatText - convert text file to MacWrite file
  403.  
  404.  * Winner of 1987 "Ugliest Function" award.  That goes for the
  405.  * comments, too.  They've been revised so often, I barely
  406.  * understand them myself, so good luck.
  407.  *
  408.  * saveQueue is the text that's been matched partially.  It's saved
  409.  * for writing to output in case no match is found:  the text is
  410.  * put back on the pushback stack, the first char is written out
  411.  * and a match is sought starting with the next char.
  412.  *
  413.  * The biggest headache about this thing is getting it to work properly
  414.  * with all legal combinations of paragraph style settings.
  415.  *
  416.  * "Each line a paragraph" is never on if blank line paragraphing is
  417.  * on, and vice-versa.  Use of a paragraph marker is compatible with
  418.  * all options.
  419.  *
  420.  * Input lines containing only imbedded commands are always ignored
  421.  * for paragraphing purposes - that is, they're not treated as blank
  422.  * lines.
  423.  */
  424.  
  425. static Boolean
  426. FormatText (void)
  427. {
  428. char    c;                /* input char */
  429. Str255    saveQueue;        /* input save queue */
  430. short    index;            /* command index */
  431. short    inLineChars;    /* # chars from current line added to */
  432.                         /* output (for blank line and beginning */
  433.                         /* of line detection) */
  434. Boolean    cmdsInLine;        /* whether commands were found in line */
  435. Boolean    xNeedFormat;
  436.  
  437.     if (!WritePrelude (fWord))
  438.         return (false);
  439.  
  440.     font = applFont;
  441.     size = 12;
  442.     style = 0;    /* plain */
  443.  
  444.     InitMarkStates ();                /* clear match info */
  445.     pushBack[0] = 0;                /* and file queues */
  446.     saveQueue[0] = 0;
  447.     inLineChars = 0;            /* no chars added from current line */
  448.     blankCount = 0;                /* no blanks needed for line joining yet */
  449.     cmdsInLine = false;            /* no commands in line yet */
  450.     paraCount = 0;                /* no paragraphs yet */
  451.     outChars = 0;                /* no chars in current paragraph */
  452.     fmtCount = 0;                /* no formats yet, either */
  453.     needFormat = true;            /* generate format on next char written */
  454.  
  455.     while (ReadTextChar (&c))            /* while not eof on text file */
  456.     {
  457.         saveQueue[++saveQueue[0]] = c;
  458.         switch (index = CheckMarkers (c))
  459.         {
  460.  
  461.         case -2:                        /* no match */
  462.             /*
  463.              * Push back saved text, read first char and add to current
  464.              * paragraph.  If the paragraph is done, flush it to disk, and
  465.              * start looking for a new marker.
  466.              *
  467.              * Note what happens on cr.  It is read, saved, fails to match, and
  468.              * is pushed back.  If there are other chars before it in pushback,
  469.              * they'll be reprocessed (except first of them, which is added to
  470.              * para).  Eventually, the cr becomes first thing in pushback.  It is
  471.              * read, saved, fails to match, and is pushed back,  Then it's read
  472.              * again, and causes para flushing according to para style rules.
  473.              *
  474.              * Carriage return check is here only.  Cr never matches any marker
  475.              * char, since it can't be typed into one.  Therefore it's not possible
  476.              * to get any other return value from CheckMarkers when c == cr.
  477.              */
  478.  
  479.             PushSavedText (saveQueue);
  480.             InitMarkStates ();
  481.             (void) ReadTextChar (&c);
  482.  
  483.             /*
  484.              * If the character is not a carriage return, then it is simply
  485.              * added to the current paragraph.  The exception occur if this is
  486.              * the first char of the line:
  487.              * 
  488.              * (i) If the character is whitespace at the beginning of the line,
  489.              * then the line begins a new paragraph, so flush the previous
  490.              * paragraph, if there's anything there.
  491.              * 
  492.              * (ii) Dribble out any blanks needed to take care of any line
  493.              * joining that might be going on.  Delay putting out any format
  494.              * change that might be pending until right before the char following
  495.              * the blanks - it's safe to do this because a format must have
  496.              * already been written for there to be any need to join with spaces.
  497.              */
  498.  
  499.             if (c != cr)
  500.             {
  501.                 if (inLineChars == 0)            /* if start of line... */
  502.                 {
  503.                     if (c == ' ' || c == '\t')
  504.                     {
  505.                         if (!DoBreak ())
  506.                             return (false);
  507.                     }
  508.                     else
  509.                     {
  510.                         xNeedFormat = needFormat;
  511.                         needFormat = false;
  512.                         while (blankCount > 0)
  513.                         {
  514.                             if (!AddChar (' '))
  515.                                 return (false);
  516.                             --blankCount;
  517.                         }
  518.                         needFormat = xNeedFormat;
  519.                     }
  520.                 }
  521.                 if (!AddChar (c))
  522.                     return (false);
  523.                 ++inLineChars;
  524.             }
  525.  
  526.             /*
  527.              * Char is cr.  If line only contained commands, ignore it.
  528.              * Else if every line begins a paragraph, flush it.  Else if
  529.              * line is blank and blank lines begin paragraphs.  Else it's just
  530.              * the end of the current line and it will be joined to the text of
  531.              * the next line unless the paragraph gets flushed first - so figure
  532.              * out how many spaces to put out before the next line is written.
  533.              * The writing happens when we actually get a line to be joined, not
  534.              * here, so that spaces don't get tacked onto lines that may not need
  535.              * them.
  536.              */
  537.  
  538.             else
  539.             {
  540.                 if (!(cmdsInLine && inLineChars == 0))
  541.                 {
  542.                     if (paraStyle.pEachLine)        /* always force line */
  543.                     {
  544.                         if (!DoSpace ())
  545.                             return (false);
  546.                     }
  547.                     else if (inLineChars == 0)            /* if blank line */
  548.                     {
  549.                         if (paraStyle.pBlankLine)    /* and they are paras */
  550.                         {
  551.                             if (!DoBreak () || !DoSpace ())
  552.                                 return (false);
  553.                         }
  554.                         else    /* ignore the line, but set blank count */
  555.                         {
  556.                             SetBlankCount ();
  557.                         }
  558.                     }
  559.                     else
  560.                     {
  561.                         SetBlankCount ();
  562.                     }
  563.                 }
  564.                 inLineChars = 0;
  565.                 cmdsInLine = false;
  566.             }
  567.             break;
  568.     
  569.         case -1:                        /* match, but not entirely yet */
  570.             break;                        /* just keep looking */
  571.  
  572.         default:                        /* found complete match */
  573.             /*
  574.              * Since the text matched a marker, it either appears in the
  575.              * output as a format change or signals the beginning of a
  576.              * paragraph.  In either case, it doesn't appear in the output
  577.              * as literal text, so can toss the save queue.
  578.              *
  579.              * If it's a format, process the format spec, setting the flag
  580.              * indicating that a new format will be needed on the next char
  581.              * written out (but only if the format actually *changes*).
  582.              *
  583.              * If it's a para marker, flush the current paragraph.
  584.              */
  585.             cmdsInLine = true;
  586.  
  587.             if (index == paraMarkIdx)
  588.             {
  589.                 if (!DoSpace ())
  590.                     return (false);
  591.             }
  592.             else if (index == pageMarkIdx)
  593.             {
  594.                 /* not implemented */
  595. # ifdef    debug
  596.                 DisplayString ("\p{Page break not implemented}");
  597. # endif
  598.             }
  599.             else
  600.                 ChangeCurFormat (&mapSpec[index]);
  601.  
  602.             saveQueue[0] = 0;        /* toss pushback */
  603.             InitMarkStates ();        /* start looking for new marker */
  604.             break;
  605.         
  606.         }
  607.     }
  608.  
  609.     /*
  610.      * It's the end of the file, but we might have been left in the middle
  611.      * of matching a mark, so return saved text to the pushback queue and
  612.      * write it out.  Note that if we *were* in the middle of mark testing,
  613.      * then this is not the beginning of a line and no spaces need to be
  614.      * dribbled.
  615.      */
  616.  
  617.     PushSavedText (saveQueue);
  618.     while (ReadTextChar (&c))
  619.     {
  620.         if (!AddChar (c))
  621.             return (false);
  622.     }
  623.  
  624.     /*
  625.      * There must be at least one text paragraph.  If there aren't any
  626.      * make sure one is generated.  If nothing at all has been formatted,
  627.      * there won't even be any formats generated, so force one.  Then
  628.      * flush the paragraph.
  629.      *
  630.      * If there already were paragraphs, then see whether unflushed
  631.      * characters are being held and flush if so.
  632.      */
  633.  
  634.     if (paraCount == 0)
  635.     {
  636.         if (fmtCount == 0)
  637.         {
  638.             if (!NewFormat ())
  639.                 return (false);
  640.         }
  641.         if (!FlushPara ())
  642.             return (false);
  643.     }
  644.     else if (outChars != 0)
  645.     {
  646.         if (!FlushPara ())
  647.             return (false);
  648.     }
  649.  
  650.     if (!WritePostlude (fWord, paraCount, paraInfo))
  651.         return (false);
  652.  
  653.     return (true);
  654. }
  655.  
  656.  
  657. void
  658. TextToWrite (void)
  659. {
  660. SFReply    inFile;
  661. SFReply    outFile;
  662. Boolean    result;
  663.  
  664.     if (FindEmptyMark ())
  665.         return;
  666.  
  667.     if (!GetInputFile ("\pOpen", 'TEXT', &inFile))
  668.         return;
  669.     CopyString (inFile.fName, outFile.fName);    /* make default output name */
  670.     AppendString ("\p.mw", outFile.fName);
  671.     if (!GetOutputFile (true, outFile.fName, 0, &outFile))
  672.         return;
  673.  
  674.     if (!OpenInputFile (&inFile, &fText))
  675.         return;
  676.  
  677.     InitTextStream (fText);
  678.     
  679.     if (!OpenOutputFile (&outFile, 'MACA', 'WORD', &fWord))
  680.     {
  681.         (void) FSClose (fText);
  682.         return;
  683.     }
  684.  
  685.     MeterBegin ();
  686.     MeterPos (5, 0);
  687.     MeterString ("\pFormatting:  ");
  688.     MeterString (inFile.fName);
  689.     MeterPos (64, 1);
  690.     MeterString ("\pTo:  ");
  691.     MeterString (outFile.fName);
  692.     StartMeterPercentInfo ();
  693.  
  694.     /*
  695.      * Fire up a debug output window if that option is true.  If it's
  696.      * false, then not executing DisplayWindow results in all output calls
  697.      * being ignored. Not as quick as testing them all, but easier.
  698.      * (None of it will be true if compilation has all this stuff turned
  699.      * off, anyway.)
  700.      */
  701.  
  702. # ifdef    debug
  703.     if (debugOut)
  704.         DisplayWindow (outFile.fName, true);
  705.     DisplayString ("\pInput file size ");
  706.     DisplayLong (tFileSize);
  707.     DisplayLn ();
  708. # endif
  709.  
  710.     fmtBuf = (FAHandle) NewHandle (0L);    /* get format array buffer */
  711.     maxFormats = 0;
  712.     paraInfo = (PIAHandle) NewHandle (0L);    /* get para info array buffer */
  713.     maxParas = 0;
  714.  
  715.     result =  FormatText ();
  716.     (void) FSClose (fText);
  717.     (void) FSClose (fWord);
  718.     (void) FlushVol (/*fWord*/ nil, outFile.vRefNum);
  719.     if (result == false)
  720.         FSDelete (outFile.fName, outFile.vRefNum);
  721.  
  722.     DisposeHandle ((Handle) fmtBuf);
  723.     DisposeHandle ((Handle) paraInfo);
  724.     MeterEnd ();
  725. }
  726.