trouble saving NSAttributedString, with image, to an RTF file

前端 未结 2 1709
萌比男神i
萌比男神i 2021-02-04 20:23

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 NSAttribut

2条回答
  •  南笙
    南笙 (楼主)
    2021-02-04 20:41

    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.

提交回复
热议问题