mirror of
https://github.com/wu736139669/ManagerDemo.git
synced 2026-08-05 05:55:42 +00:00
446 lines
10 KiB
Objective-C
446 lines
10 KiB
Objective-C
//
|
|
// DTTextAttachmentImage.m
|
|
// DTCoreText
|
|
//
|
|
// Created by Oliver Drobnik on 22.04.13.
|
|
// Copyright (c) 2013 Drobnik.com. All rights reserved.
|
|
//
|
|
|
|
#import "DTCoreText.h"
|
|
#import "DTBase64Coding.h"
|
|
|
|
#if TARGET_OS_IPHONE
|
|
#import "DTAnimatedGIF.h"
|
|
#endif
|
|
|
|
static NSCache *imageCache = nil;
|
|
|
|
@interface DTImageTextAttachment () // private stuff
|
|
|
|
+ (NSCache *)sharedImageCache;
|
|
|
|
@end
|
|
|
|
|
|
@implementation DTImageTextAttachment
|
|
{
|
|
DTImage *_image;
|
|
}
|
|
|
|
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
|
self = [super initWithCoder:aDecoder];
|
|
if (self) {
|
|
_image = [aDecoder decodeObjectForKey:@"image"];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
|
[super encodeWithCoder:aCoder];
|
|
[aCoder encodeObject:_image forKey:@"image"];
|
|
}
|
|
|
|
- (id)initWithElement:(DTHTMLElement *)element options:(NSDictionary *)options
|
|
{
|
|
self = [super initWithElement:element options:options];
|
|
|
|
if (self)
|
|
{
|
|
// get base URL
|
|
NSURL *baseURL = [options objectForKey:NSBaseURLDocumentOption];
|
|
NSString *src = [element.attributes objectForKey:@"src"];
|
|
|
|
[self _decodeImageSrc:src relativeToBaseURL:baseURL];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithImage:(DTImage *)image
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
self.image = image;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void)_decodeImageSrc:(NSString *)src relativeToBaseURL:(NSURL *)baseURL
|
|
{
|
|
NSURL *contentURL = nil;
|
|
|
|
// decode content URL
|
|
if ([src length]) // guard against img with no src
|
|
{
|
|
if ([src hasPrefix:@"data:"])
|
|
{
|
|
NSString *cleanStr = [[src componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
|
|
|
|
NSURL *dataURL = [NSURL URLWithString:cleanStr];
|
|
|
|
// try native decoding first
|
|
NSData *decodedData = [NSData dataWithContentsOfURL:dataURL];
|
|
|
|
// try own base64 decoding
|
|
if (!decodedData)
|
|
{
|
|
NSRange range = [cleanStr rangeOfString:@"base64,"];
|
|
|
|
if (range.length)
|
|
{
|
|
NSString *encodedData = [cleanStr substringFromIndex:range.location + range.length];
|
|
|
|
decodedData = [DTBase64Coding dataByDecodingString:encodedData];
|
|
}
|
|
}
|
|
|
|
// if we have image data, get the default display size
|
|
if (decodedData)
|
|
{
|
|
self.image = [[DTImage alloc] initWithData:decodedData];
|
|
_contentURL = nil;
|
|
}
|
|
}
|
|
else // normal URL
|
|
{
|
|
contentURL = [NSURL URLWithString:src];
|
|
|
|
if(!contentURL)
|
|
{
|
|
src = [src stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
|
contentURL = [NSURL URLWithString:src relativeToURL:baseURL];
|
|
}
|
|
|
|
if (![contentURL scheme])
|
|
{
|
|
// possibly a relative url
|
|
if (baseURL)
|
|
{
|
|
contentURL = [NSURL URLWithString:src relativeToURL:baseURL];
|
|
}
|
|
else
|
|
{
|
|
// file in app bundle
|
|
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
|
|
NSString *path = [bundle pathForResource:src ofType:nil];
|
|
|
|
if (path)
|
|
{
|
|
// Prevent a crash if path turns up nil.
|
|
contentURL = [NSURL fileURLWithPath:path];
|
|
}
|
|
else
|
|
{
|
|
// might also be in a different bundle, e.g. when unit testing
|
|
bundle = [NSBundle bundleForClass:[DTTextAttachment class]];
|
|
|
|
path = [bundle pathForResource:src ofType:nil];
|
|
if (path)
|
|
{
|
|
// Prevent a crash if path turns up nil.
|
|
contentURL = [NSURL fileURLWithPath:path];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if it's a local file we need to inspect it to get it's dimensions
|
|
if (!_displaySize.width || !_displaySize.height)
|
|
{
|
|
DTImage *image = _image;
|
|
|
|
// let's check if we have a cached image already then we can inspect that
|
|
if (!_image)
|
|
{
|
|
image = [[DTImageTextAttachment sharedImageCache] objectForKey:[contentURL absoluteString]];
|
|
}
|
|
|
|
if (!image)
|
|
{
|
|
// only local files we can directly load without punishment
|
|
if ([contentURL isFileURL])
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
NSString *ext = [[[contentURL lastPathComponent] pathExtension] lowercaseString];
|
|
|
|
if ([ext isEqualToString:@"gif"])
|
|
{
|
|
image = DTAnimatedGIFFromFile([contentURL path]);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
image = [[DTImage alloc] initWithContentsOfFile:[contentURL path]];
|
|
}
|
|
}
|
|
|
|
// cache that for later
|
|
if (image)
|
|
{
|
|
[[DTImageTextAttachment sharedImageCache] setObject:image forKey:[contentURL absoluteString]];
|
|
}
|
|
}
|
|
|
|
// we have an image, so we can set the original size and default display size
|
|
if (image)
|
|
{
|
|
_contentURL = nil;
|
|
[self _updateSizesFromImage:image];
|
|
}
|
|
}
|
|
|
|
// only remote images should have a URL
|
|
_contentURL = contentURL;
|
|
}
|
|
|
|
- (void)_updateSizesFromImage:(DTImage *)image
|
|
{
|
|
// set original size if there is none set yet
|
|
if (CGSizeEqualToSize(_originalSize, CGSizeZero))
|
|
{
|
|
_originalSize = image.size;
|
|
}
|
|
else
|
|
{
|
|
// get the other dimension if one is missing
|
|
|
|
if (!_originalSize.width && _originalSize.height)
|
|
{
|
|
CGFloat factor = _originalSize.height/image.size.height;
|
|
_originalSize.width = image.size.height * factor;
|
|
}
|
|
else if (_originalSize.width && !_originalSize.height)
|
|
{
|
|
CGFloat factor = _originalSize.width/image.size.width;
|
|
_originalSize.height = image.size.width * factor;
|
|
}
|
|
}
|
|
|
|
// initial display size matches original
|
|
if (CGSizeEqualToSize(CGSizeZero, _displaySize))
|
|
{
|
|
[self setDisplaySize:_originalSize withMaxDisplaySize:_maxImageSize];
|
|
}
|
|
else
|
|
{
|
|
// get the other dimension if one is missing
|
|
|
|
if (!_displaySize.width && _displaySize.height)
|
|
{
|
|
CGSize newDisplaySize = _displaySize;
|
|
|
|
CGFloat factor = _displaySize.height/_originalSize.height;
|
|
newDisplaySize.width = _originalSize.height * factor;
|
|
|
|
[self setDisplaySize:newDisplaySize withMaxDisplaySize:_maxImageSize];
|
|
}
|
|
else if (_displaySize.width && !_displaySize.height)
|
|
{
|
|
CGSize newDisplaySize = _displaySize;
|
|
|
|
CGFloat factor = _displaySize.width/_originalSize.width;
|
|
newDisplaySize.height = _originalSize.width * factor;
|
|
|
|
[self setDisplaySize:newDisplaySize withMaxDisplaySize:_maxImageSize];
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (NSCache *)sharedImageCache {
|
|
if (imageCache) return imageCache;
|
|
|
|
static dispatch_once_t onceToken; // lock
|
|
dispatch_once(&onceToken, ^{ // this block run only once
|
|
imageCache = [[NSCache alloc] init];
|
|
});
|
|
return imageCache;
|
|
}
|
|
|
|
#pragma mark - Alternative Representations
|
|
|
|
// makes a data URL of the image
|
|
- (NSString *)dataURLRepresentation
|
|
{
|
|
DTImage *image = self.image;
|
|
|
|
if (!image)
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
NSData *data = [image dataForPNGRepresentation];
|
|
NSString *encoded = [DTBase64Coding stringByEncodingData:data];
|
|
|
|
return [@"data:image/png;base64," stringByAppendingString:encoded];
|
|
}
|
|
|
|
#pragma mark - DTTextAttachmentDrawing
|
|
|
|
- (void)drawInRect:(CGRect)rect context:(CGContextRef)context
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
[self.image drawInRect:rect];
|
|
#endif
|
|
}
|
|
|
|
#pragma mark - DTTextAttachmentHTMLEncoding
|
|
|
|
- (NSString *)stringByEncodingAsHTML
|
|
{
|
|
NSMutableString *retString = [NSMutableString string];
|
|
NSString *urlString;
|
|
|
|
if (_contentURL)
|
|
{
|
|
|
|
if ([_contentURL isFileURL])
|
|
{
|
|
NSString *path = [_contentURL path];
|
|
|
|
NSRange range = [path rangeOfString:@".app/"];
|
|
|
|
if (range.length)
|
|
{
|
|
urlString = [path substringFromIndex:NSMaxRange(range)];
|
|
}
|
|
else
|
|
{
|
|
urlString = [_contentURL absoluteString];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
urlString = [_contentURL relativeString];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
urlString = [self dataURLRepresentation];
|
|
}
|
|
|
|
// output tag start
|
|
[retString appendString:@"<img"];
|
|
|
|
// build style for img/video
|
|
NSMutableString *styleString = [NSMutableString string];
|
|
|
|
switch (_verticalAlignment)
|
|
{
|
|
case DTTextAttachmentVerticalAlignmentBaseline:
|
|
{
|
|
// [classStyleString appendString:@"vertical-align:baseline;"];
|
|
break;
|
|
}
|
|
case DTTextAttachmentVerticalAlignmentTop:
|
|
{
|
|
[styleString appendString:@"vertical-align:text-top;"];
|
|
break;
|
|
}
|
|
case DTTextAttachmentVerticalAlignmentCenter:
|
|
{
|
|
[styleString appendString:@"vertical-align:middle;"];
|
|
break;
|
|
}
|
|
case DTTextAttachmentVerticalAlignmentBottom:
|
|
{
|
|
[styleString appendString:@"vertical-align:text-bottom;"];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_originalSize.width>0)
|
|
{
|
|
[styleString appendFormat:@"width:%.0fpx;", _originalSize.width];
|
|
}
|
|
|
|
if (_originalSize.height>0)
|
|
{
|
|
[styleString appendFormat:@"height:%.0fpx;", _originalSize.height];
|
|
}
|
|
|
|
// add local style for size, since sizes might vary quite a bit
|
|
if ([styleString length])
|
|
{
|
|
[retString appendFormat:@" style=\"%@\"", styleString];
|
|
}
|
|
|
|
[retString appendFormat:@" src=\"%@\"", urlString];
|
|
|
|
// attach the attributes dictionary
|
|
NSMutableDictionary *tmpAttributes = [_attributes mutableCopy];
|
|
|
|
// remove src,style, width and height we already have these
|
|
[tmpAttributes removeObjectForKey:@"src"];
|
|
[tmpAttributes removeObjectForKey:@"style"];
|
|
[tmpAttributes removeObjectForKey:@"width"];
|
|
[tmpAttributes removeObjectForKey:@"height"];
|
|
|
|
for (__strong NSString *oneKey in [tmpAttributes allKeys])
|
|
{
|
|
oneKey = [oneKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
|
NSString *value = [[tmpAttributes objectForKey:oneKey] stringByAddingHTMLEntities];
|
|
[retString appendFormat:@" %@=\"%@\"", oneKey, value];
|
|
}
|
|
|
|
// end
|
|
[retString appendString:@" />"];
|
|
|
|
return retString;
|
|
}
|
|
|
|
#pragma mark - Properties
|
|
|
|
/**
|
|
Accessor for the contents instance variable. If the content type is DTTextAttachmentTypeImage this returns a DTImage instance of the contents.
|
|
@returns Contents. If it is an image, a DTImage instance is returned. Otherwise it is returned as is.
|
|
*/
|
|
- (DTImage *)image
|
|
{
|
|
if (!_image)
|
|
{
|
|
if (_contentURL)
|
|
{
|
|
DTImage *image = [[DTImageTextAttachment sharedImageCache] objectForKey:[_contentURL absoluteString]];
|
|
|
|
// only local files can be loaded into cache
|
|
if (!image && [_contentURL isFileURL])
|
|
{
|
|
image = [[DTImage alloc] initWithContentsOfFile:[_contentURL path]];
|
|
|
|
// cache it
|
|
if (image)
|
|
{
|
|
[[DTImageTextAttachment sharedImageCache] setObject:image forKey:[_contentURL absoluteString]];
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
}
|
|
|
|
return _image;
|
|
}
|
|
|
|
- (void)setImage:(DTImage *)image
|
|
{
|
|
if (_image != image)
|
|
{
|
|
_image = image;
|
|
|
|
[self _updateSizesFromImage:_image];
|
|
}
|
|
}
|
|
|
|
- (void)setDisplaySize:(CGSize)displaySize
|
|
{
|
|
_displaySize = displaySize;
|
|
}
|
|
|
|
@end
|