trouble saving NSAttributedString, with image, to an RTF file

微笑、不失礼 提交于 2019-12-03 10:05:47

问题


I have some output that is a very simple RTF file. When I generate this document, the user can email it. All this works fine. The document looks good. Once I have the NSAttributedString, I make an NSData block, and write it to a file, like this:

NSData* rtfData = [attrString dataFromRange:NSMakeRange(0, [attrString length]) documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType} error:&error];

This file can be emailed. When I check the email all is good.

Now, I'm tasked with adding a UIImage at the top of the document. Great, so I'm creating an attributed string like this:

NSTextAttachment *attachment = [[NSTextAttachment alloc] init];

UIImage* image = [UIImage imageNamed:@"logo"];
attachment.image = image;
attachment.bounds = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);

NSMutableAttributedString *imageAttrString = [[NSAttributedString attributedStringWithAttachment:attachment] mutableCopy];

// sets the paragraph styling of the text attachment

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init] ;

[paragraphStyle setAlignment:NSTextAlignmentCenter];            // centers image horizontally

[paragraphStyle setParagraphSpacing:10.0f];   // adds some padding between the image and the following section

[imageAttrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [imageAttrString length])];
[imageAttrString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]];

At this point, in Xcode, I can do a QuickLook on imageAttrString, and it draws just fine.

Once this string is built, I'm doing this:

[attrString appendAttributedString:imageAttrString];

And then adding in all the rest of the attributed text that I originally generated.

When I look at the file now, there is no image. QuickLook looks good in the debugger, but no image in the final output.

Thanks in advance for any help with this.


回答1:


Although, RTF does support embedded images on Windows, apparently it doesn't on OS X. RTF was developed by Microsoft and they added embedded images in version 1.5 (http://en.wikipedia.org/wiki/Rich_Text_Format#Version_changes). I think that Apple took earlier version of the format and their solution to images in documents was RTFD. Here is what Apple documentation says about RTF:

Rich Text Format (RTF) is a text formatting language devised by Microsoft Corporation. You can represent character, paragraph, and document format attributes using plain text with interspersed RTF commands, groups, and escape sequences. RTF is widely used as a document interchange format to transfer documents with their formatting information across applications and computing platforms. Apple has extended RTF with custom commands, which are described in this chapter.

So no images are mentioned. Finally to prove that RTF doesn't support images on mac, download this RTF document - it will show photo in Windows WordPad and won't show it in OS X TextEdit.

So as Larme mentioned - you should choose RTFD file type when adding attachments. From Wikipedia:

Rich Text Format Directory, also known as RTFD (due to its extension .rtfd), or Rich Text Format with Attachments

Although you will be able to get NSData object that contains both the text and the image (judging by its size), via dataFromRange:documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType} error:] you probably won't be able to save it so that it could be opened successfully. At least - I wasn't able to do that.

That's probably because actually RTFD is not a file format - it's a format of a bundle. To check it, you could use TextEdit on your mac to create a new document, add image and a text to it and save it as a file. Then right click on that file and choose Show Package Contents and you'll notice that the directory contains both your image and the text in RTF format.

However you will be able to save this document successfully with this code:

NSFileWrapper *fileWrapper = [imageAttrString fileWrapperFromRange:NSMakeRange(0, [imageAttrString length]) documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType} error:&error];
[fileWrapper writeToURL:yourFileURL options:NSFileWrapperWritingAtomic originalContentsURL:nil error:&error];

Because apparently NSFileWrapper knows how to deal with RTFD documents while NSData has no clue of what it contains.

However the main problem still remains - how to send it in email? Because RTFD document is a directory not a file, I'd say it's not very well suited for sending by email, however you can zip it and send with an extension .rtfd.zip. The extension here is the crucial because it will tell Mail app how to display contents of the attachment when user taps on it. Actually it will work also in Gmail and probably other email apps on iOS because it's the UIWebView that knows how to display .rtfd.zip. Here is a technical note about it: https://developer.apple.com/library/ios/qa/qa1630/_index.html#//apple_ref/doc/uid/DTS40008749

So the bottom line is - it can be done but the RTFD document will be an attachment to the email not the email content itself. If you want to have it as an email content you should probably look into embedding your image into HTML and sending the mail as HTML.




回答2:


As Andris mentioned, Apple RTF implementation does not support embedded images.

RTFD isn't a real alternative, as only a few OS X apps can open RTFD files. For example MS Office can't.

Creating a HTML file with embedded images might help in some cases, but - for example - most email clients don't support HTML with embedded images (Apple Mail does, Outlook however doesn't).

But fortunately there is a solution to create real RTF files with embedded images!

