libs-gui/TextConverters/RTF/RTFConsumer.m

994 lines
21 KiB
Mathematica
Raw Normal View History

/* attributedStringConsumer.m
Copyright (C) 1999 Free Software Foundation, Inc.
Author: Stefan B<EFBFBD>hringer (stefan.boehringer@uni-bochum.de)
Date: Dec 1999
Author: Fred Kiefer <FredKiefer@gmx.de>
Date: June 2000
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
#include "RTFConsumer.h"
#include "RTFConsumerFunctions.h"
#include "RTFProducer.h"
/* we have to satisfy the scanner with a stream reading function */
typedef struct {
NSString *string;
int position;
int length;
} StringContext;
static void
initStringContext (StringContext *ctxt, NSString *string)
{
ctxt->string = string;
ctxt->position = 0;
ctxt->length = [string length];
}
static int
readNSString (StringContext *ctxt)
{
return (ctxt->position < ctxt->length )
? [ctxt->string characterAtIndex:ctxt->position++]: EOF;
}
// Hold the attributs of the current run
@interface RTFAttribute: NSObject <NSCopying>
{
@public
BOOL changed;
BOOL tabChanged;
NSMutableParagraphStyle *paragraph;
NSColor *fgColour;
NSColor *bgColour;
NSString *fontName;
float fontSize;
BOOL bold;
BOOL italic;
BOOL underline;
int script;
float real_fi, real_li;
}
- (NSFont*) currentFont;
- (NSNumber*) script;
- (NSNumber*) underline;
- (void) resetParagraphStyle;
- (void) resetFont;
- (void) addTab: (float)location type: (NSTextTabType)type;
@end
@implementation RTFAttribute
- (id) init
{
[self resetFont];
[self resetParagraphStyle];
return self;
}
- (void) dealloc
{
RELEASE(paragraph);
RELEASE(fontName);
RELEASE(fgColour);
RELEASE(bgColour);
[super dealloc];
}
- (id) copyWithZone: (NSZone*)zone
{
RTFAttribute *new = (RTFAttribute *)NSCopyObject (self, 0, zone);
new->paragraph = [paragraph mutableCopyWithZone: zone];
RETAIN(new->fontName);
RETAIN(new->fgColour);
RETAIN(new->bgColour);
return new;
}
- (NSFont*) currentFont
{
NSFont *font;
NSFontTraitMask traits = 0;
int weight;
if (bold)
{
weight = 9;
traits |= NSBoldFontMask;
}
else
{
weight = 5;
traits |= NSUnboldFontMask;
}
if (italic)
{
traits |= NSItalicFontMask;
}
else
{
traits |= NSUnitalicFontMask;
}
font = [[NSFontManager sharedFontManager] fontWithFamily: fontName
traits: traits
weight: weight
size: fontSize];
if (font == nil)
{
/* Before giving up and using a default font, we try if this is
* not the case of a font with a composite name, such as
* 'Helvetica-Light'. In that case, even if we don't have
* exactly an 'Helvetica-Light' font family, we might have an
* 'Helvetica' one. */
NSRange range = [fontName rangeOfString:@"-"];
if (range.location != NSNotFound)
{
NSString *fontFamily = [fontName substringToIndex: range.location];
font = [[NSFontManager sharedFontManager] fontWithFamily: fontFamily
traits: traits
weight: weight
size: fontSize];
}
if (font == nil)
{
NSDebugMLLog(@"RTFParser",
@"Could not find font %@ size %f traits %d weight %d",
fontName, fontSize, traits, weight);
/* Last resort, default font. :-( */
font = [NSFont userFontOfSize: fontSize];
}
}
return font;
}
- (NSNumber*) script
{
return [NSNumber numberWithInt: script];
}
- (NSNumber*) underline
{
if (underline)
return [NSNumber numberWithInt: NSSingleUnderlineStyle];
else
return nil;
}
- (void) resetParagraphStyle
{
ASSIGN(paragraph, [NSMutableParagraphStyle defaultParagraphStyle]);
real_fi = real_li = 0.0;
tabChanged = NO;
changed = YES;
}
- (void) resetFont
{
NSFont *font = [NSFont userFontOfSize:12];
ASSIGN(fontName, [font familyName]);
fontSize = 12.0;
italic = NO;
bold = NO;
underline = NO;
script = 0;
DESTROY(fgColour);
DESTROY(bgColour);
changed = YES;
}
- (void) addTab: (float) location type: (NSTextTabType) type
{
NSTextTab *tab = [[NSTextTab alloc] initWithType: NSLeftTabStopType
location: location];
if (!tabChanged)
{
// remove all tab stops
[paragraph setTabStops: [NSArray arrayWithObject: tab]];
tabChanged = YES;
}
else
{
[paragraph addTabStop: tab];
}
changed = YES;
RELEASE(tab);
}
@end
static BOOL classInheritsFromNSMutableAttributedString (Class c)
{
Class mutable = [NSMutableAttributedString class];
while (c != Nil)
{
if (c == mutable)
{
return YES;
}
c = [c superclass];
}
return NO;
}
@interface RTFConsumer (Private)
- (NSAttributedString*) parseRTF: (NSData *)rtfData
documentAttributes: (NSDictionary **)dict
class: (Class)class;
- (NSDictionary*) documentAttributes;
- (RTFAttribute*) attr;
- (void) push;
- (void) pop;
@end
@implementation RTFConsumer
/* RTFConsumer is the principal class and thus implements this */
+ (Class) classForFormat: (NSString *)format producer: (BOOL)flag
{
Class cClass = Nil;
if (flag)
{
if ([format isEqual: @"RTFD"])
{
cClass = [RTFDProducer class];
}
else if ([format isEqual: @"RTF"])
{
cClass = [RTFProducer class];
}
}
else
{
if ([format isEqual: @"RTFD"])
{
cClass = [RTFDConsumer class];
}
else if ([format isEqual: @"RTF"])
{
cClass = [RTFConsumer class];
}
}
return cClass;
}
+ (NSAttributedString*) parseFile: (NSFileWrapper *)wrapper
documentAttributes: (NSDictionary **)dict
class: (Class)class
{
RTFConsumer *consumer = [RTFConsumer new];
NSAttributedString *text = nil;
if ([wrapper isRegularFile])
{
text = [consumer parseRTF: [wrapper regularFileContents]
documentAttributes: dict
class: class];
}
else if ([wrapper isDirectory])
{
NSDictionary *files = [wrapper fileWrappers];
NSFileWrapper *contents;
//FIXME: We should store the files in the consumer
// We try to read the main file in the directory
if ((contents = [files objectForKey: @"TXT.rtf"]) != nil)
{
text = [consumer parseRTF: [contents regularFileContents]
documentAttributes: dict
class: class];
}
}
RELEASE(consumer);
return text;
}
+ (NSAttributedString*) parseData: (NSData *)rtfData
documentAttributes: (NSDictionary **)dict
class: (Class)class
{
RTFConsumer *consumer = [RTFConsumer new];
NSAttributedString *text;
text = [consumer parseRTF: rtfData
documentAttributes: dict
class: class];
RELEASE(consumer);
return text;
}
- (id) init
{
ignore = 0;
result = nil;
documentAttributes = nil;
fonts = nil;
attrs = nil;
colours = nil;
_class = Nil;
return self;
}
- (void) dealloc
{
RELEASE(fonts);
RELEASE(attrs);
RELEASE(colours);
RELEASE(result);
RELEASE(documentAttributes);
[super dealloc];
}
@end
@implementation RTFDConsumer
+ (NSAttributedString*) parseData: (NSData *)rtfData
documentAttributes: (NSDictionary **)dict
class: (Class)class
{
NSAttributedString *str;
NSFileWrapper *wrapper = [[NSFileWrapper alloc]
initWithSerializedRepresentation: rtfData];
str = [self parseFile: wrapper documentAttributes: dict class: class];
RELEASE (wrapper);
return str;
}
@end
@implementation RTFConsumer (Private)
- (NSDictionary*) documentAttributes
{
RETAIN(documentAttributes);
return AUTORELEASE(documentAttributes);
}
- (void) reset
{
RTFAttribute *attr = [RTFAttribute new];
ignore = 0;
DESTROY(result);
if (classInheritsFromNSMutableAttributedString (_class))
{
result = [[_class alloc] init];
}
else
{
result = [[NSMutableAttributedString alloc] init];
}
ASSIGN(documentAttributes, [NSMutableDictionary dictionary]);
ASSIGN(fonts, [NSMutableDictionary dictionary]);
ASSIGN(attrs, [NSMutableArray array]);
ASSIGN(colours, [NSMutableArray array]);
[attrs addObject: attr];
RELEASE(attr);
}
- (RTFAttribute*) attr
{
return [attrs lastObject];
}
- (void) push
{
RTFAttribute *attr = [[attrs lastObject] copy];
[attrs addObject: attr];
RELEASE(attr);
}
- (void) pop
{
[attrs removeLastObject];
((RTFAttribute*)[attrs lastObject])->changed = YES;
}
- (NSAttributedString*) parseRTF: (NSData *)rtfData
documentAttributes: (NSDictionary **)dict
class: (Class)class
{
CREATE_AUTORELEASE_POOL(pool);
RTFscannerCtxt scanner;
StringContext stringCtxt;
// We should read in the first few characters to find out which
// encoding we have
NSString *rtfString = [[NSString alloc]
initWithData: rtfData
encoding: NSASCIIStringEncoding];
// Reset this RFTConsumer, as it might already have been used!
_class = class;
[self reset];
initStringContext(&stringCtxt, rtfString);
lexInitContext(&scanner, &stringCtxt, (int (*)(void*))readNSString);
NS_DURING
GSRTFparse((void *)self, &scanner);
NS_HANDLER
NSLog(@"Problem during RTF Parsing: %@",
[localException reason]);
//[localException raise];
NS_ENDHANDLER
RELEASE(rtfString);
RELEASE(pool);
// document attributes
if (dict)
{
*dict = [self documentAttributes];
}
if (classInheritsFromNSMutableAttributedString (_class))
{
RETAIN (result);
AUTORELEASE (result);
return result;
}
else
{
return AUTORELEASE ([[_class alloc] initWithAttributedString: result]);
}
}
@end
#undef IGNORE
#define FONTS ((RTFConsumer *)ctxt)->fonts
#define COLOURS ((RTFConsumer *)ctxt)->colours
#define RESULT ((RTFConsumer *)ctxt)->result
#define IGNORE ((RTFConsumer *)ctxt)->ignore
#define TEXTPOSITION [RESULT length]
#define DOCUMENTATTRIBUTES ((RTFConsumer*)ctxt)->documentAttributes
#define CTXT [((RTFConsumer *)ctxt) attr]
#define CHANGED CTXT->changed
#define PARAGRAPH CTXT->paragraph
#define FONTNAME CTXT->fontName
#define SCRIPT CTXT->script
#define ITALIC CTXT->italic
#define BOLD CTXT->bold
#define UNDERLINE CTXT->underline
#define FGCOLOUR CTXT->fgColour
#define BGCOLOUR CTXT->bgColour
#define PAPERSIZE @"PaperSize"
#define LEFTMARGIN @"LeftMargin"
#define RIGHTMARGIN @"RightMargin"
#define TOPMARGIN @"TopMargin"
#define BUTTOMMARGIN @"ButtomMargin"
/*
we must implement from the rtfConsumerFunctions.h file (Supporting files)
this includes the yacc error handling and output
*/
/* handle errors (this is the yacc error mech) */
void GSRTFerror (const char *msg)
{
[NSException raise:NSInvalidArgumentException
format:@"Syntax error in RTF: %s", msg];
}
void GSRTFgenericRTFcommand (void *ctxt, RTFcmd cmd)
{
NSDebugLLog(@"RTFParser", @"encountered rtf cmd:%s", cmd.name);
if (!cmd.isEmpty)
NSDebugLLog(@"RTFParser", @" argument is %d\n", cmd.parameter);
}
//Start: we're doing some initialization
void GSRTFstart (void *ctxt)
{
NSDebugLLog(@"RTFParser", @"Start RTF parsing");
[RESULT beginEditing];
}
// Finished to parse one piece of RTF.
void GSRTFstop (void *ctxt)
{
//<!> close all open bolds et al.
[RESULT endEditing];
NSDebugLLog(@"RTFParser", @"End RTF parsing");
}
void GSRTFopenBlock (void *ctxt, BOOL ignore)
{
if (!IGNORE)
{
[(RTFConsumer *)ctxt push];
}
// Switch off any output for ignored block statements
if (ignore)
{
IGNORE++;
}
}
void GSRTFcloseBlock (void *ctxt, BOOL ignore)
{
if (ignore)
{
IGNORE--;
}
if (!IGNORE)
{
[(RTFConsumer *)ctxt pop];
}
}
void GSRTFmangleText (void *ctxt, const char *text)
{
int oldPosition = TEXTPOSITION;
int textlen = strlen(text);
NSRange insertionRange = NSMakeRange(oldPosition,0);
NSMutableDictionary *attributes;
if (!IGNORE && textlen)
{
[RESULT replaceCharactersInRange: insertionRange
withString: [NSString stringWithCString:text]];
if (CHANGED)
{
attributes = [NSMutableDictionary
dictionaryWithObjectsAndKeys:
[CTXT currentFont], NSFontAttributeName,
[[PARAGRAPH copy] autorelease], NSParagraphStyleAttributeName,
nil];
if (UNDERLINE)
{
[attributes setObject: [CTXT underline]
forKey: NSUnderlineStyleAttributeName];
}
if (SCRIPT)
{
[attributes setObject: [CTXT script]
forKey: NSSuperscriptAttributeName];
}
if (FGCOLOUR != nil)
{
[attributes setObject: FGCOLOUR
forKey: NSForegroundColorAttributeName];
}
if (BGCOLOUR != nil)
{
[attributes setObject: BGCOLOUR
forKey: NSBackgroundColorAttributeName];
}
[RESULT setAttributes: attributes
range: NSMakeRange(oldPosition, textlen)];
CHANGED = NO;
}
}
}
void GSRTFregisterFont (void *ctxt, const char *fontName,
RTFfontFamily family, int fontNumber)
{
NSString *fontNameString;
NSNumber *fontId = [NSNumber numberWithInt: fontNumber];
if (!fontName || !*fontName)
{
[NSException raise: NSInvalidArgumentException
format: @"Error in RTF (font omitted?), position:%d",
TEXTPOSITION];
}
// exclude trailing ';' from fontName
if (';' == fontName[strlen(fontName)-1])
{
fontNameString = [NSString stringWithCString: fontName
length: strlen(fontName)-1];
}
else
{
fontNameString = [NSString stringWithCString: fontName
length: strlen(fontName)];
}
[FONTS setObject: fontNameString forKey: fontId];
}
void GSRTFfontNumber (void *ctxt, int fontNumber)
{
NSNumber *fontId = [NSNumber numberWithInt: fontNumber];
NSString *fontName = [FONTS objectForKey: fontId];
if (fontName == nil)
{
/* we're about to set an unknown font */
[NSException raise: NSInvalidArgumentException
format: @"Error in RTF (referring to undefined font \\f%d), position:%d",
fontNumber,
TEXTPOSITION];
}
else
{
if (![fontName isEqual: FONTNAME])
{
ASSIGN(FONTNAME, fontName);
CHANGED = YES;
}
}
}
// <N> fontSize is in halfpoints according to spec
void GSRTFfontSize (void *ctxt, int fontSize)
{
float size = halfpoints2points(fontSize);
if (size != CTXT->fontSize)
{
CTXT->fontSize = size;
CHANGED = YES;
}
}
void GSRTFpaperWidth (void *ctxt, int width)
{
float fwidth = twips2points(width);
NSMutableDictionary *dict = DOCUMENTATTRIBUTES;
NSValue *val = [dict objectForKey: PAPERSIZE];
NSSize size;
if (val == nil)
{
size = NSMakeSize(fwidth, 792);
}
else
{
size = [val sizeValue];
size.width = fwidth;
}
[dict setObject: [NSValue valueWithSize: size] forKey: PAPERSIZE];
}
void GSRTFpaperHeight (void *ctxt, int height)
{
float fheight = twips2points(height);
NSMutableDictionary *dict = DOCUMENTATTRIBUTES;
NSValue *val = [dict objectForKey: PAPERSIZE];
NSSize size;
if (val == nil)
{
size = NSMakeSize(612, fheight);
}
else
{
size = [val sizeValue];
size.height = fheight;
}
[dict setObject: [NSValue valueWithSize: size] forKey: PAPERSIZE];
}
void GSRTFmarginLeft (void *ctxt, int margin)
{
float fmargin = twips2points(margin);
NSMutableDictionary *dict = DOCUMENTATTRIBUTES;
[dict setObject: [NSNumber numberWithFloat: fmargin] forKey: LEFTMARGIN];
}
void GSRTFmarginRight (void *ctxt, int margin)
{
float fmargin = twips2points(margin);
NSMutableDictionary *dict = DOCUMENTATTRIBUTES;
[dict setObject: [NSNumber numberWithFloat: fmargin] forKey: RIGHTMARGIN];
}
void GSRTFmarginTop (void *ctxt, int margin)
{
float fmargin = twips2points(margin);
NSMutableDictionary *dict = DOCUMENTATTRIBUTES;
[dict setObject: [NSNumber numberWithFloat: fmargin] forKey: TOPMARGIN];
}
void GSRTFmarginButtom (void *ctxt, int margin)
{
float fmargin = twips2points(margin);
NSMutableDictionary *dict = DOCUMENTATTRIBUTES;
[dict setObject: [NSNumber numberWithFloat: fmargin] forKey: BUTTOMMARGIN];
}
void GSRTFfirstLineIndent (void *ctxt, int indent)
{
NSMutableParagraphStyle *para = PARAGRAPH;
float findent = twips2points(indent);
CTXT->real_fi = findent;
findent = CTXT->real_li + CTXT->real_fi;
// for attributed strings only positiv indent is allowed
if ((findent >= 0.0) && ([para firstLineHeadIndent] != findent))
{
[para setFirstLineHeadIndent: findent];
CHANGED = YES;
}
}
void GSRTFleftIndent (void *ctxt, int indent)
{
NSMutableParagraphStyle *para = PARAGRAPH;
float findent = twips2points(indent);
CTXT->real_li = findent;
// for attributed strings only positiv indent is allowed
if ((findent >= 0.0) && ([para headIndent] != findent))
{
[para setHeadIndent: findent];
CHANGED = YES;
}
findent = CTXT->real_li + CTXT->real_fi;
if ((findent >= 0.0) && ([para firstLineHeadIndent] != findent))
{
[para setFirstLineHeadIndent: findent];
CHANGED = YES;
}
}
void GSRTFrightIndent (void *ctxt, int indent)
{
NSMutableParagraphStyle *para = PARAGRAPH;
float findent = twips2points(indent);
// for attributed strings only positiv indent is allowed
if ((findent >= 0.0) && ([para tailIndent] != findent))
{
[para setTailIndent: -findent];
CHANGED = YES;
}
}
void GSRTFtabstop (void *ctxt, int location)
{
float flocation = twips2points(location);
if (flocation >= 0.0)
{
[CTXT addTab: flocation type: NSLeftTabStopType];
}
}
void GSRTFalignCenter (void *ctxt)
{
NSMutableParagraphStyle *para = PARAGRAPH;
if ([para alignment] != NSCenterTextAlignment)
{
[para setAlignment: NSCenterTextAlignment];
CHANGED = YES;
}
}
void GSRTFalignJustified (void *ctxt)
{
NSMutableParagraphStyle *para = PARAGRAPH;
if ([para alignment] != NSJustifiedTextAlignment)
{
[para setAlignment: NSJustifiedTextAlignment];
CHANGED = YES;
}
}
void GSRTFalignLeft (void *ctxt)
{
NSMutableParagraphStyle *para = PARAGRAPH;
if ([para alignment] != NSLeftTextAlignment)
{
[para setAlignment: NSLeftTextAlignment];
CHANGED = YES;
}
}
void GSRTFalignRight (void *ctxt)
{
NSMutableParagraphStyle *para = PARAGRAPH;
if ([para alignment] != NSRightTextAlignment)
{
[para setAlignment: NSRightTextAlignment];
CHANGED = YES;
}
}
void GSRTFspaceAbove (void *ctxt, int space)
{
NSMutableParagraphStyle *para = PARAGRAPH;
float fspace = twips2points(space);
if (fspace >= 0.0)
{
[para setParagraphSpacing: fspace];
}
}
void GSRTFlineSpace (void *ctxt, int space)
{
NSMutableParagraphStyle *para = PARAGRAPH;
float fspace = twips2points(space);
if (space == 1000)
{
[para setMinimumLineHeight: 0.0];
[para setMaximumLineHeight: 0.0];
}
else if (fspace < 0.0)
{
[para setMaximumLineHeight: -fspace];
}
else
{
[para setMinimumLineHeight: fspace];
}
}
void GSRTFdefaultParagraph (void *ctxt)
{
[CTXT resetParagraphStyle];
}
void GSRTFstyle (void *ctxt, int style)
{
}
void GSRTFdefaultCharacterStyle (void *ctxt)
{
[CTXT resetFont];
}
void GSRTFaddColor (void *ctxt, int red, int green, int blue)
{
NSColor *colour = [NSColor colorWithCalibratedRed: red/255.0
green: green/255.0
blue: blue/255.0
alpha: 1.0];
[COLOURS addObject: colour];
}
void GSRTFaddDefaultColor (void *ctxt)
{
[COLOURS addObject: [NSColor textColor]];
}
void GSRTFcolorbg (void *ctxt, int color)
{
if ([COLOURS count] <= color)
{
ASSIGN (BGCOLOUR, [NSColor whiteColor]);
}
else
{
ASSIGN (BGCOLOUR, [COLOURS objectAtIndex: color]);
}
}
void GSRTFcolorfg (void *ctxt, int color)
{
if ([COLOURS count] <= color)
{
ASSIGN (FGCOLOUR, [NSColor blackColor]);
}
else
{
ASSIGN (FGCOLOUR, [COLOURS objectAtIndex: color]);
}
}
void GSRTFsubscript (void *ctxt, int script)
{
script = (int) (-halfpoints2points(script) / 3.0);
if (script != SCRIPT)
{
SCRIPT = script;
CHANGED = YES;
}
}
void GSRTFsuperscript (void *ctxt, int script)
{
script = (int) (halfpoints2points(script) / 3.0);
if (script != SCRIPT)
{
SCRIPT = script;
CHANGED = YES;
}
}
void GSRTFitalic (void *ctxt, BOOL state)
{
if (state != ITALIC)
{
ITALIC = state;
CHANGED = YES;
}
}
void GSRTFbold (void *ctxt, BOOL state)
{
if (state != BOLD)
{
BOLD = state;
CHANGED = YES;
}
}
void GSRTFunderline (void *ctxt, BOOL state)
{
if (state != UNDERLINE)
{
UNDERLINE = state;
CHANGED = YES;
}
}
void GSRTFparagraph (void *ctxt)
{
GSRTFmangleText(ctxt, "\n");
CTXT->tabChanged = NO;
}