844 lines
24 KiB
Objective-C
844 lines
24 KiB
Objective-C
//
|
|
// IJSVGImage.m
|
|
// IconJar
|
|
//
|
|
// Created by Curtis Hard on 30/08/2014.
|
|
// Copyright (c) 2014 Curtis Hard. All rights reserved.
|
|
//
|
|
|
|
#import "IJSVG.h"
|
|
#import "IJSVGCache.h"
|
|
#import "IJSVGExporter.h"
|
|
#import "IJSVGTransaction.h"
|
|
|
|
@implementation IJSVG
|
|
|
|
@synthesize renderingBackingScaleHelper;
|
|
@synthesize clipToViewport;
|
|
@synthesize renderQuality;
|
|
@synthesize style = _style;
|
|
|
|
- (void)dealloc
|
|
{
|
|
IJSVGBeginTransactionLock();
|
|
(void)([renderingBackingScaleHelper release]),
|
|
renderingBackingScaleHelper = nil;
|
|
(void)([_group release]), _group = nil;
|
|
(void)([_layerTree release]), _layerTree = nil;
|
|
(void)([_replacementColors release]), _replacementColors = nil;
|
|
(void)([_quartzRenderer release]), _quartzRenderer = nil;
|
|
(void)([_style release]), _style = nil;
|
|
[super dealloc];
|
|
IJSVGEndTransactionLock();
|
|
}
|
|
|
|
+ (id)svgNamed:(NSString*)string error:(NSError**)error
|
|
{
|
|
return [self.class svgNamed:string error:error delegate:nil];
|
|
}
|
|
|
|
+ (id)svgNamed:(NSString*)string
|
|
{
|
|
return [self.class svgNamed:string error:nil];
|
|
}
|
|
|
|
+ (id)svgNamed:(NSString*)string
|
|
useCache:(BOOL)useCache
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return [self.class svgNamed:string
|
|
useCache:useCache
|
|
error:nil
|
|
delegate:delegate];
|
|
}
|
|
|
|
+ (id)svgNamed:(NSString*)string delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return [self.class svgNamed:string error:nil delegate:delegate];
|
|
}
|
|
|
|
+ (id)svgNamed:(NSString*)string
|
|
error:(NSError**)error
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return [self svgNamed:string useCache:YES error:error delegate:delegate];
|
|
}
|
|
|
|
+ (id)svgNamed:(NSString*)string
|
|
useCache:(BOOL)useCache
|
|
error:(NSError**)error
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
NSBundle* bundle = NSBundle.mainBundle;
|
|
NSString* str = nil;
|
|
NSString* ext = [string pathExtension];
|
|
if (ext == nil || ext.length == 0) {
|
|
ext = @"svg";
|
|
}
|
|
if ((str = [bundle pathForResource:[string stringByDeletingPathExtension]
|
|
ofType:ext])
|
|
!= nil) {
|
|
return [[[self alloc] initWithFile:str
|
|
useCache:useCache
|
|
error:error
|
|
delegate:delegate] autorelease];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (id)initWithImage:(NSImage*)image
|
|
{
|
|
__block IJSVGGroupLayer* layer = nil;
|
|
__block IJSVGImageLayer* imageLayer = nil;
|
|
|
|
// make sure we obtain a lock, with whatever we do with layers!
|
|
IJSVGBeginTransactionLock();
|
|
// create the layers we require
|
|
layer = [[[IJSVGGroupLayer alloc] init] autorelease];
|
|
imageLayer =
|
|
[[[IJSVGImageLayer alloc] initWithImage:image] autorelease];
|
|
[layer addSublayer:imageLayer];
|
|
IJSVGEndTransactionLock();
|
|
|
|
// return the initialized SVG
|
|
return [self initWithSVGLayer:layer viewBox:imageLayer.frame];
|
|
}
|
|
|
|
- (id)initWithSVGLayer:(IJSVGGroupLayer*)group viewBox:(NSRect)viewBox
|
|
{
|
|
// this completely bypasses passing of files
|
|
if ((self = [super init]) != nil) {
|
|
// keep the layer tree
|
|
_layerTree = [group retain];
|
|
_viewBox = viewBox;
|
|
|
|
// any setups
|
|
[self _setupBasicsFromAnyInitializer];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithFile:(NSString*)file
|
|
{
|
|
return [self initWithFile:file delegate:nil];
|
|
}
|
|
|
|
- (id)initWithFile:(NSString*)file useCache:(BOOL)useCache
|
|
{
|
|
return [self initWithFile:file useCache:useCache error:nil delegate:nil];
|
|
}
|
|
|
|
- (id)initWithFile:(NSString*)file
|
|
useCache:(BOOL)useCache
|
|
error:(NSError**)error
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return [self initWithFilePathURL:[NSURL fileURLWithPath:file]
|
|
useCache:useCache
|
|
error:error
|
|
delegate:delegate];
|
|
}
|
|
|
|
- (id)initWithFile:(NSString*)file error:(NSError**)error
|
|
{
|
|
return [self initWithFile:file error:error delegate:nil];
|
|
}
|
|
|
|
- (id)initWithFile:(NSString*)file delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return [self initWithFile:file error:nil delegate:delegate];
|
|
}
|
|
|
|
- (id)initWithFile:(NSString*)file
|
|
error:(NSError**)error
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return [self initWithFilePathURL:[NSURL fileURLWithPath:file]
|
|
useCache:YES
|
|
error:error
|
|
delegate:delegate];
|
|
}
|
|
|
|
- (id)initWithFilePathURL:(NSURL*)aURL
|
|
{
|
|
return [self initWithFilePathURL:aURL useCache:YES error:nil delegate:nil];
|
|
}
|
|
|
|
- (id)initWithFilePathURL:(NSURL*)aURL error:(NSError**)error
|
|
{
|
|
return [self initWithFilePathURL:aURL useCache:YES error:error delegate:nil];
|
|
}
|
|
|
|
- (id)initWithFilePathURL:(NSURL*)aURL useCache:(BOOL)useCache
|
|
{
|
|
return
|
|
[self initWithFilePathURL:aURL
|
|
useCache:useCache
|
|
error:nil
|
|
delegate:nil];
|
|
}
|
|
|
|
- (id)initWithFilePathURL:(NSURL*)aURL delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
return
|
|
[self initWithFilePathURL:aURL
|
|
useCache:YES
|
|
error:nil
|
|
delegate:delegate];
|
|
}
|
|
|
|
- (id)initWithFilePathURL:(NSURL*)aURL
|
|
useCache:(BOOL)useCache
|
|
error:(NSError**)error
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
#ifndef __clang_analyzer__
|
|
|
|
// check the cache first
|
|
if (useCache && [IJSVGCache enabled]) {
|
|
IJSVG* svg = nil;
|
|
if ((svg = [IJSVGCache cachedSVGForFileURL:aURL]) != nil) {
|
|
// have to release, as this was called from an alloc..!
|
|
[self release];
|
|
return [svg retain];
|
|
}
|
|
}
|
|
|
|
// create the object
|
|
if ((self = [super init]) != nil) {
|
|
NSError* anError = nil;
|
|
_delegate = delegate;
|
|
|
|
// this is a really quick check against the delegate
|
|
// for methods that exist
|
|
[self _checkDelegate];
|
|
|
|
// create the group
|
|
_group = [[IJSVGParser groupForFileURL:aURL error:&anError delegate:self]
|
|
retain];
|
|
|
|
[self _setupBasicInfoFromGroup];
|
|
[self _setupBasicsFromAnyInitializer];
|
|
|
|
// something went wrong...
|
|
if (_group == nil) {
|
|
if (error != NULL) {
|
|
*error = anError;
|
|
}
|
|
(void)([self release]), self = nil;
|
|
return nil;
|
|
}
|
|
|
|
// cache the file
|
|
if (useCache && [IJSVGCache enabled]) {
|
|
[IJSVGCache cacheSVG:self fileURL:aURL];
|
|
}
|
|
}
|
|
#endif
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithSVGString:(NSString*)string
|
|
{
|
|
return [self initWithSVGString:string error:nil delegate:nil];
|
|
}
|
|
|
|
- (id)initWithSVGString:(NSString*)string error:(NSError**)error
|
|
{
|
|
return [self initWithSVGString:string error:error delegate:nil];
|
|
}
|
|
|
|
- (id)initWithSVGString:(NSString*)string
|
|
error:(NSError**)error
|
|
delegate:(id<IJSVGDelegate>)delegate
|
|
{
|
|
if ((self = [super init]) != nil) {
|
|
// this is basically the same as init with URL just
|
|
// bypasses the loading of a file
|
|
NSError* anError = nil;
|
|
_delegate = delegate;
|
|
[self _checkDelegate];
|
|
|
|
// setup the parser
|
|
_group = [[IJSVGParser alloc] initWithSVGString:string
|
|
error:&anError
|
|
delegate:self];
|
|
|
|
[self _setupBasicInfoFromGroup];
|
|
[self _setupBasicsFromAnyInitializer];
|
|
|
|
// something went wrong :(
|
|
if (_group == nil) {
|
|
if (error != NULL) {
|
|
*error = anError;
|
|
}
|
|
(void)([self release]), self = nil;
|
|
return nil;
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)discardDOM
|
|
{
|
|
// if we discard, we can no longer create a tree, so lets create tree
|
|
// upfront before we kill anything
|
|
[self layer];
|
|
|
|
// now clear memory
|
|
(void)([_group release]), _group = nil;
|
|
}
|
|
|
|
- (void)_setupBasicInfoFromGroup
|
|
{
|
|
// store the viewbox
|
|
_viewBox = _group.viewBox;
|
|
_proposedViewSize = _group.proposedViewSize;
|
|
}
|
|
|
|
- (void)_setupBasicsFromAnyInitializer
|
|
{
|
|
self.style = [[[IJSVGRenderingStyle alloc] init] autorelease];
|
|
self.clipToViewport = YES;
|
|
self.renderQuality = IJSVGRenderQualityFullResolution;
|
|
|
|
// setup low level backing scale
|
|
_lastProposedBackingScale = 0.f;
|
|
self.renderingBackingScaleHelper = ^CGFloat {
|
|
return 1.f;
|
|
};
|
|
}
|
|
|
|
- (NSString*)identifier
|
|
{
|
|
return _group.identifier;
|
|
}
|
|
|
|
- (void)_checkDelegate
|
|
{
|
|
_respondsTo.shouldHandleForeignObject =
|
|
[_delegate respondsToSelector:@selector(svg:shouldHandleForeignObject:)];
|
|
_respondsTo.handleForeignObject = [_delegate
|
|
respondsToSelector:@selector(svg:handleForeignObject:document:)];
|
|
_respondsTo.shouldHandleSubSVG =
|
|
[_delegate respondsToSelector:@selector(svg:foundSubSVG:withSVGString:)];
|
|
}
|
|
|
|
- (NSRect)viewBox
|
|
{
|
|
return _viewBox;
|
|
}
|
|
|
|
- (IJSVGGroup*)rootNode
|
|
{
|
|
return _group;
|
|
}
|
|
|
|
- (BOOL)isFont
|
|
{
|
|
return [_group isFont];
|
|
}
|
|
|
|
- (NSArray<IJSVGPath*>*)glyphs
|
|
{
|
|
return [_group glyphs];
|
|
}
|
|
|
|
- (NSArray<IJSVG*>*)subSVGs:(BOOL)recursive
|
|
{
|
|
return [_group subSVGs:recursive];
|
|
}
|
|
|
|
- (NSString*)SVGStringWithOptions:(IJSVGExporterOptions)options
|
|
{
|
|
IJSVGExporter* exporter =
|
|
[[[IJSVGExporter alloc] initWithSVG:self
|
|
size:self.viewBox.size
|
|
options:options] autorelease];
|
|
return [exporter SVGString];
|
|
}
|
|
|
|
- (NSImage*)imageWithSize:(NSSize)aSize
|
|
{
|
|
return [self imageWithSize:aSize flipped:NO error:nil];
|
|
}
|
|
|
|
- (NSImage*)imageWithSize:(NSSize)aSize error:(NSError**)error;
|
|
{
|
|
return [self imageWithSize:aSize flipped:NO error:error];
|
|
}
|
|
|
|
- (NSImage*)imageWithSize:(NSSize)aSize flipped:(BOOL)flipped
|
|
{
|
|
return [self imageWithSize:aSize flipped:flipped error:nil];
|
|
}
|
|
|
|
- (NSRect)computeOriginalDrawingFrameWithSize:(NSSize)aSize
|
|
{
|
|
[self _beginDraw:(NSRect){ .origin = CGPointZero, .size = aSize }];
|
|
return NSMakeRect(0.f, 0.f, _proposedViewSize.width * _clipScale,
|
|
_proposedViewSize.height * _clipScale);
|
|
}
|
|
|
|
- (NSImage*)imageWithSize:(NSSize)aSize
|
|
flipped:(BOOL)flipped
|
|
error:(NSError**)error
|
|
{
|
|
NSImage* im = [[[NSImage alloc] initWithSize:aSize] autorelease];
|
|
[im lockFocus];
|
|
CGContextRef ref = [[NSGraphicsContext currentContext] CGContext];
|
|
CGContextSaveGState(ref);
|
|
if (flipped) {
|
|
CGContextTranslateCTM(ref, 0.f, aSize.height);
|
|
CGContextScaleCTM(ref, 1.f, -1.f);
|
|
}
|
|
[self drawAtPoint:NSMakePoint(0.f, 0.f) size:aSize error:error];
|
|
CGContextRestoreGState(ref);
|
|
[im unlockFocus];
|
|
return im;
|
|
}
|
|
|
|
- (NSImage*)imageByMaintainingAspectRatioWithSize:(NSSize)aSize
|
|
flipped:(BOOL)flipped
|
|
error:(NSError**)error
|
|
{
|
|
NSRect rect = [self computeOriginalDrawingFrameWithSize:aSize];
|
|
return [self imageWithSize:rect.size flipped:flipped error:error];
|
|
}
|
|
|
|
- (NSData*)PDFData
|
|
{
|
|
return [self PDFData:nil];
|
|
}
|
|
|
|
- (NSData*)PDFData:(NSError**)error
|
|
{
|
|
return [self
|
|
PDFDataWithRect:(NSRect){ .origin = NSZeroPoint, .size = _viewBox.size }
|
|
error:error];
|
|
}
|
|
|
|
- (NSData*)PDFDataWithRect:(NSRect)rect
|
|
{
|
|
return [self PDFDataWithRect:rect error:nil];
|
|
}
|
|
|
|
- (NSData*)PDFDataWithRect:(NSRect)rect error:(NSError**)error
|
|
{
|
|
// create the data for the PDF
|
|
NSMutableData* data = [[[NSMutableData alloc] init] autorelease];
|
|
|
|
// assign the data to the consumer
|
|
CGDataConsumerRef dataConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)data);
|
|
const CGRect box = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width,
|
|
rect.size.height);
|
|
|
|
// create the context
|
|
CGContextRef context = CGPDFContextCreate(dataConsumer, &box, NULL);
|
|
|
|
CGContextBeginPage(context, &box);
|
|
|
|
// the context is currently upside down, doh! flip it...
|
|
CGContextScaleCTM(context, 1, -1);
|
|
CGContextTranslateCTM(context, 0, -box.size.height);
|
|
|
|
// make sure we set the masks to path bits n bobs
|
|
[self _beginVectorDraw];
|
|
{
|
|
// draw the icon
|
|
[self _drawInRect:(NSRect)box context:context error:error];
|
|
}
|
|
[self _endVectorDraw];
|
|
|
|
CGContextEndPage(context);
|
|
|
|
// clean up
|
|
CGPDFContextClose(context);
|
|
CGContextRelease(context);
|
|
CGDataConsumerRelease(dataConsumer);
|
|
return data;
|
|
}
|
|
|
|
- (void)endVectorDraw
|
|
{
|
|
[self _endVectorDraw];
|
|
}
|
|
|
|
- (void)beginVectorDraw
|
|
{
|
|
[self _beginVectorDraw];
|
|
}
|
|
|
|
- (void)_beginVectorDraw
|
|
{
|
|
// turn on converts masks to PDF's
|
|
// as PDF context and layer masks dont work
|
|
void (^block)(CALayer* layer, BOOL isMask) = ^void(CALayer* layer, BOOL isMask) {
|
|
((IJSVGLayer*)layer).convertMasksToPaths = YES;
|
|
};
|
|
[IJSVGLayer recursivelyWalkLayer:self.layer withBlock:block];
|
|
}
|
|
|
|
- (void)_endVectorDraw
|
|
{
|
|
// turn of convert masks to paths as not
|
|
// needed for generic rendering
|
|
void (^block)(CALayer* layer, BOOL isMask) = ^void(CALayer* layer, BOOL isMask) {
|
|
((IJSVGLayer*)layer).convertMasksToPaths = NO;
|
|
};
|
|
[IJSVGLayer recursivelyWalkLayer:self.layer withBlock:block];
|
|
}
|
|
|
|
- (void)prepForDrawingInView:(NSView*)view
|
|
{
|
|
// kill the render
|
|
if (view == nil) {
|
|
self.renderingBackingScaleHelper = nil;
|
|
return;
|
|
}
|
|
|
|
// construct the layer before drawing
|
|
[self layer];
|
|
|
|
// set the scale
|
|
__block NSView* weakView = view;
|
|
self.renderingBackingScaleHelper = ^CGFloat {
|
|
return weakView.window.screen.backingScaleFactor;
|
|
};
|
|
}
|
|
|
|
- (BOOL)drawAtPoint:(NSPoint)point size:(NSSize)aSize
|
|
{
|
|
return [self drawAtPoint:point size:aSize error:nil];
|
|
}
|
|
|
|
- (BOOL)drawAtPoint:(NSPoint)point size:(NSSize)aSize error:(NSError**)error
|
|
{
|
|
return
|
|
[self drawInRect:NSMakeRect(point.x, point.y, aSize.width, aSize.height)
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)drawInRect:(NSRect)rect
|
|
{
|
|
return [self drawInRect:rect error:nil];
|
|
}
|
|
|
|
- (BOOL)drawInRect:(NSRect)rect error:(NSError**)error
|
|
{
|
|
return [self _drawInRect:rect
|
|
context:[[NSGraphicsContext currentContext] CGContext]
|
|
error:error];
|
|
}
|
|
|
|
- (CGFloat)computeBackingScale:(CGFloat)actualScale
|
|
{
|
|
_backingScale = actualScale;
|
|
return (CGFloat)(_scale + actualScale);
|
|
}
|
|
|
|
- (NSRect)computeRectDrawingInRect:(NSRect)rect isValid:(BOOL*)valid
|
|
{
|
|
// we also need to calculate the viewport so we can clip
|
|
// the drawing if needed
|
|
NSRect viewPort = NSZeroRect;
|
|
viewPort.origin.x = round((rect.size.width / 2 - (_proposedViewSize.width / 2) * _clipScale) + rect.origin.x);
|
|
viewPort.origin.y = round(
|
|
(rect.size.height / 2 - (_proposedViewSize.height / 2) * _clipScale) + rect.origin.y);
|
|
viewPort.size.width = _proposedViewSize.width * _clipScale;
|
|
viewPort.size.height = _proposedViewSize.height * _clipScale;
|
|
|
|
// check the viewport
|
|
if (NSEqualRects(_viewBox, NSZeroRect) || _viewBox.size.width <= 0 || _viewBox.size.height <= 0 || NSEqualRects(NSZeroRect, viewPort) || CGRectIsEmpty(viewPort) || CGRectIsNull(viewPort) || viewPort.size.width <= 0 || viewPort.size.height <= 0) {
|
|
*valid = NO;
|
|
return NSZeroRect;
|
|
}
|
|
|
|
*valid = YES;
|
|
return viewPort;
|
|
}
|
|
|
|
- (void)drawInRect:(NSRect)rect context:(CGContextRef)context
|
|
{
|
|
[self _drawInRect:rect context:context error:nil];
|
|
}
|
|
|
|
- (BOOL)_drawInRect:(NSRect)rect
|
|
context:(CGContextRef)ref
|
|
error:(NSError**)error
|
|
{
|
|
// prep for draw...
|
|
@synchronized(self)
|
|
{
|
|
CGContextSaveGState(ref);
|
|
@try {
|
|
[self _beginDraw:rect];
|
|
|
|
// we also need to calculate the viewport so we can clip
|
|
// the drawing if needed
|
|
BOOL canDraw = NO;
|
|
NSRect viewPort = [self computeRectDrawingInRect:rect isValid:&canDraw];
|
|
// check the viewport
|
|
if (canDraw == NO) {
|
|
if (error != NULL) {
|
|
*error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain
|
|
code:IJSVGErrorDrawing
|
|
userInfo:nil] autorelease];
|
|
}
|
|
} else {
|
|
// clip to mask
|
|
if (self.clipToViewport == YES) {
|
|
CGContextClipToRect(ref, viewPort);
|
|
}
|
|
|
|
// add the origin back onto the viewport
|
|
viewPort.origin.x -= (_viewBox.origin.x) * _scale;
|
|
viewPort.origin.y -= (_viewBox.origin.y) * _scale;
|
|
|
|
// transforms
|
|
CGContextTranslateCTM(ref, viewPort.origin.x, viewPort.origin.y);
|
|
CGContextScaleCTM(ref, _scale, _scale);
|
|
|
|
// render the layer, its really important we lock
|
|
// the transaction when drawing
|
|
IJSVGBeginTransactionLock();
|
|
// do we need to update the backing scales on the
|
|
// layers?
|
|
if (self.renderingBackingScaleHelper != nil) {
|
|
[self _askHelperForBackingScale];
|
|
}
|
|
|
|
CGInterpolationQuality quality;
|
|
switch (self.renderQuality) {
|
|
case IJSVGRenderQualityLow: {
|
|
quality = kCGInterpolationLow;
|
|
break;
|
|
}
|
|
case IJSVGRenderQualityOptimized: {
|
|
quality = kCGInterpolationMedium;
|
|
break;
|
|
}
|
|
default: {
|
|
quality = kCGInterpolationHigh;
|
|
}
|
|
}
|
|
CGContextSetInterpolationQuality(ref, quality);
|
|
[self.layer renderInContext:ref];
|
|
IJSVGEndTransactionLock();
|
|
}
|
|
} @catch (NSException* exception) {
|
|
// just catch and give back a drawing error to the caller
|
|
if (error != NULL) {
|
|
*error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain
|
|
code:IJSVGErrorDrawing
|
|
userInfo:nil] autorelease];
|
|
}
|
|
}
|
|
CGContextRestoreGState(ref);
|
|
}
|
|
return (error == nil);
|
|
}
|
|
|
|
- (void)_askHelperForBackingScale
|
|
{
|
|
CGFloat scale = (self.renderingBackingScaleHelper)();
|
|
if (scale < 1.f) {
|
|
scale = 1.f;
|
|
}
|
|
|
|
// make sure we multiple the scale by the scale of the rendered clip
|
|
// or it will be blurry for gradients and other bitmap drawing
|
|
scale = (_scale * scale);
|
|
|
|
// dont do anything, nothing has changed, no point of iterating over
|
|
// every layer for no reason!
|
|
if (scale == _lastProposedBackingScale && renderQuality == _lastProposedRenderQuality) {
|
|
return;
|
|
}
|
|
|
|
IJSVGRenderQuality quality = self.renderQuality;
|
|
_lastProposedBackingScale = scale;
|
|
_lastProposedRenderQuality = quality;
|
|
|
|
// walk the tree
|
|
void (^block)(CALayer* layer, BOOL isMask) = ^void(CALayer* layer, BOOL isMask) {
|
|
IJSVGLayer* propLayer = ((IJSVGLayer*)layer);
|
|
propLayer.renderQuality = quality;
|
|
if (propLayer.requiresBackingScaleHelp == YES) {
|
|
propLayer.backingScaleFactor = scale;
|
|
}
|
|
};
|
|
|
|
// gogogo
|
|
[IJSVGLayer recursivelyWalkLayer:self.layer withBlock:block];
|
|
}
|
|
|
|
- (IJSVGLayer*)layerWithTree:(IJSVGLayerTree*)tree
|
|
{
|
|
// clear memory
|
|
if (_layerTree != nil) {
|
|
(void)([_layerTree release]), _layerTree = nil;
|
|
}
|
|
|
|
// force rebuild of the tree
|
|
IJSVGBeginTransactionLock();
|
|
_layerTree = [[tree layerForNode:_group] retain];
|
|
IJSVGEndTransactionLock();
|
|
return _layerTree;
|
|
}
|
|
|
|
- (IJSVGLayer*)layer
|
|
{
|
|
if (_layerTree != nil) {
|
|
return _layerTree;
|
|
}
|
|
|
|
// create the renderer and assign default values
|
|
// from this SVG object
|
|
IJSVGLayerTree* renderer = [[[IJSVGLayerTree alloc] init] autorelease];
|
|
renderer.viewBox = self.viewBox;
|
|
renderer.style = self.style;
|
|
|
|
// return the rendered layer
|
|
return [self layerWithTree:renderer];
|
|
}
|
|
|
|
- (void)setStyle:(IJSVGRenderingStyle*)style
|
|
{
|
|
(void)([_style release]), _style = nil;
|
|
_style = style.retain;
|
|
}
|
|
|
|
- (void)observeValueForKeyPath:(NSString*)keyPath
|
|
ofObject:(id)object
|
|
change:(NSDictionary<NSKeyValueChangeKey, id>*)change
|
|
context:(void*)context
|
|
{
|
|
// invalidate the tree if a style is set
|
|
if (object == _style) {
|
|
[self invalidateLayerTree];
|
|
}
|
|
}
|
|
|
|
- (void)setNeedsDisplay
|
|
{
|
|
[self invalidateLayerTree];
|
|
}
|
|
|
|
- (void)invalidateLayerTree
|
|
{
|
|
(void)([_layerTree release]), _layerTree = nil;
|
|
}
|
|
|
|
- (IJSVGColorList*)computedColorList:(BOOL*)hasPatternFills
|
|
{
|
|
IJSVGColorList* sheet = [[[IJSVGColorList alloc] init] autorelease];
|
|
void (^block)(CALayer* layer, BOOL isMask) = ^void(CALayer* layer,
|
|
BOOL isMask) {
|
|
if ([layer isKindOfClass:[IJSVGShapeLayer class]] && isMask == NO && layer.isHidden == NO) {
|
|
IJSVGShapeLayer* sLayer = (IJSVGShapeLayer*)layer;
|
|
NSColor* color = nil;
|
|
if (sLayer.fillColor != nil) {
|
|
color = [NSColor colorWithCGColor:sLayer.fillColor];
|
|
if (color.alphaComponent != 0.f) {
|
|
[sheet addColor:color];
|
|
}
|
|
}
|
|
if (sLayer.strokeColor != nil) {
|
|
color = [NSColor colorWithCGColor:sLayer.strokeColor];
|
|
color = [IJSVGColor computeColorSpace:color];
|
|
if (color.alphaComponent != 0.f) {
|
|
[sheet addColor:color];
|
|
}
|
|
}
|
|
|
|
// check for any patterns
|
|
if (sLayer.patternFillLayer != nil || sLayer.gradientFillLayer != nil || sLayer.gradientStrokeLayer != nil || sLayer.patternStrokeLayer != nil) {
|
|
if (hasPatternFills != nil && *hasPatternFills != YES) {
|
|
*hasPatternFills = YES;
|
|
}
|
|
|
|
// add any colors from gradients
|
|
IJSVGGradientLayer* gradLayer = nil;
|
|
IJSVGGradientLayer* gradStrokeLayer = nil;
|
|
if ((gradLayer = sLayer.gradientFillLayer) != nil) {
|
|
IJSVGColorList* gradSheet = gradLayer.gradient.computedColorList;
|
|
[sheet addColorsFromList:gradSheet];
|
|
}
|
|
if ((gradStrokeLayer = sLayer.gradientStrokeLayer) != nil) {
|
|
IJSVGColorList* gradSheet = gradStrokeLayer.gradient.computedColorList;
|
|
[sheet addColorsFromList:gradSheet];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// walk
|
|
[IJSVGLayer recursivelyWalkLayer:self.layer withBlock:block];
|
|
|
|
// return the colours!
|
|
return sheet;
|
|
}
|
|
|
|
- (void)_beginDraw:(NSRect)rect
|
|
{
|
|
// in order to correctly fit the the SVG into the
|
|
// rect, we need to work out the ratio scale in order
|
|
// to transform the paths into our viewbox
|
|
NSSize dest = rect.size;
|
|
NSSize source = _viewBox.size;
|
|
_clipScale = MIN(dest.width / _proposedViewSize.width,
|
|
dest.height / _proposedViewSize.height);
|
|
|
|
// work out the actual scale based on the clip scale
|
|
CGFloat w = _proposedViewSize.width * _clipScale;
|
|
CGFloat h = _proposedViewSize.height * _clipScale;
|
|
_scale = MIN(w / source.width, h / source.height);
|
|
}
|
|
|
|
#pragma mark NSPasteboard
|
|
|
|
- (NSArray*)writableTypesForPasteboard:(NSPasteboard*)pasteboard
|
|
{
|
|
return @[ NSPasteboardTypePDF ];
|
|
}
|
|
|
|
- (id)pasteboardPropertyListForType:(NSString*)type
|
|
{
|
|
if ([type isEqualToString:NSPasteboardTypePDF]) {
|
|
return [self PDFData];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
#pragma mark IJSVGParserDelegate
|
|
|
|
- (void)svgParser:(IJSVGParser*)svg
|
|
foundSubSVG:(IJSVG*)subSVG
|
|
withSVGString:(NSString*)string
|
|
{
|
|
if (_delegate != nil && _respondsTo.shouldHandleSubSVG == 1) {
|
|
[_delegate svg:self foundSubSVG:subSVG withSVGString:string];
|
|
}
|
|
}
|
|
|
|
- (BOOL)svgParser:(IJSVGParser*)parser
|
|
shouldHandleForeignObject:(IJSVGForeignObject*)foreignObject
|
|
{
|
|
if (_delegate != nil && _respondsTo.shouldHandleForeignObject == 1) {
|
|
return [_delegate svg:self shouldHandleForeignObject:foreignObject];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)svgParser:(IJSVGParser*)parser
|
|
handleForeignObject:(IJSVGForeignObject*)foreignObject
|
|
document:(NSXMLDocument*)document
|
|
{
|
|
if (_delegate != nil && _respondsTo.handleForeignObject == 1) {
|
|
[_delegate svg:self handleForeignObject:foreignObject document:document];
|
|
}
|
|
}
|
|
|
|
@end
|