As the RTF format of course supports embedded images (only Apples implementation doesn't), images in a NSAttributedStrings (NSTextAttachments) can be (hand) coded into the RTF stream.

The following category does all the work needed:

/**
 NSAttributedString (MMRTFWithImages)

 */
@interface NSAttributedString (MMRTFWithImages)

- (NSString *)encodeRTFWithImages;

@end

/**
 NSAttributedString (MMRTFWithImages)

 */
@implementation NSAttributedString (MMRTFWithImages)

/*
 encodeRTFWithImages

 */
- (NSString *)encodeRTFWithImages {

    NSMutableAttributedString*  stringToEncode = [[NSMutableAttributedString alloc] initWithAttributedString:self];
    NSRange                     strRange = NSMakeRange(0, stringToEncode.length);

    //
    // Prepare the attributed string by removing the text attachments (images) and replacing them by
    // references to the images dictionary
    NSMutableDictionary*        attachmentDictionary = [NSMutableDictionary dictionary];
    while (strRange.length) {
        // Get the next text attachment
        NSRange effectiveRange;
        NSTextAttachment* textAttachment = [stringToEncode attribute:NSAttachmentAttributeName
                                                             atIndex:strRange.location
                                                      effectiveRange:&effectiveRange];

        strRange = NSMakeRange(NSMaxRange(effectiveRange), NSMaxRange(strRange) - NSMaxRange(effectiveRange));

        if (textAttachment) {
            // Text attachment found -> store image to image dictionary and remove the attachment
            NSFileWrapper*  fileWrapper = [textAttachment fileWrapper];

            UIImage*    image = [[UIImage alloc] initWithData:[fileWrapper regularFileContents]];
            // Kepp image size
            UIImage*    scaledImage = [self imageFromImage:image
                                               withSize:textAttachment.bounds.size];
            NSString*   imageKey = [NSString stringWithFormat:@"_MM_Encoded_Image#%zi_", [scaledImage hash]];
            [attachmentDictionary setObject:scaledImage
                                     forKey:imageKey];

            [stringToEncode removeAttribute:NSAttachmentAttributeName
                                      range:effectiveRange];
            [stringToEncode replaceCharactersInRange:effectiveRange
                                          withString:imageKey];
            strRange.length += [imageKey length] - 1;
        } // if
    } // while

    //
    // Create the RTF stream; without images but including our references
    NSData*             rtfData = [stringToEncode dataFromRange:NSMakeRange(0, stringToEncode.length)
                                    documentAttributes:@{
                                                         NSDocumentTypeDocumentAttribute:   NSRTFTextDocumentType
                                                         }
                                                 error:NULL];
    NSMutableString*    rtfString = [[NSMutableString alloc] initWithData:rtfData
                                                              encoding:NSASCIIStringEncoding];

    //
    // Replace the image references with hex encoded image data
    for (id key in attachmentDictionary) {
        NSRange     keyRange = [rtfString rangeOfString:(NSString*)key];
        if (NSNotFound != keyRange.location) {
            // Reference found -> replace with hex coded image data
            UIImage*    image = [attachmentDictionary objectForKey:key];
            NSData*     pngData = UIImagePNGRepresentation(image);

            NSString*   hexCodedString = [self hexadecimalRepresentation:pngData];
            NSString*   encodedImage = [NSString stringWithFormat:@"{\\*\\shppict {\\pict \\pngblip %@}}", hexCodedString];

            [rtfString replaceCharactersInRange:keyRange withString:encodedImage];
        }
    }
    return rtfString;
}

/*
 imageFromImage:withSize:

 Scales the input image to pSize
 */
- (UIImage *)imageFromImage:(UIImage *)pImage
                   withSize:(CGSize)pSize {

    UIGraphicsBeginImageContextWithOptions(pSize, NO, 0.0);
    [pImage drawInRect:CGRectMake(0, 0, pSize.width, pSize.height)];

    UIImage*    resultImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return resultImage;
}

/*
 hexadecimalRepresentation:

 Returns a hex codes string for all bytes in a NSData object
 */
- (NSString *) hexadecimalRepresentation:(NSData *)pData {

    static const char*  hexDigits = "0123456789ABCDEF";

    NSString*   result = nil;

    size_t      length = pData.length;
    if (length) {

        NSMutableData*  tempData = [NSMutableData dataWithLength:(length << 1)];    // double length
        if (tempData) {
            const unsigned char*    src = [pData bytes];
            unsigned char*          dst = [tempData mutableBytes];

            if ((src) &&
                (dst)) {
                // encode nibbles
                while (length--) {
                    *dst++ = hexDigits[(*src >> 4) & 0x0F];
                    *dst++ = hexDigits[(*src++ & 0x0F)];
                } // while

                result = [[NSString alloc] initWithData:tempData
                                               encoding:NSASCIIStringEncoding];
            } // if
        } // if
    } // if
    return result;
}

@end

The basic idea was taken from this article.



来源:https://stackoverflow.com/questions/23370275/trouble-saving-nsattributedstring-with-image-to-an-rtf-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!