This is rtfConverter.m in view mode; [Download] [Up]
/***********************************************************************\ Converter class for Convert RTF which converts between Mac and NeXT rtf formats. Copyright (C) 1993 David John Burrowes This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. The author, David John Burrowes, can be reached at: davidjohn@kira.net.netcom.com David John Burrowes 1926 Ivy #10 San Mateo, CA 94403-1367 \***********************************************************************/ #import "rtfConverter.h" #import "rtfController.h" // <--- Ugly hack! #import "rtfFile.h" #import "rtfToken.h" #import <stdio.h> #import <stdlib.h> #import <string.h> #import <objc/HashTable.h> #import "MacToNeXTRTFText.h" #import "NeXTToMacRTFText.h" #import "FontEntry.h" #import <appkit/publicWraps.h> #import <appkit/Listener.h> // for the drag-and-drop of files facility #import <appkit/Speaker.h> // ibid #import <appkit/Application.h> // To get NXApp (I thought that is what defaults was...) #import "djbflag.h" @implementation rtfConverter /*######################################################################*\ HISTORY: 93.04.03 djb Added code for a bugfix for character conversions. Here's the scoop. Given a document that was of the form: {...\f23 blahblah {\f45 foo foo} blah blah...} If \f23 was, say, Symbol, and \f45 was Times, then what used to happen is that the blahblah would not have characters converted, then, the \f45 would change the conversion flag, and 'foo foo' would be, and then 'blah blah' would also be converted because I was ignoring group boundraries. oops. This will now do that conversion properly if the user has chosen to have only standard fonts converted. This involves maintaining a stack of conversion values that we push and pop for each group, so we can revert to the previous conversion values easily. Note that examineFile still does not do essentially any processing of whether individual text should be converted, and so it wasn't affected by this bug, or the fix. \*######################################################################*/ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: init // Parameters: none // Returns: none // Stores: none // Description: // Initalizes an instance... namely by defining th manager instance as null // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - init { [super init]; MacTextConverter = [[MacToNeXTRTFText alloc] init]; NeXTTextConverter = [[NeXTToMacRTFText alloc] init]; colorsUsed = NO; SymbolNumber = -1; addedSymbol = NO; numPicts = 0; // Number of pictures found in the file pictNum = 0; // Number of the last picture found. // // These will presumably be set by the caller, we just define them just in case // RemoveUnderline = NO; AlterSingleQuotes = NO; ConvertAllText = ConvertAll; // Modified for change from boolean to 3 optins ExtractPicts = NO; ConvertPictures =YES; // // KeepTokensForMe is intended only for the original author. No one else // probably could care less. It stores the various \pich, etc tokens from // a pict group in another file. These are usually useless details though. // if (ForDavidJohnBurrowesOnly == 1) KeepTokensForMe = YES; else KeepTokensForMe = NO; PictConverterIsOpen = NO; // // 93.04.03 djb Added as part of char convert bugfix // CharConvertStack = (ConvertTypes*) malloc(50*sizeof(ConvertTypes)); StackTop = 49; StackLocation = 0; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: free // Parameters: none // Returns: none // Stores: none // Description: // Frees the object. This involves killing the pict converter if we opened it. // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - free { int result, flag; Instance ourSpeaker; port_t thePort; if (PictConverterIsOpen == YES) { thePort = NXPortFromName("Convert_PICT", NULL); if (thePort != PORT_NULL) { ourSpeaker = [NXApp appSpeaker]; [ourSpeaker setSendPort: thePort ]; result = [ourSpeaker msgQuit: &flag]; } } [super free]; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: isThisAGoodFile: // Parameters: A File instance // Returns: YES if it is, NO if it isn't. // Stores: none // Description: // This method attempts to determine if the given file (being accessed as an rtfFile // instance) is indeed an RTF one. A file is judged to be an RTF one iff it's first // two tokens are {\rtf#. Note that it restores the file's position when done. // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (Boolean) isThisAGoodFile: fileInstance { Instance thetoken; Boolean isRTF = NO; Integer storedLocation = [fileInstance GetCurrentPosition]; CString tempName; [fileInstance MoveTo: 0]; thetoken = [fileInstance GetToken]; if ([thetoken GetType] == tokenBeginGroup) isRTF = YES; [thetoken free]; thetoken = [fileInstance GetToken]; tempName = [thetoken GetName]; if ((strcmp(tempName, "rtf") == 0) && (isRTF == YES)) isRTF = YES; else { [self PutCString: "This does not appear to be an rtf file (because the first two tokens were not `{\\rtf'). Proceed with caution!" Into: SECOND_RESULT]; isRTF = NO; } FreeCString(tempName); [thetoken free]; [fileInstance MoveTo: storedLocation]; return isRTF; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: SetConversionDirection: // Parameters: A boolean value. YES if to assume files are Mac RTF files. No if not. // Returns: self // Stores: none // Description: // If the user has asked that files, by default, be assumed to be Mac rtf files, // this will be alled with YES. If the user prefers to assume RTF files are // going to be processed, NO is passed. // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - SetConversionDirection: (Boolean) convertAsMacRTF { if (convertAsMacRTF == YES) ConvertSource = MacRTF; else ConvertSource = NeXTRTF; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: RemoveFirstNeXTUnderline: // Parameters: A boolean value. YES if we should remove the first \ul0 in an rtf file. // Returns: self // Stores: none // Description: // If the source file is a NeXT rtf file (ConvertSource is NeXTRTF), then if our // parameter is YES, then we will remove the first \ul0 in the file // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - RemoveFirstNeXTUnderline: (Boolean) StripFirstUL0 { RemoveUnderline = StripFirstUL0; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: ConvertTheSingleQuotes: // Parameters: A boolean value. YES if we should always change the ascii single quotes // to equivalent looking quotes on the destination platform. Otherwise, let // them appear on thedestination as thye would there. (e.g. next single quotes // look curley. mac ones are straight and an accent. if this is yes, the next // quotes will be mapped to curley quotes on the mac, not the default ones) // Returns: self // Stores: none // Description: // Stores the specified parameter until later processing. // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ConvertTheSingleQuotes: (Boolean) changeQuotes { AlterSingleQuotes = changeQuotes; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: SetTextConversion: // Parameters: A boolean value. YES if we should convert the special characters for // all fonts, NO if we should only do it for 'standard' fonts. // Returns: self // Stores: none // Description: // Stores the specified parameter until later processing. // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - SetTextConversion: (GuiConvertChoices) convertText { ConvertAllText = convertText; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Method: SetPictConversion:AndDataRemoval: // Parameters: YES if we are to convert Mac PICT data // YES if we are to remove all traces of pict data // Returns: self // Stores: none // Description: // This sets two conversion options in this class. The first specifies whether // we should try to use the Convert_PICT program to convert any PICT data // we encounter. The second is a bit more complex. This converter always removes // the {\pict\macpict...} data from the rtf file. The question is: do we discard it // completely (after perhaps doing the above conversion), or do we store it in an // another file (a .pict file). This latter is only of interest if the user doesn't want // to loose the original pict data for one reason or another. I'm such a person, and // so I'd prefer to keep it around. // Bugs: ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - SetPictConversion: (Boolean) ConvertPictData AndDataRemoval: (Boolean) DeletePictData; { if (DeletePictData == YES) ExtractPicts = NO; else ExtractPicts = YES; ConvertPictures = ConvertPictData; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: examineFile: // Parameters: The file to be examined // Returns: self // Stores: error code // Description: // This walks through the rtf file from start to end, and watches for certain // things: // 1) Whether any color tokens are used (\cf and \cb) // 2) it counts how many times each type face is used (using \f) // (allowing us to judge what to write out in the font table) // 3) It stores whether Symbol may be needed for some character conversions (in \') // 4) Counts occurrences of PICT images (\pict) // As part of the work for counting font use, we read in the whole font table, and // store it as a fontTable variable. We then later increment usage counts in the // individual FontEntry instances. (this table is later used to write out the font table) // Bugs // When computing whether symbol was used while processing \', we ignore // the 'current' font at that point. This may have the effect of leaving the count // at least partially inaccurate, since those characters may not actually be converted // later on. // History: // 93.01.24 Added storing of the lasterrorcode for the while, because in circumstances // where the end of file occurrs at a point where we want to update the // progress indicator, we'd wipe out the sourceFile's error code, and produce // a crash. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - examineFile: sourceFile { Instance thetoken, temptoken; TokenType theType; CString theName; Real modifier = 30.0 / [sourceFile FileSize]; Integer groupDepth = 0; Character aCharacter; Instance myEntry; Integer tokenCount = 0; CString tempName; Integer lastErrorCode; Character fontName[31]; // To hold UnnamedFont32767 (for instance) Integer fontNumber; [sourceFile MoveTo: 0]; thetoken = [sourceFile GetNextControlToken]; lastErrorCode = [sourceFile GetErrorCode]; theName = [thetoken GetName]; while ( lastErrorCode == ERR_OK ) { // // If we just found the \fonttbl token. Consume the font table and turn it into our // hashed fontTable structure. // if (strcmp(theName, "fonttbl") == 0) { groupDepth = 1; while (groupDepth > 0) { // // Watch for the end of the table (when we have // found a matching number of {'s and }'s (measured by groupDepth). // Store the font information // [thetoken free]; FreeCString(theName); thetoken = [sourceFile GetToken]; theType = [thetoken GetType]; theName = [thetoken GetName]; if (theType == tokenBeginGroup) groupDepth ++; else if (theType == tokenEndGroup) groupDepth --; else if (theType == tokenControlWord) { if (strcmp(theName, "f") == 0) { myEntry = [[FontEntry alloc] init]; [myEntry SetNumber: [thetoken GetValue]]; [thetoken free]; // // Assume the next token is our type token, and that is followed // by the font name token. // [myEntry SetFontType: [sourceFile GetToken]]; thetoken = [sourceFile GetFamilyNameToken]; tempName = [thetoken GetName]; [myEntry SetName: tempName]; [myEntry PrepName]; FreeCString(tempName); // // Add the entry to the font table // [fontTable insertKey: (const void *) [myEntry GetNumber] value: (void *) myEntry]; // // If this entry described symbol, get the number for reference // tempName = [myEntry GetName]; if (strcmp(tempName, "Symbol") == 0) SymbolNumber = [myEntry GetNumber]; FreeCString(tempName); } } /* End of if-then-else on a token type */ } } else if (strcmp(theName, "f") == 0) { // // Count how often each font is used. (assumes all \fxx are in the font table) // fontNumber = [thetoken GetValue]; myEntry = [fontTable valueForKey: (const void *) fontNumber]; if (myEntry== nil) { // // 93.02.10 djb Added when I realized I really did want a clear record // of all fonts being used in the document, even if I didn't know their // names (this is useful, because you might know what \f201 refers to, // and thus be able to manually fix the font table. This chunk of code has // the effect of creating a new font entry in the table which will then // have it's count incremented and be written out in the final thing // myEntry = [[FontEntry alloc] init]; [myEntry SetNumber: fontNumber]; sprintf(fontName, "UnnamedFont%d", (int) fontNumber); [myEntry SetName: fontName]; temptoken=[[rtfToken alloc] initTokenOfType: tokenControlWord]; [temptoken SetTokenName: "fnil"]; [myEntry SetFontType: temptoken]; [fontTable insertKey: (const void *) fontNumber value: (void *) myEntry]; } [myEntry IncrementCount]; } // // Check whether color values are used. // else if ( (strcmp(theName, "cf") == 0) || (strcmp(theName, "cb") == 0) ) colorsUsed = YES; else if (ConvertSource == MacRTF) { // // Do some Mac specific conversions. // if (strcmp(theName, "\'") == 0) { // // We found a \' token. Check whether this will be converted into // Symbol (BUG: We ignore the actual font in effect at this piont) // If it will be, the increment the Symbol count. (we determine if the // char will be converted to symbol by actually converting it and // checking the result code. ICK! // aCharacter = (Character) [thetoken GetValue]; [CharConverter ConvertCharacter: aCharacter]; if ([CharConverter GetBooleanFrom: SECOND_RESULT] == YES) { // // If symbol wasn't in the font entry table, pick it a number, // by claiming the first unused number // if (SymbolNumber == -1) { SymbolNumber = 1; while ([fontTable isKey: (const void *) SymbolNumber] == YES) SymbolNumber++; myEntry = [[FontEntry alloc] init]; [myEntry SetNumber: SymbolNumber]; [myEntry SetName: "Symbol"]; temptoken=[[rtfToken alloc] initTokenOfType: tokenControlWord]; [temptoken SetTokenName: "ftech"]; [myEntry SetFontType: temptoken]; [fontTable insertKey: (const void *) SymbolNumber value: (void *) myEntry]; addedSymbol = YES; } // // Now that we're garantueed that symbol is in here, // increment it's count // myEntry = [fontTable valueForKey: (const void *) SymbolNumber] ; [ myEntry IncrementCount ]; } } else if (strcmp(theName, "pict") == 0) { // // If we find a \pict instruction, check to see if it is a Mac picture, // and increment a count if so. // [thetoken free]; FreeCString(theName); thetoken = [sourceFile GetToken]; theName = [thetoken GetName]; if (strcmp(theName, "macpict") == 0) numPicts++; // // Force the us to ping the mamager, since sometimes // pictures are big enough to cause the update on the screen not to work // right. [myManager SetPercentageDone: modifier * [sourceFile GetCurrentPosition]]; } } // // BUG: We should be walking through each word token, and if one would convert // to symbol, then setting symbol as above. // [thetoken free]; FreeCString(theName); thetoken = [sourceFile GetNextControlToken]; lastErrorCode = [sourceFile GetErrorCode]; if (thetoken != NullInstance) theName = [thetoken GetName]; else theName = NullCString; if ((tokenCount++ % 100) == 0) [myManager SetPercentageDone: modifier * [sourceFile GetCurrentPosition]]; } [thetoken free]; FreeCString(theName); [self StoreErrorCode: ERR_OK AndText: "OK"]; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: PrepareFontTable // Parameters: none // Returns: self // Stores: none directly // Description: // Given the instance's fontTable, we walk through each entry, and convert the // font names based on which way we are converting. e.g. the Mac's // Avant Garde is called AvantGarde-Book on the NeXT. Thus, for all the standard // Mac fonts, we make sure they will come up right on the NeXT. And vice versa. // Some fonts should not have their characters converted at all (e.g. Zaph Dingbats) // because they have the same layout on both platforms. Otherwise, we use // whatever conversion option the user has chosen. // Bugs // A NeXT font can be specified as, say, AvantGarde-Italic. Because NeXt utilities // don't always record just the family name, it becomes very difficult to retain // this information. So. We do not. // History: // 93.02.15 djb Added hack to the end: If the user has chosen to convert NO fonts, // then override whatever we've already put in and set it to none. // This is a bit of a kludge, but it suffices for now. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - PrepareFontTable { NXHashState state = [fontTable initState]; Integer fontNumber; Instance fontEntry; CString fontName; // // For each fontTable entry, massage the name and the conversion options // while ([fontTable nextState: &state key: (void **)&fontNumber value: (const void **)&fontEntry] == YES) { // // Set the default type of conversion. // [fontEntry SetConversion: FullConversion]; fontName = [fontEntry GetName]; if (ConvertSource == MacRTF) { // // Convert type family names // if (strcmp(fontName, "Times") == 0) [fontEntry SetName: "Times-Roman"]; else if (strcmp(fontName, "Avant Garde") == 0) [fontEntry SetName: "AvantGarde-Book"]; else if (strcmp(fontName, "Bookman") == 0) [fontEntry SetName: "Bookman-Light"]; else if (strcmp(fontName, "Helvetica Narrow") == 0) [fontEntry SetName: "Helvetica-Narrow"]; else if (strcmp(fontName, "New Century Schlbk") == 0) [fontEntry SetName: "NewCenturySchlbk-Roman"]; else if (strcmp(fontName, "Palatino") == 0) [fontEntry SetName: "Palatino-Roman"]; else if (strcmp(fontName, "Zapf Chancery") == 0) [fontEntry SetName: "ZapfChancery-MediumItalic"]; else if (strcmp(fontName, "New York") == 0) [fontEntry SetName: "NewYork"]; else if (strcmp(fontName, "Los Angeles") == 0) [fontEntry SetName: "LosAngeles"]; else if (strcmp(fontName, "San Francisco") == 0) [fontEntry SetName: "SanFrancisco"]; // // Also, mark certain type families as requiring no character conversions // else if (strcmp(fontName, "Symbol") == 0) [fontEntry SetConversion: NoConversion]; // Identical on both platforms else if (strcmp(fontName, "Zapf Dingbats") == 0) { [fontEntry SetName: "ZapfDingbats"]; [fontEntry SetConversion: NoConversion]; // Identical on both platforms. } else if (strcmp(fontName, "Cairo") == 0) [fontEntry SetConversion: NoConversion]; else if (strcmp(fontName, "Mobile") == 0) [fontEntry SetConversion: NoConversion]; else if (strcmp(fontName, "Taliesin") == 0) [fontEntry SetConversion: NoConversion]; // // The following families require no name changes, and take the FullConversion // If a name isn't in the above or below, we set it's conversion options to // whatever the user specified // else if ( (strcmp(fontName, "Geneva") != 0) && (strcmp(fontName, "Courier") != 0) && (strcmp(fontName, "Helvetica") != 0) && (strcmp(fontName, "Monaco") != 0) && (strcmp(fontName, "Athens") != 0) && (strcmp(fontName, "Toronto") != 0) && (strcmp(fontName, "Venice") != 0) && (strcmp(fontName, "London") != 0) && (strcmp(fontName, "Chicago") != 0) && (strcmp(fontName, "Seattle") != 0) ) { if (ConvertAllText == ConvertAll) [fontEntry SetConversion: FullConversion]; else [fontEntry SetConversion: NoConversion]; } } else // NeXTRTF { // // Convert names of type faces // if (strcmp(fontName, "Times-Roman") == 0) [fontEntry SetName: "Times"]; else if (strncmp(fontName, "AvantGarde", 10) == 0) [fontEntry SetName: "Avant Garde"]; else if (strncmp(fontName, "Bookman",7) == 0) [fontEntry SetName: "Bookman"]; else if (strcmp(fontName, "Helvetica-Narrow") == 0) [fontEntry SetName: "Helvetica Narrow"]; else if (strncmp(fontName, "NewCenturySchlbk", 16) == 0) [fontEntry SetName: "New Century Schlbk"]; else if (strncmp(fontName, "Palatino", 8) == 0) [fontEntry SetName: "Palatino"]; else if (strncmp(fontName, "ZapfChancery", 12) == 0) [fontEntry SetName: "Zapf Chancery"]; // // Declare these as being ones that require no conversion // else if (strcmp(fontName, "Symbol") == 0) [fontEntry SetConversion: NoConversion]; // Identical on both platforms else if (strncmp(fontName, "ZapfDingbats",12) == 0) { [fontEntry SetName: "Zapf Dingbats"]; [fontEntry SetConversion: NoConversion]; // Identical on both platforms. } // // The following families require no name changes, and take the FullConversion // If a name isn't in the above or below, we set it's conversion options to // whatever the user specified // else if ( (strcmp(fontName, "Courier") != 0) && (strcmp(fontName, "Helvetica") != 0) && (strcmp(fontName, "Ohlfs") != 0) ) { if (ConvertAllText == ConvertAll) [fontEntry SetConversion: FullConversion]; else [fontEntry SetConversion: NoConversion]; } } // // 93.02.15 djb Added this to force no conversion if the user has chosen. // Note that by now we've polarized the 3 options of ConvertAllText // to the two that a FontEntry understands. // if (ConvertAllText == ConvertNone) [fontEntry SetConversion: NoConversion]; FreeCString(fontName); } return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: WriteFontTableEntriesTo: // Parameters: the file object to write to // Returns: self // Stores: none directly // Description: // This takes the instance fontTable object, and iterates through it, writing each // font entry out to the destination file. Each entry will have the form // {\fxx \yyy fontname ; } // Naturally, this assumes that the fontTable was created and filled elsewhere. // This does *not* destroy the fontTable's entries. // History // 93.07.07 djb Bugfix for 1.1 to convert the characters in the name of the font. // Bugs // By virtue of the way these are read in, this writes out fonts in the reverse order. // Thus \f23 is written before \f1. This is probably not a big deal, just unaesthetically // pleasing. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - WriteFontTableEntriesTo: destinationFile { NXHashState state = [fontTable initState]; Integer fontNumber; Instance fontEntry; Instance temptoken; CString tempName; // // Step to the next entry in the fontTable. If there is a next entry, we get // YES and continue // while ([fontTable nextState: &state key: (void **)&fontNumber value: (const void **)&fontEntry] == YES) { // // If this font was used more than 0 times, we will write it out. // (Thus, we avoid writing out the unused entries that Word so happily writes) // if ([fontEntry GetCount] > 0) { // // Write the {\fxx // temptoken=[[rtfToken alloc] initTokenOfType: tokenBeginGroup]; [destinationFile WriteToken: temptoken]; temptoken=[[rtfToken alloc] initTokenOfType: tokenControlWord]; [temptoken SetTokenName: "f"]; [temptoken SetTokenValue: (Integer) [fontEntry GetNumber]]; [destinationFile WriteToken: temptoken]; // // Write the \yyy (the type of the font (e.g. modern) // [destinationFile WriteToken: [fontEntry GetFontType]]; // // Write: font-name ; } // temptoken = [[rtfToken alloc] initTokenOfType: tokenWord]; tempName = [fontEntry GetName]; [temptoken SetTokenName: tempName]; FreeCString(tempName); // // 93.07.07 djb A bug reported (by moi) revealed that if there was an 8 bit char // in a font name, it was not being converted when the rtf file was converted. // So, I've changed the straight 'write out' to the method to convert and // then write out. Note that we always do a full conversion. Note also // that this is affected by the current quote conversion preference // [self ConvertWordIn: temptoken To: destinationFile WithConversion: FullConversion]; temptoken = [[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: ";"]; [destinationFile WriteToken: temptoken]; temptoken=[[rtfToken alloc] initTokenOfType: tokenEndGroup]; [destinationFile WriteToken: temptoken]; } } return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: ConvertFrom:To: // Parameters: The file to be converted from, and the file to be converted to // Returns: self // Stores: error code // Description: // this long routine is the main converter of rtf tokens. // After doing lots of initalizations, walk through the file to get a sense of what is there, // and to gather all the information about the font table. // If necessary, change to an .rtfd file, and start up a pict converter. // After this, walk through the source file one token at a time. In certain cases, // such as in the font and color table, we switch into special modes to consume their // data properly. Each time, we keep a count of how deep we are (counting braces) // to know when to quit (the inital brace count comes futher down in the file from // where we do this consumption, (where we find the initial token in the group). // Otherwise, write out the token in a straight forward manner... more or less. // Do some character processing on \' tokens. And, for word tokens, call another // method (this one is already plenty big, thank you very much) to do any // conversions to be done there. // Bugs // If the source has foo\'xxbar and this would become fooñbar, this may cause the // single 'word' to be split across lines. // Reminder: // WriteToken takes posession of the token instance. We should not free it. // History: // 92.12.13 djb Beginning to clean up from 'proof of concept' form. // 93.04.03 djb Modified so used the char conversion stack rather than a local // variable called ConvertChars // 93.07.07 djb Enhancement for 1.1. Added code to convert \bullet to ·, \emdash to // Ð, etc. when converting a Mac RTF file. // 93.07.10 djb Added a bit to the code added on 07.07 so it calls a routine to write the token out // as it is if we haven't found it in any of the special cases. This addition is a bit of a hack, // since the code is duplicated a line later. But, this way the multiple if's are avoided if we are // doing a conversion from NeXT. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ConvertFrom: sourceFile To: destinationFile { Real modifier = 70.0 / [sourceFile FileSize]; CString theName; CString tempString; Integer GroupDepth = 0; Integer colorStartDepth = 0; Integer fontStartDepth = 0; WritingState state = normal; Boolean LookingForUL = NO; Integer tokenCount = 0; Instance myEntry; Instance thetoken; Instance tempToken; TokenType theType; NXHashState tableState; Integer fontNumber; Instance temptoken; [self ResetResults]; // // Initalize the instance variables for this file conversion, and set up the // proper character converter. // pictNum = 0; colorsUsed = NO; SymbolNumber = -1; addedSymbol = NO; numPicts = 0; [self ResetStack]; // 93.04.03 djb Added for char conversion bugfix if (ConvertSource == MacRTF) CharConverter = MacTextConverter; else { CharConverter = NeXTTextConverter; // // Set flag to look for underline if user so wishes. // if ( RemoveUnderline == YES) LookingForUL = YES; } // // Set up the object to hold type family information. // fontTable = [[HashTable alloc] initKeyDesc:"i" valueDesc: "@"]; // // Do a first pass through the file, counting various elements for our use here. // Then, convert the new contents of the font table. // [self examineFile: sourceFile]; [self PrepareFontTable]; // // If we found PICTs in the file, we must change ourselves to an rtfd file. Nasty. // (we only do this, though, if we're going to be writing something about them out) // if ( (numPicts > 0) &&( (ExtractPicts == YES) || (ConvertPictures == YES))) destinationFile = [myManager MakeDestAnRTFD]; // // If there's a destination file to convert to, then begin!!! // (the rtfd conversion above might have failed and left us nothing) // if (destinationFile != NullInstance) { [sourceFile MoveTo: 0]; // // ASSUMPTION: // the first three tokens in the file will be { \rtf and \mac (or \ansi) // }So, we read these three tokens in, and process accordingly. // If we fail to find the \rtf, then we drop into our main converter below. // thetoken = [sourceFile GetToken]; [destinationFile WriteToken: thetoken]; GroupDepth ++; thetoken = [sourceFile GetToken]; theName = [thetoken GetName]; if (strcmp(theName, "rtf") == 0) { [destinationFile WriteToken: thetoken]; thetoken = [sourceFile GetToken]; if (ConvertSource == MacRTF) // Ignore whatever it came in with... [thetoken SetTokenName: "ansi"]; else [thetoken SetTokenName: "mac"]; [destinationFile WriteToken: thetoken]; thetoken = [sourceFile GetToken]; } FreeCString(theName); // // Commence main conversion. // while ( [sourceFile GetErrorCode] == ERR_OK ) { // // If we're processing the font table: // 1) watch begin and end tokens so we know when we're out // 2) When we fall out, dump the fonts processed earlier, and change // state back to the normal processing state. // if (state == inFontTable) { theType =[thetoken GetType]; if (theType == tokenBeginGroup) { GroupDepth ++; [thetoken free]; } else if (theType == tokenEndGroup) { GroupDepth --; if (GroupDepth != fontStartDepth) [thetoken free]; else { [self WriteFontTableEntriesTo: destinationFile]; [destinationFile WriteToken: thetoken]; // need close brace state = normal; } } else [thetoken free]; } // // If we're processing the color table, and colors were not used in the document // then discard the whole table. Reset our state when we're done. // else if ( (state == inColorTable) && (colorsUsed == NO)) { theType =[thetoken GetType]; if (theType == tokenBeginGroup) GroupDepth ++; else if (theType == tokenEndGroup) { GroupDepth --; if (GroupDepth == colorStartDepth) state = normal; } [thetoken free]; } else { theName = [thetoken GetName]; // // Switch indented strangely so more room on lines. =( // switch([thetoken GetType]) { case tokenBeginGroup: [destinationFile WriteToken: thetoken]; GroupDepth ++; [self StartNewGroup]; // 93.04.03 added for char convert bugfix break; case tokenEndGroup: GroupDepth --; [destinationFile WriteToken: thetoken]; [self FinishGroup]; // 93.04.03 added for char convert bugfix break; case tokenControlWord: // // Process a control word, watching for special cases: // \f // \colortbl // \fonttbl // \pict // \ul // \bullet // \emdash // \endash // \lquote // \rquote // \ldblquote // \rdblquote // if (strcmp(theName, "f") == 0) { // // Determine whether text in this typeface should be // converted or not. store the value on the stack. // myEntry = [fontTable valueForKey: (const void *) [thetoken GetValue]]; if (myEntry == nil) [self SetCurrentConverstionTo: FullConversion]; else { if ([myEntry GetConversion] == FullConversion) [self SetCurrentConverstionTo: FullConversion]; else [self SetCurrentConverstionTo: NoConversion]; } [destinationFile WriteToken: thetoken]; } else if (strncmp(theName, "colortbl", 8) == 0) { state = inColorTable; colorStartDepth = GroupDepth -1; if (colorsUsed == YES) [destinationFile WriteToken: thetoken]; else { [destinationFile ClearThroughLastBegin]; [thetoken free]; } } else if (strcmp(theName, "fonttbl") == 0) { state = inFontTable; fontStartDepth = GroupDepth -1; [destinationFile WriteToken: thetoken]; } else if (strcmp(theName, "pict") == 0) { // // If we've found a Mac image, then call the convert routine // [destinationFile WriteToken: thetoken]; thetoken = [sourceFile GetToken]; FreeCString(theName); theName = [thetoken GetName]; [destinationFile WriteToken: thetoken]; if (strcmp(theName, "macpict") == 0) [self ConvertMacPictImageIn: sourceFile To: destinationFile]; [myManager SetPercentageDone: (modifier * [sourceFile GetCurrentPosition]) + 30]; } else if ((LookingForUL == YES) && (ConvertSource == NeXTRTF) && (strcmp(theName, "ul") == 0) && ([thetoken GetValue] == 0) ) { // // We we're still looking for \ul0, and we found it, zap it. // [thetoken free]; LookingForUL = NO; } // // 93.07.07 djb Added bullet, lquote, rquote, ldblquote, and rdblquote // conversions. Note that they are only done when converting // a Mac file, since NeXT stuff should not be generating these. // As always, these reveal some problems with the kinda kludgey // parser I have: Minorly, maybe I should be converting them // when going out. More significantly, this writing out of single // characters as word tokens runsthe risk of these being broken up // from their associated text, which we may not want. Neither // issue is very large, but.... it bugs the perfectionist in me. // else if (ConvertSource == MacRTF) { if (strcmp(theName, "bullet") == 0) { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "·"]; [destinationFile WriteToken: temptoken]; } else if (strcmp(theName, "emdash") == 0) { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "Ð"]; [destinationFile WriteToken: temptoken]; } else if (strcmp(theName, "endash") == 0) // 93.10.15 djb fixed from emdash { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "±"]; [destinationFile WriteToken: temptoken]; } else if (strcmp(theName, "lquote") == 0) { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "`"]; [destinationFile WriteToken: temptoken]; } else if (strcmp(theName, "rquote") == 0) { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "'"]; [destinationFile WriteToken: temptoken]; } else if (strcmp(theName, "ldblquote") == 0) { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "ª"]; [destinationFile WriteToken: temptoken]; } else if (strcmp(theName, "rdblquote") == 0) { temptoken=[[rtfToken alloc] initTokenOfType: tokenWord]; [temptoken SetTokenName: "º"]; [destinationFile WriteToken: temptoken]; } else // Added this so that we don't loose the token if it is none of the above. [destinationFile WriteToken: thetoken]; } else [destinationFile WriteToken: thetoken]; break; case tokenControlSymbol: // // If the token isn't a \', or a \<newline>, write it out. // Otherwise, process it some. // if ((strcmp(theName, "\n")== 0) || (strcmp(theName, "\r") == 0)) { // // Token is escaped end of line character. Convert to a \par. // Note: Generally \EOL seems to be treated the same // as \par. But \EOL isn't explicitly defined in the spec I have, // while \par is. Thus, my preference for it. // [thetoken free]; thetoken=[[rtfToken alloc] initTokenOfType: tokenControlWord]; [thetoken SetTokenName: "par"]; [destinationFile WriteToken: thetoken]; } else if (strcmp(theName, "\'") == 0) { // // If the current type font is to be converted, convert the chars. // if ([self GetCurrentConversion] == NoConversion) { if (ConvertSource == NeXTRTF) [destinationFile WriteToken: thetoken]; else { tempToken = [[rtfToken alloc] initTokenOfType: tokenWord]; tempString = NewCString(1); tempString[0] = (Character) [thetoken GetValue]; tempString[1] = EndOfCString; [tempToken SetTokenName: tempString]; [destinationFile WriteToken: tempToken]; FreeCString(tempString); [thetoken free]; } } else { [self ConvertThisChar: (Character) [thetoken GetValue] Into: destinationFile]; [thetoken free]; } } else [destinationFile WriteToken: thetoken]; break; case tokenWord: // // If we've found a word, stop looking for \ul0 // Then, convert the word we found. // LookingForUL = NO; [self ConvertWordIn: thetoken To: destinationFile WithConversion: [self GetCurrentConversion] ]; [thetoken free]; break; case tokenNoToken: // // This is here mainly to make the compiler stop telling me that // tokenNoToken isn't in the switch. Free the token while we're at it. // [thetoken free]; break; } FreeCString(theName); } // // Tell our manager how we're doing, and get a new token. // tokenCount++; if ((tokenCount %100) == 0) [myManager SetPercentageDone: (modifier * [sourceFile GetCurrentPosition]) + 30]; thetoken = [sourceFile GetToken]; } [thetoken free]; // Free the token that was last retrieved } tableState = [fontTable initState]; while ([fontTable nextState: &tableState key: (void **)&fontNumber value: (const void **)&myEntry] == YES) {[myEntry free];} [fontTable free]; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: ConvertWordIn:To:WithConversion: // Parameters: The word token we should be writing out, and the file to write it into. // Returns: self // Stores: none // Description: // This does the generally simple (?) task of writing a word token out. // If w are converting the current font, then we go through the string a character // at a time, and convert the letters. If we are altering the quotes, we also watch for // quote chars and map to the others as appropriate. // Bugs // Shouldn't we watching for non-printable ascii chars if we aren't // converting anyway? // History: // 93.01.02 djb Broke off from ConvertFrom:To:, and commented! // 93.04.03 djb Modified so ConvertChars is of ConvertTypes ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ConvertWordIn: thetoken To: destinationFile WithConversion: (ConvertTypes) ConvertChars { CString nameStart; CString namePtr = [thetoken GetName]; Character aCharacter; PositiveInteger index = 0; Instance myToken; Instance quoteToken; Character theQuote; CString subPart; PositiveInteger subloc; nameStart = namePtr; // // Write the word out, modifying any characters we need to. // if (AlterSingleQuotes == NO) { // // If we are not converting the current font, just write the word. // Otherwise, step through each character. If you find a character that is // not in the printable range, write out everything up to it, and then // have the char converted and printed out, and then continue. // if (ConvertChars == NoConversion) { myToken = [[rtfToken alloc] initTokenOfType: tokenWord]; [myToken SetTokenName: namePtr]; [destinationFile WriteToken: myToken]; } else { while (namePtr[index] != EndOfCString) { aCharacter = namePtr[index]; if ((aCharacter < 32) || (aCharacter > 126)) { namePtr[index] = EndOfCString; myToken = [[rtfToken alloc] initTokenOfType: tokenWord]; [myToken SetTokenName: namePtr]; [destinationFile WriteToken: myToken]; namePtr = &namePtr[++index]; index = 0; [self ConvertThisChar: aCharacter Into: destinationFile]; } else index++; } // // If there is still a chunk waiting to be written. // if (index != 0) { myToken = [[rtfToken alloc] initTokenOfType: tokenWord]; [myToken SetTokenName: namePtr]; [destinationFile WriteToken: myToken]; } } } else { // // Since we are converting quotes, we must examine every character in // the string. // while (namePtr[index] != EndOfCString) { // // Look for either start or end quote in namePtr. Store the character // in theQuote (If it failed to find a quote, it returns the index to the null // terminator) // index = strcspn(namePtr, "`\'"); theQuote = namePtr[index]; // // Don't do this work if the quote was the first character found. // if (index != 0) { // // We don't need the quote character any longer, so make it a // null terminator so we can easily process the portion of the string // that came before the quote. // namePtr[index] = EndOfCString; if (ConvertChars == NoConversion) { myToken = [[rtfToken alloc] initTokenOfType: tokenWord]; [myToken SetTokenName: namePtr]; [destinationFile WriteToken: myToken]; } else { // // We are converting the string, so walk through the part up to // the quote, and convert any special characters we find. // subPart = namePtr; subloc = 0; while (subPart[subloc] != EndOfCString) { aCharacter = subPart[subloc]; if ((aCharacter < 32) || (aCharacter > 126)) { // // we found a character that needs converting. Soooo. // make IT a null, write out that which went before, and // then convert it and continue. // subPart[subloc] = EndOfCString; myToken = [[rtfToken alloc] initTokenOfType: tokenWord]; [myToken SetTokenName: subPart]; [destinationFile WriteToken: myToken]; [self ConvertThisChar: aCharacter Into: destinationFile]; subPart = &subPart[++subloc]; subloc = 0; } else subloc++; } if (subloc != 0) { // // If there are still characters waiting to be written out, do so. // myToken = [[rtfToken alloc] initTokenOfType: tokenWord]; [myToken SetTokenName: subPart]; [destinationFile WriteToken: myToken]; } } } // // Having written out the text that comes before the quote, now // write out the quote itself. // switch (theQuote) { case EndOfCString : index--; break; case '\'' : quoteToken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol]; [quoteToken SetTokenName: "\'"]; if (ConvertSource == MacRTF) [quoteToken SetTokenValue: (Character) '©']; else [quoteToken SetTokenValue: (Character) 0xD5]; //mac curley quote [destinationFile WriteToken: quoteToken]; break; case '`' : quoteToken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol]; [quoteToken SetTokenName: "\'"]; if (ConvertSource == MacRTF) [quoteToken SetTokenValue: (Character) 'Á']; else [quoteToken SetTokenValue: (Character) 0xD4]; //mac curley quote [destinationFile WriteToken: quoteToken]; break; } // // Set namePtr to point just beyond the quote. // namePtr = &namePtr[index+1]; // 93.01.09 djb Wasn't resetting index. DUH. (final pre-beta 1.0 bug) index = 0; } } FreeCString(nameStart); return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: ConvertThisChar:Into: // Parameters: A character we are reading from, and a file to write into. // Returns: self // Stores: none // Description: // Given a character, this does any conversions needed, and writes it out to // the destination file. // Conversions that might be needed are: Convert between Mac and NeXT // character sets. Change some ctrl characters to rtf things (e.g. tab char to \tab). // Conversion to a \' notation if appropriate and going to the Mac. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ConvertThisChar: (Character) aCharacter Into: destinationFile { Character newCharacter; Boolean usedSymbol; Instance thetoken; CString tempString; CString newString; if (aCharacter == '\t') { thetoken=[[rtfToken alloc] initTokenOfType: tokenControlWord]; [thetoken SetTokenName: "tab"]; [destinationFile WriteToken: thetoken]; } else { // // Try to map the character to the other character set. // If we sould not map the char to a single dest character (e.g. NeXt's // thorn character must be converted to [thorn]) // If we could, then check if the result required Symbol. If so, then // write a proper font change out. // Otherwise, just write out the charaction. // newCharacter = [CharConverter ConvertCharacter: aCharacter]; if ([CharConverter GetErrorCode] == errCANTMAPTOONE) { // // If we could not map our character to a single destination character, // Then, resort to this hack. Store the character in a tiny string. Ask the // converter to convert it with it's string converter, which will give us a longer // version of the character (e.g. theNeXT thorn character becomes [thorn]), // turn this into a token, and write it out. // tempString = NewCString(1); tempString[0] = newCharacter; tempString[1] = EndOfCString; newString = [CharConverter ConvertString: tempString WithLength: 1]; thetoken = [[rtfToken alloc] initTokenOfType: tokenWord]; [thetoken SetTokenName: newString]; [destinationFile WriteToken: thetoken]; FreeCString(tempString); FreeCString(newString); } else { usedSymbol = [CharConverter GetBooleanFrom: SECOND_RESULT]; // // If we converted a mac source character, and it will only map to a symbol // character, then write out the introduction to the symbol sequence: // {\f23 (or whatever number symbol is) // if (usedSymbol == YES) { thetoken=[[rtfToken alloc] initTokenOfType: tokenBeginGroup]; [destinationFile WriteToken: thetoken]; thetoken=[[rtfToken alloc] initTokenOfType: tokenControlWord]; [thetoken SetTokenName: "f"]; [thetoken SetTokenValue: (Integer) SymbolNumber]; [destinationFile WriteToken: thetoken]; } // // In all cases, build the proper token type for the converted character // (a new \' or a word token, depending on whether the character is > or < 126), // and write it out. (I'm taking the liberty of putting ctrl characters out as // control symbols as well) Also, note that we do not convert to \', but always // to a litteral character when translating TO the NeXT. This is because // I've not seen Edit write out these commands, and edit mis-displayed some // chars (0xDF-0xFF converted to no visible character in Times-Roman, // when they very well should have) that were in this notation. // if ( ((newCharacter > 126) || (newCharacter < 32)) && (ConvertSource == NeXTRTF)) { thetoken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol]; [thetoken SetTokenName: "\'"]; [thetoken SetTokenValue: (Integer) newCharacter]; } else { thetoken = [[rtfToken alloc] initTokenOfType: tokenWord]; tempString = NewCString(1); tempString[0] = newCharacter; tempString[1] = EndOfCString; [thetoken SetTokenName: tempString]; FreeCString(tempString); } [destinationFile WriteToken: thetoken]; // // If we are, building the symbol sequence, add the closing group token. // if (usedSymbol == YES) { thetoken=[[rtfToken alloc] initTokenOfType: tokenEndGroup]; [destinationFile WriteToken: thetoken]; } } } return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: ConvertMacPictImageIn:To: // Parameters: The rtf file containing the picture info, and the file to write any stuff to. // Returns: self // Stores: none // Description: // Note 'To:', above, referes to the rtffile that is being converted into, not where we should // convert the pict to. // // In brief, consuming picture data from the source file, and treat in some way according // to user preferences. // When called, several things are assumed: // - The destination file has had {\pict\macpict written out to it // - The instance variable ExtractPicts will store whether we should write pict // data out as files, or whether we should just discard it // - The instance variable ConvertPicts will have a YES value if we are to try to // get the pict data converted into an eps file // - The variable KeepTokensForMe will have a YES value if all extra tokens in // the picture group should be kept (this is probably only of interest to me, // hence this setting is only changable by recompilation) // This will then do several things. It will discard the items mentiod above in the // destinationFile. It will then give the rtf file the WordsArePictures hint. // Following this, tokens are read in one after another. Ultimately, these are discarded // (unles KeepTokensForMe is YES, in which case they are written to a tiny rtf file). // If we are converting the image, however, we watch each, and figure out what the // width and height of the image should be, and proceed to write proper information // to the destinationFile. The picture data itself is then encountered. This is either then // discarded, or stored in a file. If we have been asked to, we will then pass the // name of this file to the pict converter to be converted. We then discard the final }. // Bugs // We deal with binary data inadequately. We should be getting the parameter to // \bin, and reading exactly that number of bytes in. // History: // 92.12.24 djb created initial version // 93.01.03 djb enhanced so it worked completely. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ConvertMacPictImageIn: sourceFile To: destinationFile { Instance thetoken; Instance tokenFile = NullInstance; // initialized to shut the compiler up Instance pictFile; TokenType thetokentype; Integer groupDepth = 0; CString theName; Boolean BinaryData = NO; Integer ImageWidth = 0; Integer ImageHeight = 0; CString newFilename; // extensionless name of file inside the .rtfd wrapper CString baseFilename; CString charLocation; CString theDirectory; CString epsFilename; CString tempFilename; CString justFilename; CString tempName; Boolean weHaveError = NO; ByteString buffer; PositiveInteger nameLength; PositiveInteger tempLength; PositiveInteger index; PositiveInteger destIndex; port_t thePort; Instance ourSpeaker = [NXApp appSpeaker]; int storedTimeout; // // Increment the count, to show which picture this is (1st, 2nd, etc) // pictNum ++; // // Compute base name for the file(s) we create here.Get the path to // the rtfd folder: /x/y/...z/foo.rtfd // extract 'foo' (the tempFilename) // build /x/y/...z/foo.rtfd/foo (the baseFilename) // build foo(picture#0000) (justFilename) // theDirectory = [destinationFile GetDirectory]; baseFilename = NewCString(strlen(theDirectory)+1); strcpy(baseFilename, theDirectory); strcat(baseFilename, "/"); tempFilename = NewCString(strlen(theDirectory)); charLocation = strrchr(theDirectory, '/'); if (charLocation == NullCString) charLocation = theDirectory; // No /. Weird. Use whole dir name. else charLocation++; // skip over the / that we just found strcpy(tempFilename, charLocation); charLocation = strchr(tempFilename, '.'); // Remove everything after first period if (charLocation != NullCString) charLocation[0] = EndOfCString; // amputate the string at the period // // 93.02.20 djb Added this loop to remove any spaces, {'s, }'s, /'s, \'s or .'s // found in the filename. Removing the \ and }'s is pretty necessary. // Removing the others is for the sake of sanity. We do this by walking // through tempFilename one char at a time. If the char is an acceptable // one, we move it to the next spot down in the filename string // (in the worst case, this will move it onto itself). // tempLength = strlen(tempFilename); destIndex = 0; for (index = 0; index < tempLength; index++) { // // If the current source character was not one of the ones we are looking for, // (the default case), we copy this next character to the next available // destination position in the same string and then increment the destination // position. // switch (tempFilename[index]) { case '\\' : case '}' : case '{' : case '.' : case ' ': case '/' : case '\'' : case '\"' : case '`': // Do nothing break; default : tempFilename[destIndex] = tempFilename[index]; destIndex++; break; } } tempFilename[destIndex] = EndOfCString; justFilename = NewCString(strlen(tempFilename)+15); sprintf(justFilename, "%s(picture#%.4ld)", tempFilename, pictNum); // only 999 images... FreeCString(tempFilename); FreeCString(theDirectory); // // Now, build an extensionless filename of the form // /x/y/...z/foo.rtfd/foo(picture#0000) // newFilename = NewCString(strlen(baseFilename)+strlen(justFilename)); sprintf(newFilename, "%s%s", baseFilename, justFilename); FreeCString(baseFilename); // // Create the temporary file. Note that we totally ignore any error codes. // if ((KeepTokensForMe == YES) && (ExtractPicts == YES)) { tempFilename = NewCString(strlen(newFilename)+7); strcpy(tempFilename, newFilename); strcat(tempFilename, ".tokens"); tokenFile = [[rtfFile alloc] initAndUse: tempFilename]; [tokenFile CreateAndOpenFor: FILE_WRITE]; FreeCString(tempFilename); } // // Discard the pending {\pict\macpict // And, tell the source file that the next word token it finds may be big // (and that it can do a hex convert) // (void) [destinationFile ClearThroughLastBegin]; [sourceFile Hint: WordsArePictures]; // // Read in any and all control tokens before the bitmap. For some of them, we'll // want to record particular information for later use. Most we just ignore. // (if we have the KeepTokensForMe set to YS, we also store these in our temp file) // thetoken = [sourceFile GetToken]; thetokentype = [thetoken GetType]; while (thetokentype != tokenWord) { switch (thetokentype) { case tokenBeginGroup: groupDepth++; break; case tokenEndGroup: groupDepth--; break; case tokenControlWord: theName = [thetoken GetName]; if (strcmp(theName, "picw") == 0) ImageWidth = [thetoken GetValue]; else if (strcmp(theName, "pich") == 0) ImageHeight = [thetoken GetValue]; else if (strcmp(theName, "bin") == 0) BinaryData = YES; FreeCString(theName); break; case tokenControlSymbol: case tokenNoToken: case tokenWord: // // Should be setting some kinda error // break; } if ((KeepTokensForMe == YES) && (ExtractPicts == YES)) [tokenFile WriteToken: thetoken]; else [thetoken free]; thetoken = [sourceFile GetToken]; thetokentype = [thetoken GetType]; } // // Close the notes file. // if ((KeepTokensForMe == YES) && (ExtractPicts == YES)) [tokenFile CloseAndSave]; // // Now, write out the proper info into the destination file, if we are converting. // if (ConvertPictures == YES) { // // 93.01.31 djb We used to be writing the NeXTGraphic out as a series of // rtf tokens. This was a bit precarious, and when it broke again // this time because the line had necessarily grown too long for the // rtf file, I dediced the rtffile needed a specialized method for writing // this construct out. So, all the convoluted stuff that was here has // been replaced by this one method call. // tempName = NewCString(strlen(justFilename)+4); sprintf(tempName, "%s.eps", justFilename); [destinationFile WriteNeXTGraphicAt: 0 WithName: tempName Width: ImageWidth Height: ImageHeight]; FreeCString(tempName); } // // We have just found a word token, (just before the if statement immediately above, // that is). This should be the picture token. If it isn't, then we're probably // in deep trouble. Sigh. Why doesn the spec say so little about what MUST be // where? If we are extracting or converting pictures, we must store this ascii // data as a binary pict file. // if ((ConvertPictures == YES) || (ExtractPicts == YES)) { // // If we are not extracting the pictures (storing them as part of the // .rtfd wrapper), then we will store them as temporary files to be cleaned // up by the OS later. Otherwise, we create the .pict file inside the wrapper. // if (ExtractPicts == NO) pictFile = [[File alloc] initAndUseTemporary]; else { tempFilename = NewCString(strlen(newFilename)+5); strcpy(tempFilename, newFilename); strcat(tempFilename, ".pict"); pictFile = [[File alloc] initAndUse: tempFilename]; FreeCString(tempFilename); } // // Open the file, and write a vanity pict header. // [pictFile CreateAndOpenFor: FILE_WRITE]; buffer = NewByteString(512); for (index = 0; index < 512; index+=16) strcpy((CString)&buffer[index], "Convert_RTF V1.1"); [pictFile Write: 512 BytesFrom: buffer]; FreeByteString(buffer); // // Now, write out the picture data. // If the picture data is binary, then we've got trouble because we read it // in wrong because we ignored the argument to \bin above. // if (BinaryData == YES) { // uh oh.... } else { // // Get a copy of the hex version of the picture data. // theName = [thetoken GetName]; nameLength = strlen(theName); buffer = [self Convert: nameLength BytesToBinFrom: (ByteString) theName]; [pictFile Write: nameLength / 2 BytesFrom: buffer]; FreeByteString(buffer); FreeCString(theName); } [pictFile CloseAndSave]; // // Finally, invoke the convert pict app, and ask it to convert the stuff. // if (ConvertPictures == YES) { thePort = NXPortFromName("Convert_PICT", NULL); if (thePort != PORT_NULL) { PictConverterIsOpen = YES; // // Set up the filenames, and call the IAC method.... // epsFilename = NewCString(strlen(newFilename)+5); strcpy(epsFilename, newFilename); strcat(epsFilename, ".eps"); tempFilename = [pictFile GetPathname]; ourSpeaker = [NXApp appSpeaker]; storedTimeout = [ourSpeaker sendTimeout]; [ourSpeaker setSendTimeout: 120000]; //60,000ms. 2 mintues. [ourSpeaker setSendPort: thePort ]; [ourSpeaker msgConvert: tempFilename To: epsFilename]; [ourSpeaker setSendTimeout: storedTimeout]; //restore it. FreeCString(tempFilename); FreeCString(epsFilename); } } } // // Discard the picture data. // [thetoken free]; // // Finally, read in the last token, which had better be the } token. // thetoken = [sourceFile GetToken]; if ( ([thetoken GetType] != tokenEndGroup) || (groupDepth != 0) ) weHaveError = YES; [thetoken free]; // // Undo the setting stored above, and free strings. // [destinationFile Hint: WordsAreNotPictures]; FreeCString(newFilename); FreeCString(justFilename); return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: Convert:BytesToBinFrom: // Parameters: A source buffer and it's length // Returns: A pointer to a buffer of the converted data // Stores: none // Description: // This takes a buffer of the form "0A0053FD..." (that is, a hex representation of // binary data), and returns a buffer with 0x0A0053FD... in it (binary rep). // It does this with an algorithm that probably is not optimal, that converts the // individual nibbles (each represented by a char of ascii) into a binary form. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (ByteString) Convert: (Integer) sourceSize BytesToBinFrom: (ByteString) source { Integer nibblecnt; Byte result; Character thenibble; ByteString buffer; PositiveInteger bufferSize; PositiveInteger bufferIndex = 0; PositiveInteger index; bufferSize = sourceSize / 2; // The source had BETTER be an even bytes length. buffer = NewByteString(bufferSize); for (index = 0; index < sourceSize; index+=2) { thenibble = source[index]; result = 0; // // GROSS algorithm. Loop through switch twice. First time using nibble1, // second time using nibble2. This allows us to use this switch for each nibble. // for (nibblecnt = 0; nibblecnt < 2; nibblecnt++) { result = result << 4; // 1st, shift nothing, 2nd time shift 1st nible to high nibble. switch (thenibble) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // subtracting '0' (0x30) from a character gives binary # result += (Byte) (thenibble - '0'); break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': result += (Byte) ((thenibble - 'A') + 10); break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': result += (Byte) ((thenibble - 'a') + 10); break; default: result += 0; // This is really an error condition. break; } thenibble = source[index+1]; } buffer[bufferIndex] = result; bufferIndex++; } return buffer; } // // 93.04.04 djb These methods were added today to fix a final bug in the // converter. Ideally, they should actually be implemented by another object. // However, it's not that important, and it will be easier to put them here // I just want to get this done. If I ever fix this converter so it works as // it should (a real parser, for instance) then these should move into their // own 'stack object'. Anyway. These routines will manipulate a stack of // flags indicating whether text should be converted or not. See above for // more details. // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: ResetStack // Parameters: none // Returns: self // Stores: none // Description: // This will set the current stack location back to 0. It also sets the first value on // the stack to use the current default conversion value the user has specified using // the preferences. It could legitimately free // the current stack and remalloc a small one, in the case that the last time the // stack got much too large. But, this is unlikely, so this won't. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ResetStack { StackLocation = 0; if (ConvertAllText == ConvertAll) CharConvertStack[StackLocation] = FullConversion; else CharConvertStack[StackLocation] = NoConversion; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: StartNewGroup // Parameters: none // Returns: self // Stores: none // Description: // This causes a new conversion value to be pushed on the stack. The value is // duplicated from what was previously on the top of the stack. Thus, if ConvertAll // was on the top of the stack, this pushes a new value of ConvertAll on the stack. // This is because a new group always will start out with the same font that was // used in the surrounding group. This value can be changed later. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - StartNewGroup { ConvertTypes *tempStack; StackLocation++; if (StackLocation > StackTop) { tempStack = malloc((1+StackTop)*sizeof(ConvertTypes) * 2); memcpy(tempStack, CharConvertStack, (1+StackTop)*sizeof(ConvertTypes)); StackTop = ((StackTop+1)*2) -1; free(CharConvertStack); CharConvertStack = tempStack; } CharConvertStack[StackLocation] = CharConvertStack[StackLocation-1]; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: FinishGroup // Parameters: none // Returns: self // Stores: none // Description: // When we finish a group, we simply pop the top element off the stack so we revert // back to the conversion value for the surrounding group ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - FinishGroup { StackLocation--; if (StackLocation < 0) StackLocation = 0; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: ChangeCurrentConverstionTo: // Parameters: The value to be replace the value at the top of the stack. // Returns: self // Stores: none // Description: // In violation of a pristine stack paradigm, this will replace the value on the top // of the stack with the specified value. This allows the top value to reflect whatever // the conversion is for the current group properly. This could actually be modeled // as a pop and a push. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - SetCurrentConverstionTo: (ConvertTypes) newValue { CharConvertStack[StackLocation] = newValue; return self; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Routine: GetCurrentConverstion: // Parameters: none // Returns: the ConvertTypes that is on the top of the stack // Stores: none // Description: // In violation of a pristine stack paradigm, this will replace the value on the top // of the stack with the specified value. This allows the top value to reflect whatever // the conversion is for the current group properly. This could actually be modeled // as a pop and a push. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (ConvertTypes) GetCurrentConversion { return CharConvertStack[StackLocation]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.