libs-gui/TextConverters/RTF/RTFProducer.m
Fred Kiefer 4d8b1b42a9 * TextConverters/RTF/RTFProducer.m (-_addAttributesString:):
Correctly handle NSUnderlineStyleNone. This fixes bug #37043.
        * Headers/AppKit/NSOpenGL.h,
        * Source/NSOpenGLContext.m: New 10.6 methods to get/set the
        * CGLContextObj.



git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@35358 72102866-910b-0410-8b05-ffd578937521
2012-08-07 20:50:45 +00:00

1258 lines
35 KiB
Objective-C

/*
RTFProducer.m
Writes out a NSAttributedString as RTF
Copyright (C) 1999 Free Software Foundation, Inc.
Author: Daniel Boehringer
Date: November 1999
Modifications: Fred Kiefer <FredKiefer@gmx.de>
Date: June 2000
Modifications: Axel Katerbau <axel@objectpark.org>
Date: April 2003
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 Lesser 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <GNUstepGUI/GSHelpAttachment.h>
#import "RTFProducer.h"
// FIXME: Should be defined in a central place
#define PAPERSIZE @"PaperSize"
#define LEFTMARGIN @"LeftMargin"
#define RIGHTMARGIN @"RightMargin"
#define TOPMARGIN @"TopMargin"
#define BUTTOMMARGIN @"ButtomMargin"
#define HYPHENATIONFACTOR @"HyphenationFactor"
#define VIEWSIZE @"ViewSize"
#define VIEWZOOM @"ViewZoom"
#define VIEWMODE @"ViewMode"
#define points2twips(a) ((int)((a) * 20.0))
@interface RTFDProducer (Private)
- (NSArray *)_attachments;
- (NSDictionary *)_attributesOfLastRun;
- (void)_setAttributesOfLastRun: (NSDictionary *)aDict;
- (NSString *)_runStringForString: (NSString *)substring
attributes: (NSDictionary *)attributes;
- (NSString *)_ASCIIfiedString: (NSString *)string;
- (NSString *)_headerString;
- (NSString *)_trailerString;
- (NSString *)_bodyString;
- (NSString *)_RTFDStringFromAttributedString: (NSAttributedString *)aText
documentAttributes: (NSDictionary *)dict
inlineGraphics: (BOOL)inlineGraphics;
@end
@implementation RTFDProducer
+ (NSFileWrapper *)produceFileFrom: (NSAttributedString *)aText
documentAttributes: (NSDictionary *)dict
error: (NSError **)error
{
RTFDProducer *producer;
NSData *encodedText;
NSFileWrapper *wrapper;
producer = [[self alloc] init];
encodedText = [[producer _RTFDStringFromAttributedString: aText
documentAttributes: dict
inlineGraphics: NO]
dataUsingEncoding: NSASCIIStringEncoding];
// if ([aText containsAttachments])
if (YES)
{
NSMutableDictionary *fileDict;
NSFileWrapper *txt;
NSEnumerator *enumerator;
NSFileWrapper *fileWrapper;
fileDict = [NSMutableDictionary dictionary];
txt = [[NSFileWrapper alloc] initRegularFileWithContents: encodedText];
[fileDict setObject: txt forKey: @"TXT.rtf"];
RELEASE(txt);
enumerator = [[producer _attachments] objectEnumerator];
while ((fileWrapper = [enumerator nextObject]))
{
NSString *filename;
filename = [fileWrapper filename] ? [fileWrapper filename]
: [fileWrapper preferredFilename];
filename = [filename lastPathComponent];
[fileDict setObject: fileWrapper forKey: filename];
}
wrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers: fileDict];
}
else
{
wrapper = [[NSFileWrapper alloc]
initRegularFileWithContents: encodedText];
}
RELEASE(producer);
return AUTORELEASE(wrapper);
}
+ (NSData *)produceDataFrom: (NSAttributedString *)aText
documentAttributes: (NSDictionary *)dict
error: (NSError **)error
{
return [[self produceFileFrom: aText
documentAttributes: dict
error: error] serializedRepresentation];
}
- (id)init
{
/*
* maintain a dictionary for the used colours
* (for rtf-header generation)
*/
colorDict = [[NSMutableDictionary alloc] init];
/*
* maintain a dictionary for the used fonts
* (for rtf-header generation)
*/
fontDict = [[NSMutableDictionary alloc] init];
attachments = [[NSMutableArray alloc] init];
ASSIGN(fgColor, [NSColor textColor]);
ASSIGN(bgColor, [NSColor textBackgroundColor]);
ASSIGN(ulColor, [NSColor textColor]);
return self;
}
- (void)dealloc
{
RELEASE(text);
RELEASE(fontDict);
RELEASE(colorDict);
RELEASE(docDict);
RELEASE(attachments);
RELEASE(fgColor);
RELEASE(bgColor);
RELEASE(ulColor);
RELEASE(_attributesOfLastRun);
[super dealloc];
}
@end
@implementation RTFProducer
+ (NSData *)produceDataFrom: (NSAttributedString *)aText
documentAttributes: (NSDictionary *)dict
error: (NSError **)error
{
RTFProducer *producer;
NSData *data;
producer = [[self alloc] init];
data = [[producer _RTFDStringFromAttributedString: aText
documentAttributes: dict
inlineGraphics: YES]
dataUsingEncoding: NSASCIIStringEncoding];
RELEASE(producer);
return data;
}
+ (NSFileWrapper *)produceFileFrom: (NSAttributedString *)aText
documentAttributes: (NSDictionary *)dict
error: (NSError **)error
{
return AUTORELEASE([[NSFileWrapper alloc]
initRegularFileWithContents: [self produceDataFrom: aText
documentAttributes: dict
error: error]]);
}
@end
@implementation RTFDProducer (Private)
- (NSArray *)_attachments
{
return attachments;
}
- (NSDictionary *)_attributesOfLastRun
{
return _attributesOfLastRun;
}
- (void)_setAttributesOfLastRun: (NSDictionary *)aDict;
{
ASSIGN(_attributesOfLastRun, aDict);
}
- (NSString *)fontTable
{
if ([fontDict count])
{
NSMutableString *fontlistString;
NSEnumerator *fontEnum;
NSString *currFont;
NSArray *keyArray;
fontlistString = (NSMutableString *)[NSMutableString string];
keyArray = [fontDict allKeys];
keyArray = [keyArray sortedArrayUsingSelector: @selector(compare:)];
fontEnum = [keyArray objectEnumerator];
while ((currFont = [fontEnum nextObject]))
{
NSString *fontFamily;
// ##FIXME: If we ever have more fonts to map to families, we should
// use a dictionary
if ([currFont isEqualToString: @"Symbol"])
{
fontFamily = @"tech";
}
else if ([currFont isEqualToString: @"Helvetica"])
{
fontFamily = @"swiss";
}
else if ([currFont isEqualToString: @"Courier"])
{
fontFamily = @"modern";
}
else if ([currFont isEqualToString: @"Times"])
{
fontFamily = @"roman";
}
else
{
fontFamily = @"nil";
}
[fontlistString appendFormat: @"%@\\f%@ %@;",
[fontDict objectForKey: currFont], fontFamily, currFont];
}
return [NSString stringWithFormat: @"{\\fonttbl%@}\n", fontlistString];
}
else
{
return @"";
}
}
- (NSString *)colorTable
{
if ([colorDict count])
{
NSMutableString *result;
unsigned int count = [colorDict count];
id list[count];
NSEnumerator *keyEnum;
id next;
int i;
keyEnum = [colorDict keyEnumerator];
while ((next = [keyEnum nextObject]))
{
NSNumber *cn;
cn = [colorDict objectForKey: next];
list[[cn intValue] - 1] = next;
}
result = (NSMutableString *)[NSMutableString
stringWithString: @"{\\colortbl;"];
for (i = 0; i < count; i++)
{
NSColor *color;
color = [list[i] colorUsingColorSpaceName: NSCalibratedRGBColorSpace];
[result appendFormat: @"\\red%d\\green%d\\blue%d;",
(short)([color redComponent] * 255),
(short)([color greenComponent] * 255),
(short)([color blueComponent] * 255)];
}
[result appendString: @"}\n"];
return result;
}
else
{
return @"";
}
}
- (NSString *)documentAttributes
{
if (docDict)
{
NSMutableString *result;
NSValue *val;
NSNumber *num;
result = (NSMutableString *)[NSMutableString string];
if ((val = [docDict objectForKey: PAPERSIZE]))
{
NSSize size = [val sizeValue];
[result appendFormat: @"\\paperw%d\\paperh%d",
(short)points2twips(size.width),
(short)points2twips(size.height)];
}
if ((num = [docDict objectForKey: LEFTMARGIN]))
{
[result appendFormat: @"\\margl%d",
(short)points2twips([num floatValue])];
}
if ((num = [docDict objectForKey: RIGHTMARGIN]))
{
[result appendFormat: @"\\margr%d",
(short)points2twips([num floatValue])];
}
if ((num = [docDict objectForKey: TOPMARGIN]))
{
[result appendFormat: @"\\margt%d",
(short)points2twips([num floatValue])];
}
if ((num = [docDict objectForKey: BUTTOMMARGIN]))
{
[result appendFormat: @"\\margb%d",
(short)points2twips([num floatValue])];
}
if ((val = [docDict objectForKey: VIEWSIZE]))
{
NSSize size = [val sizeValue];
[result appendFormat: @"\\vieww%d\\viewh%d",
(short)points2twips(size.width),
(short)points2twips(size.height)];
}
if ((num = [docDict objectForKey: VIEWZOOM]))
{
float factor = [num floatValue];
[result appendFormat: @"\\viewscale%d",
(short)factor];
}
if ((num = [docDict objectForKey: VIEWMODE]))
{
int mode = [num intValue];
[result appendFormat: @"\\viewkind%d",
(short)mode];
}
if ((num = [docDict objectForKey: HYPHENATIONFACTOR]))
{
[result appendFormat: @"\\hyphauto1\\hyphfactor%d",
(short)points2twips([num floatValue]) * 5];
}
return result;
}
else
{
return @"";
}
}
- (NSString *)_headerString
/*" It is essential that before this method is called the method
-_bodyString is called! "*/
{
NSMutableString *result;
// As 'ugly' as it seems but had to add \cocoartf to let Apple's RTF parser
// grok paragraph spacing \saN. Should be no problem with other RTF parsers
// as this command will be ignored. So this is for compatibility with OS X.
result = (NSMutableString *)[NSMutableString stringWithString:
@"{\\rtf1\\ansi\\ansicpg1252\\cocoartf102"];
[result appendString: [self fontTable]];
[result appendString: [self colorTable]];
[result appendString: [self documentAttributes]];
return result;
}
- (NSString *)_trailerString
{
return @"}";
}
- (NSString *)fontToken: (NSString *)fontName
{
NSString *fCount;
fCount = [fontDict objectForKey: fontName];
if (! fCount)
{
unsigned count = [fontDict count];
fCount = [NSString stringWithFormat: @"\\f%d", (short)count];
[fontDict setObject: fCount forKey: fontName];
}
return fCount;
}
- (int)numberForColor: (NSColor *)color
{
unsigned int cn;
NSNumber *num;
num = [colorDict objectForKey: color];
if (! num)
{
cn = [colorDict count] + 1;
[colorDict setObject: [NSNumber numberWithInt: cn] forKey: color];
}
else
{
cn = [num intValue];
}
return cn;
}
- (NSString *)paragraphStyle: (NSParagraphStyle *)paraStyle
{
NSMutableString *result;
int twips;
result = (NSMutableString *)[NSMutableString stringWithString: @"\\pard"];
if (! paraStyle)
{
return result;
}
// tabs
{
NSEnumerator *enumerator;
NSTextTab *tab;
enumerator = [[paraStyle tabStops] objectEnumerator];
while ((tab = [enumerator nextObject]))
{
switch ([tab tabStopType])
{
case NSLeftTabStopType:
// no tabkind emission needed
break;
case NSRightTabStopType:
[result appendString: @"\\tqr"];
break;
case NSCenterTabStopType:
[result appendString: @"\\tqc"];
break;
case NSDecimalTabStopType:
[result appendString: @"\\tqdec"];
break;
default:
NSLog(@"Unknown tab stop type.");
break;
}
[result appendString: [NSString stringWithFormat: @"\\tx%d",
(short)points2twips([tab location])]];
}
}
switch ((int)[paraStyle baseWritingDirection])
{
case NSWritingDirectionLeftToRight:
// default -> nothing to emit
break;
case NSWritingDirectionRightToLeft:
[result appendString: @"\\rtlpar"];
break;
default:
break;
}
// write first line indent and left indent
twips = points2twips([paraStyle headIndent]);
if (twips != 0)
{
[result appendFormat: @"\\li%d", (short)twips];
}
twips = points2twips([paraStyle firstLineHeadIndent]) - twips;
if (twips != 0)
{
[result appendFormat: @"\\fi%d", (short)twips];
}
// right indent
// this only works when doc attributes are given
{
NSNumber *rightMargin, *leftMargin;
NSValue *paperSize;
paperSize = [docDict objectForKey: PAPERSIZE];
rightMargin = [docDict objectForKey: RIGHTMARGIN];
leftMargin = [docDict objectForKey: LEFTMARGIN];
if (paperSize && leftMargin && rightMargin)
{
int rightMarginTwips;
int leftMarginTwips;
int tailIndentTwips;
int paperWidthTwips;
rightMarginTwips = points2twips([rightMargin floatValue]);
leftMarginTwips = points2twips([leftMargin floatValue]);
tailIndentTwips = points2twips([paraStyle tailIndent]);
paperWidthTwips = points2twips([paperSize sizeValue].width);
[result appendFormat: @"\\ri%d",
(short)(paperWidthTwips - rightMarginTwips
- leftMarginTwips - tailIndentTwips)];
}
}
twips = points2twips([paraStyle paragraphSpacing]);
if (twips != 0)
{
[result appendFormat: @"\\sa%d", (short)twips];
}
twips = points2twips([paraStyle minimumLineHeight]);
if (twips != 0)
{
[result appendFormat: @"\\sl%d", (short)twips];
}
twips = points2twips([paraStyle maximumLineHeight]);
if (twips != 0)
{
[result appendFormat: @"\\sl-%d", (short)twips];
}
switch ([paraStyle alignment])
{
case NSRightTextAlignment:
[result appendString: @"\\qr"];
break;
case NSCenterTextAlignment:
[result appendString: @"\\qc"];
break;
case NSLeftTextAlignment:
[result appendString: @"\\ql"];
break;
case NSJustifiedTextAlignment:
[result appendString: @"\\qj"];
break;
default:
[result appendString: @"\\ql"];
break;
}
return result;
}
- (NSString *)font: (NSFont *)font
{
NSString *fontName;
NSFontTraitMask traits, traitsOfLastRun;
NSMutableString *result;
NSFont *fontOfLastRun;
fontOfLastRun = [[self _attributesOfLastRun]
objectForKey: NSFontAttributeName];
result = (NSMutableString *)[NSMutableString string];
// name
fontName = [font familyName];
if ((! fontOfLastRun) || (! [fontName isEqualToString:
[fontOfLastRun familyName]]))
{
[result appendString: [self fontToken: fontName]];
}
// size
if ((! fontOfLastRun) || ([font pointSize] != [fontOfLastRun pointSize]))
{
[result appendFormat: @"\\fs%d", (short)(int)([font pointSize] * 2)];
}
// traits
traits = [[NSFontManager sharedFontManager] traitsOfFont: font];
traitsOfLastRun = [[NSFontManager sharedFontManager] traitsOfFont:
fontOfLastRun];
if ((traits & NSItalicFontMask) != (traitsOfLastRun & NSItalicFontMask))
{
if (traits & NSItalicFontMask)
{
[result appendString: @"\\i"];
}
else
{
[result appendString: @"\\i0"];
}
}
if ((traits & NSBoldFontMask) != (traitsOfLastRun & NSBoldFontMask))
{
if (traits & NSBoldFontMask)
{
[result appendString: @"\\b"];
}
else
{
[result appendString: @"\\b0"];
}
}
return result;
}
- (NSString *)_removeAttributesString: (NSDictionary *)attributesToRemove
{
NSMutableString *result;
NSEnumerator *enumerator;
NSString *attributeName;
result = (NSMutableString *)[NSMutableString string];
enumerator = [attributesToRemove keyEnumerator];
while ((attributeName = [enumerator nextObject]))
{
if ([attributeName isEqualToString: NSParagraphStyleAttributeName])
{
[result appendString: @"\\pard\\ql"];
}
else if ([attributeName isEqualToString: NSForegroundColorAttributeName])
{
[result appendString: @"\\cf0"];
}
else if ([attributeName isEqualToString: NSBackgroundColorAttributeName])
{
[result appendString: @"\\cb0"];
}
else if ([attributeName isEqualToString: NSUnderlineStyleAttributeName])
{
[result appendString: @"\\ulnone"];
}
else if ([attributeName isEqualToString: NSSuperscriptAttributeName])
{
[result appendString: @"\\nosupersub"];
}
else if ([attributeName isEqualToString: NSBaselineOffsetAttributeName])
{
NSNumber *value = [[self _attributesOfLastRun] objectForKey:
NSBaselineOffsetAttributeName];
int svalue = (int)[value floatValue];
if (svalue >= 0)
{
[result appendString: @"\\up0"];
}
else if (svalue < 0)
{
[result appendString: @"\\dn0"];
}
}
else if ([attributeName isEqualToString: NSLigatureAttributeName])
{
[result appendString: @"\\zwnj"];
}
else if ([attributeName isEqualToString: NSAttachmentAttributeName])
{
}
else if ([attributeName isEqualToString: NSFontAttributeName])
{
}
else if ([attributeName isEqualToString: NSLinkAttributeName])
{
[result appendString: @"}}"];
}
else
{
NSLog(@"(removal) Missing handling of '%@' attributes.",
attributeName);
// ##TODO: attributes missing e.g. NSKernAttributeName
}
}
return result;
}
- (NSString *)_stringWithRTFCharacters: (NSString *)string
{
NSString *result;
NSMutableData *resultData;
unichar *buffer;
int length, i;
BOOL uc_flagged = NO;
if (string == nil)
{
return nil;
}
length = [string length];
buffer = NSZoneCalloc([self zone], length, sizeof(unichar));
[string getCharacters: buffer];
resultData = [[NSMutableData alloc]
initWithCapacity: (int)(length * 1.2)];
for (i = 0; i < length; i++)
{
unichar c;
c = buffer[i];
if (c < 0x80)
{
// encoding found
char ansiChar;
ansiChar = (char)c;
switch (ansiChar)
{
case '\\':
[resultData appendBytes: "\\\\" length: 2];
break;
case '\n':
[resultData appendBytes: "\\par\n" length: 5];
break;
case '\t':
[resultData appendBytes: "\\tab " length: 5];
break;
case '{':
[resultData appendBytes: "\\{" length: 2];
break;
case '}':
[resultData appendBytes: "\\}" length: 2];
break;
case '`':
[resultData appendBytes: "\\lquote " length: 8];
break;
case '\'':
[resultData appendBytes: "\\rquote " length: 8];
break;
default:
[resultData appendBytes: &ansiChar length: 1];
break;
}
}
else if (c < 0xFF)
{
char unicodeCommand[16];
snprintf(unicodeCommand, 16, "\\'%X", (short)c);
unicodeCommand[15] = '\0';
[resultData appendBytes: unicodeCommand
length: strlen(unicodeCommand)];
}
else if (c == NSAttachmentCharacter)
{
[resultData appendBytes: "\\'AC}" length: 5];
}
else
{
// write unicode encoding
char unicodeCommand[16];
if (!uc_flagged)
{
// We don't supply an ANSI representation for Unicode characters
[resultData appendBytes: "\\uc0 " length: 5];
uc_flagged = YES;
}
snprintf(unicodeCommand, 16, "\\u%d ", (short)c);
unicodeCommand[15] = '\0';
[resultData appendBytes: unicodeCommand
length: strlen(unicodeCommand)];
}
}
NSZoneFree([self zone], buffer);
result = AUTORELEASE([[NSString alloc] initWithData: resultData
encoding: NSASCIIStringEncoding]);
RELEASE(resultData);
return result;
}
- (NSString *)_ASCIIfiedString: (NSString *)string;
{
// workaround:converting non-ASCII chars
NSData *lossyConversion;
lossyConversion = [string dataUsingEncoding: NSASCIIStringEncoding
allowLossyConversion: YES];
return AUTORELEASE([[NSString alloc] initWithData: lossyConversion
encoding: NSASCIIStringEncoding]);
}
- (NSString *)_addAttributesString: (NSDictionary *)attributesToAdd
{
NSMutableString *result;
NSEnumerator *enumerator;
NSString *attributeName;
result = (NSMutableString *)[NSMutableString string];
enumerator = [attributesToAdd keyEnumerator];
while ((attributeName = [enumerator nextObject]))
{
if ([attributeName isEqualToString: NSParagraphStyleAttributeName])
{
[result appendString: [self paragraphStyle:
[attributesToAdd objectForKey: NSParagraphStyleAttributeName]]];
}
else if ([attributeName isEqualToString: NSFontAttributeName])
{
[result appendString: [self font:
[attributesToAdd objectForKey: NSFontAttributeName]]];
}
else if ([attributeName isEqualToString: NSForegroundColorAttributeName])
{
NSColor *color = [attributesToAdd objectForKey:
NSForegroundColorAttributeName];
if (! [color isEqual: fgColor])
{
[result appendFormat: @"\\cf%d",
(short)[self numberForColor: color]];
}
}
else if ([attributeName isEqualToString: NSBackgroundColorAttributeName])
{
NSColor *color = [attributesToAdd objectForKey:
NSBackgroundColorAttributeName];
if (! [color isEqual: bgColor])
{
[result appendFormat: @"\\cb%d",
(short)[self numberForColor: color]];
}
}
else if ([attributeName isEqualToString: NSUnderlineColorAttributeName])
{
NSColor *color = [attributesToAdd objectForKey:
NSUnderlineColorAttributeName];
[result appendFormat: @"\\ulc%d",
(short)[self numberForColor: color]];
}
else if ([attributeName isEqualToString: NSUnderlineStyleAttributeName])
{
NSInteger styleMask = [[attributesToAdd objectForKey:
NSUnderlineStyleAttributeName] integerValue];
if ((styleMask & NSUnderlineByWordMask) == NSUnderlineByWordMask)
{
[result appendString: @"\\ulw"];
}
if (styleMask == NSUnderlineStyleNone)
{
[result appendString: @"\\ulnone"];
}
else if ((styleMask & NSUnderlineStyleDouble) == NSUnderlineStyleDouble)
{
[result appendString: @"\\uldb"];
}
else if ((styleMask & NSUnderlineStyleThick) == NSUnderlineStyleThick)
{
if ((styleMask & NSUnderlinePatternDot) == NSUnderlinePatternDot)
{
[result appendString: @"\\ulthd"];
}
else if ((styleMask & NSUnderlinePatternDash) == NSUnderlinePatternDash)
{
[result appendString: @"\\ulthdash"];
}
else if ((styleMask & NSUnderlinePatternDashDot) == NSUnderlinePatternDashDot)
{
[result appendString: @"\\ulthdashd"];
}
else if ((styleMask & NSUnderlinePatternDashDotDot) == NSUnderlinePatternDashDotDot)
{
[result appendString: @"\\ulthdashdd"];
}
else // Assume NSUnderlinePatternSolid
{
[result appendString: @"\\ulth"];
}
}
else // Assume NSUnderlineStyleSingle
{
if ((styleMask & NSUnderlinePatternDot) == NSUnderlinePatternDot)
{
[result appendString: @"\\uld"];
}
else if ((styleMask & NSUnderlinePatternDash) == NSUnderlinePatternDash)
{
[result appendString: @"\\uldash"];
}
else if ((styleMask & NSUnderlinePatternDashDot) == NSUnderlinePatternDashDot)
{
[result appendString: @"\\uldashd"];
}
else if ((styleMask & NSUnderlinePatternDashDotDot) == NSUnderlinePatternDashDotDot)
{
[result appendString: @"\\uldashdd"];
}
else // Assume NSUnderlinePatternSolid
{
[result appendString: @"\\ul"];
}
}
}
else if ([attributeName isEqualToString: NSSuperscriptAttributeName])
{
NSNumber *value = [attributesToAdd objectForKey:
NSSuperscriptAttributeName];
int svalue = [value intValue];
if (svalue > 0)
{
[result appendString: @"\\super"];
if (svalue > 1)
{
[result appendFormat: @"%d", (short)svalue];
}
}
else if (svalue < 0)
{
[result appendString: @"\\sub"];
if (svalue < -1)
{
[result appendFormat: @"%d", (short)-svalue];
}
}
}
else if ([attributeName isEqualToString: NSBaselineOffsetAttributeName])
{
NSNumber *value = [attributesToAdd objectForKey:
NSBaselineOffsetAttributeName];
int svalue = (int)([value floatValue] * 2);
if (svalue > 0)
{
[result appendFormat: @"\\up%d", (short)svalue];
}
else if (svalue < 0)
{
[result appendFormat: @"\\dn%d", (short)-svalue];
}
}
else if ([attributeName isEqualToString: NSLigatureAttributeName])
{
[result appendString: @"\\zwj"];
}
else if ([attributeName isEqualToString: NSAttachmentAttributeName])
{
NSTextAttachment *attachment;
if ((attachment = [attributesToAdd objectForKey:
NSAttachmentAttributeName]))
{
NSFileWrapper *attachmentFileWrapper;
NSString *attachmentFilename;
NSSize cellSize;
if ([attachment isKindOfClass: [GSHelpLinkAttachment class]])
{
GSHelpLinkAttachment *link =
(GSHelpLinkAttachment *)attachment;
[result appendString: @"{{\\NeXTHelpLink"];
[result appendString: @" \\markername "];
[result appendString: @";\\linkFilename "];
[result appendString: [link fileName]];
[result appendString: @";\\linkMarkername "];
[result appendString: [link markerName]];
[result appendString: @";}"];
}
else if ([attachment
isKindOfClass: [GSHelpMarkerAttachment class]])
{
GSHelpMarkerAttachment *marker =
(GSHelpMarkerAttachment *)attachment;
[result appendString: @"{{\\NeXTHelpMarker"];
[result appendString: @" \\markername "];
[result appendString: [marker markerName]];
[result appendString: @";}"];
}
else
{
attachmentFileWrapper = [attachment fileWrapper];
attachmentFilename = [attachmentFileWrapper filename];
if (! attachmentFilename)
{
attachmentFilename =
[attachmentFileWrapper preferredFilename];
if (! attachmentFilename)
{
// If we do not have a proper filename, we set it
// here, incrementing the number of unnamed attachment
// so we do not overwrite existing ones.
// FIXME: setting the filename extension to tiff
// is not that great, but as we anyway append
// \NeXTGraphic just after it makes sense... (without
// the extension the file is not loaded)
attachmentFilename =
[NSString stringWithFormat:
@"__unnamed_file_%d.tiff",
unnamedAttachmentCounter++];
[attachmentFileWrapper
setPreferredFilename: attachmentFilename];
}
}
/*
if ([attachmentFilename respondsToSelector:
@selector(fileSystemRepresentation)])
{
const char *fileSystemRepresentation;
fileSystemRepresentation =
[attachmentFilename fileSystemRepresentation];
attachmentFilename =
[self _encodedFilenameRepresentation:
fileSystemRepresentation];
}
else
*/
{
attachmentFilename =
[self _ASCIIfiedString: attachmentFilename];
}
cellSize = [[attachment attachmentCell] cellSize];
[result appendString: @"{{\\NeXTGraphic "];
[result appendString: [attachmentFilename lastPathComponent]];
[result appendFormat: @" \\width%d \\height%d}",
(short)points2twips(cellSize.width),
(short)points2twips(cellSize.height)];
[attachmentFileWrapper setFilename: attachmentFilename];
[attachmentFileWrapper
setPreferredFilename: attachmentFilename];
if (attachmentFileWrapper)
[attachments addObject: attachmentFileWrapper];
}
}
}
else if ([attributeName isEqualToString: NSLinkAttributeName])
{
id dest = [attributesToAdd objectForKey:
NSLinkAttributeName];
NSString *destString = @"";
if ([dest isKindOfClass: [NSURL class]])
{
destString = [dest absoluteString];
}
else if ([dest isKindOfClass: [NSString class]])
{
destString = [[NSURL URLWithString: dest] absoluteString];
}
// NOTE: RTF control characters (backslash, curly braces)
// will be escaped by -absoluteString, so the result is safe to
// concatenate into the RTF stream
[result appendString:
[NSString stringWithFormat: @"{\\field{\\*\\fldinst HYPERLINK \"%@\"}{\\fldrslt ", destString]];
}
else
{
NSLog(@"(addition) Missing handling of '%@' attributes.",
attributeName);
// ##TODO: attributes missing e.g. NSKernAttributeName
}
}
return result;
}
- (NSString *)_runStringForString: (NSString *)string
attributes: (NSDictionary *)attributes
{
NSMutableString *result;
NSMutableDictionary *attributesToAdd, *attributesToRemove;
NSEnumerator *enumerator;
NSString *attributeName;
result = (NSMutableString *)[NSMutableString stringWithCapacity:
[string length] + 15];
attributesToAdd = [[NSMutableDictionary alloc] init];
attributesToRemove = [[self _attributesOfLastRun] mutableCopy];
// calculation of deltas
enumerator = [attributes keyEnumerator];
while ((attributeName = [enumerator nextObject]))
{
id attributeValue;
id attributeValueOfLastRun;
attributeValue = [attributes objectForKey: attributeName];
attributeValueOfLastRun =
[attributesToRemove objectForKey: attributeName];
if (attributeValueOfLastRun)
{
if ([attributeValueOfLastRun isEqual: attributeValue])
{
[attributesToRemove removeObjectForKey: attributeName];
}
else
{
[attributesToAdd setObject: attributeValue forKey: attributeName];
}
}
else
{
[attributesToAdd setObject: attributeValue forKey: attributeName];
}
}
[result appendString: [self _removeAttributesString: attributesToRemove]];
[result appendString: [self _addAttributesString: attributesToAdd]];
RELEASE(attributesToRemove);
RELEASE(attributesToAdd);
if ([result length])
{
unichar c = [result characterAtIndex: [result length] - 1];
if ((c != '}') && (c != ' '))
{
// ensure delimiter
[result appendString: @" "];
}
}
[result appendString: [self _stringWithRTFCharacters: string]];
return result;
}
- (NSString *)_bodyString
{
NSString *string;
NSMutableString *result;
unsigned length;
NSRange effectiveRange;
string = [text string];
result = (NSMutableString *)[NSMutableString string];
length = [string length];
effectiveRange = NSMakeRange(0, 0);
while (effectiveRange.location < length)
{
NSDictionary *attributes;
CREATE_AUTORELEASE_POOL(pool);
attributes = [text attributesAtIndex: effectiveRange.location
longestEffectiveRange: &effectiveRange
inRange: NSMakeRange(effectiveRange.location,
length
- effectiveRange.location)];
[result appendString: [self _runStringForString:
[string substringWithRange: effectiveRange]
attributes: attributes]];
effectiveRange = NSMakeRange(NSMaxRange(effectiveRange), 0);
[self _setAttributesOfLastRun: attributes];
[pool drain];
}
[self _setAttributesOfLastRun: nil]; // cleanup, should be unneccessary
return result;
}
- (NSString *)_RTFDStringFromAttributedString: (NSAttributedString *)aText
documentAttributes: (NSDictionary *)dict
inlineGraphics: (BOOL)inlineGraphics
{
NSMutableString *output;
NSString *headerString;
NSString *trailerString;
NSString *bodyString;
ASSIGN(text, aText);
ASSIGN(docDict, dict);
output = (NSMutableString *)[NSMutableString string];
_inlineGraphics = inlineGraphics;
/*
* do not change order! (esp. body has to be generated first; builds context)
*/
bodyString = [self _bodyString];
trailerString = [self _trailerString];
headerString = [self _headerString];
[output appendString: headerString];
[output appendString: bodyString];
[output appendString: trailerString];
return output;
}
@end