Compare commits

...

16 Commits

Author SHA1 Message Date
Curtis Hard 35fc5157b3 fix crash with realloc call being incorrect 2025-01-26 12:44:09 +00:00
Curtis Hard 3742cfd16e implement better sizing 2024-12-15 20:47:30 +00:00
Curtis Hard b47b487a8d Speed up Xcode 16 build times 2024-09-17 23:00:34 +01:00
Curtis Hard 6dc767f88e Remove file 2024-07-21 13:45:39 +01:00
Curtis Hard f8efb68277 Merge branch 'fixes/gradient-stop-parsing' 2024-07-21 13:44:32 +01:00
Curtis Hard 27c4638fa7 Add the inner transforms 2024-07-16 18:48:13 +01:00
Curtis Hard 7c8bfa937e Fixes crash when large whitespace string is used 2024-07-03 17:11:38 +01:00
Curtis Hard fdc30123a4 be sure to copy children 2024-06-27 16:45:04 +01:00
Curtis Hard 80ef3790d2 Update Xcode settings 2024-02-11 19:26:26 +00:00
Curtis Hard ded0c9b839 updated proj 2024-02-06 19:24:47 +00:00
Curtis Hard 6585501f5d Merge branch 'fixes/masking-offsets' 2023-12-20 23:10:05 +00:00
Curtis Hard 846f0fe27c Fix memory leak 2023-11-09 21:59:22 +00:00
Curtis Hard 0c64626e3e Fix for colours using weird methods 2023-10-30 22:47:27 +00:00
Curtis Hard 9ea09ebeee Typo 2023-10-30 20:03:17 +00:00
Curtis Hard 0c04202ad1 Possible masking alpha channel fixes 2023-10-30 20:02:29 +00:00
Curtis Hard e66ee5e5a1 Possible fix for masking offsets 2023-10-30 15:59:11 +00:00
16 changed files with 212 additions and 72 deletions
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 53;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -764,7 +764,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
LastUpgradeCheck = 1600;
ORGANIZATIONNAME = "Curtis Hard";
TargetAttributes = {
594CF46E238FF38E009B251B = {
@@ -926,9 +926,9 @@
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
ENABLE_MODULE_VERIFIER = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -951,6 +951,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "-Wl,-no_warn_duplicate_libraries";
PRODUCT_MODULE_NAME = IJSVG;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
@@ -999,6 +1000,7 @@
ENABLE_MODULE_VERIFIER = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREFIX_HEADER = "";
@@ -1014,6 +1016,7 @@
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "-Wl,-no_warn_duplicate_libraries";
PRODUCT_MODULE_NAME = IJSVG;
SDKROOT = macosx;
VERSIONING_SYSTEM = "apple-generic";
@@ -1043,7 +1046,7 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.iconjar.ijsvg;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
@@ -1073,7 +1076,7 @@
"@executable_path/../Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 2.2.0;
PRODUCT_BUNDLE_IDENTIFIER = com.iconjar.ijsvg;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -294,22 +294,12 @@ CGFloat* IJSVGColorCSSHSLToHSB(CGFloat hue, CGFloat saturation, CGFloat lightnes
NSInteger floatCount = 0;
CGFloat *params = [IJSVGUtils scanFloatsFromCString:method->parameters
size:&floatCount];
// Make sure the floats are not negative
BOOL validParam = true;
for(int i = 0; i < floatCount; i++) {
if(params[i] == -0x0) {
validParam = NO;
break;
}
}
((void)free(params)), params = NULL;
// If we dont, just return black
if(validParam == NO) {
(void)free(params), params = NULL;
if(floatCount < 3) {
return [self computeColorSpace:NSColor.blackColor];
}
// Make sure the floats are not negative
// parse the parameters
NSString* parameters = [NSString stringWithUTF8String:method->parameters];
NSArray* parts = [parameters ijsvg_componentsSeparatedByChars:","];
@@ -21,6 +21,7 @@
#import <Foundation/Foundation.h>
@class IJSVG;
@class IJSVGParser;
@interface IJSVG : NSObject <NSPasteboardWriting> {
@@ -30,6 +31,7 @@
CGRect _viewBox;
CGFloat _backingScale;
IJSVGUnitSize* _intrinsicSize;
IJSVGParser* _parser;
}
// set this to be called when the layer is about to draw, it will call this
+47 -14
View File
@@ -11,6 +11,10 @@
#import <IJSVG/IJSVGTransaction.h>
#import <IJSVG/IJSVGThreadManager.h>
@interface IJSVG (private)
@property (nonatomic, strong) IJSVGParser* parser;
@end
@implementation IJSVG
// these are explicitly implemented
@@ -174,10 +178,10 @@
NSError* anError = nil;
// create the group
IJSVGParser* parser = [IJSVGParser parserForFileURL:aURL
IJSVGParser *parser = [IJSVGParser parserForFileURL:aURL
error:&anError];
_rootNode = parser.rootNode;
self.parser = parser;
[self _setupBasicInfoFromGroup];
[self _setupBasicsFromAnyInitializer];
@@ -199,6 +203,16 @@
error:nil];
}
- (void)setParser:(IJSVGParser*)parser {
_rootNode = [parser rootNodeWithSize:CGSizeZero];
// if the rootNode has any form of relative units, we need to keep hold of
// the parser so when we render, we can ask for new values.
if(_rootNode.viewBoxContainsRelativeUnits) {
_parser = parser;
}
}
- (id)initWithSVGData:(NSData*)data
error:(NSError**)error
{
@@ -224,9 +238,9 @@
// setup the parser
IJSVGParser* parser = [[IJSVGParser alloc] initWithSVGString:string
error:&anError];
_rootNode = parser.rootNode;
error:&anError];
self.parser = parser;
[self _setupBasicInfoFromGroup];
[self _setupBasicsFromAnyInitializer];
@@ -625,7 +639,7 @@
}
}
CGContextSetInterpolationQuality(ctx, quality);
IJSVGRootLayer* rootLayer = self.rootLayer;
IJSVGRootLayer* rootLayer = [self rootLayerWithRect:rect];
[rootLayer renderInContext:ctx
viewPort:rect
backingScale:backingScale
@@ -647,16 +661,35 @@
return _layerTree;
}
- (IJSVGRootLayer*)rootLayerWithRect:(CGRect)rect {
// no parser, which means there is no need to recompute the value
if(_parser == nil || !_rootNode.viewBoxContainsRelativeUnits ||
CGSizeEqualToSize(_rootNode.clientSize, rect.size)) {
return self.rootLayer;
}
// if we do have a parser, that means the node has relative values, lets recompute
__weak IJSVG* weakSelf = self;
[self performBlock:^{
IJSVG* strongSelf = weakSelf;
strongSelf->_rootNode = [strongSelf->_parser rootNodeWithSize:rect.size];
strongSelf->_rootLayer = [strongSelf.layerTree rootLayerForRootNode:strongSelf->_rootNode];
}];
return _rootLayer;
}
- (IJSVGRootLayer*)rootLayer
{
if(_rootLayer == nil) {
__weak IJSVG* weakSelf = self;
[self performBlock:^{
IJSVG* strongSelf = weakSelf;
strongSelf->_rootLayer = [strongSelf.layerTree rootLayerForRootNode:strongSelf->_rootNode];
}];
}
if(_rootLayer != nil) {
return _rootLayer;
}
__weak IJSVG* weakSelf = self;
[self performBlock:^{
IJSVG* strongSelf = weakSelf;
strongSelf->_rootLayer = [strongSelf.layerTree rootLayerForRootNode:strongSelf->_rootNode];
}];
return _rootLayer;
}
- (CGFloat)backingScaleFactor
@@ -396,13 +396,21 @@ intoUserSpaceUnitsFrom:(CALayer<IJSVGDrawableLayer>*)fromLayer
scale:(CGFloat)scale
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGImageRef ref = [self newImageForLayer:layer
options:options
colorSpace:colorSpace
bitmapInfo:kCGImageAlphaNone
scale:scale];
CGImageRef alphaMask = [self newImageForLayer:layer
options:options
colorSpace:colorSpace
bitmapInfo:kCGImageAlphaNone
scale:scale];
// low - high pairs
const CGFloat colors[6] = {
0.f, 11.f,
0.f, 11.f,
0.f, 11.f
};
CGImageRef masked = CGImageCreateWithMaskingColors(alphaMask, colors);
CGImageRelease(alphaMask);
CGColorSpaceRelease(colorSpace);
return ref;
return masked;
}
+ (CGImageRef)newImageWithSize:(CGSize)size
@@ -428,8 +436,10 @@ intoUserSpaceUnitsFrom:(CALayer<IJSVGDrawableLayer>*)fromLayer
bitmapInfo:(uint32_t)bitmapInfo
scale:(CGFloat)scale
{
CALayer<IJSVGDrawableLayer>* referenceLayer = layer.referencingLayer ?: layer;
CGRect frame = layer.outerBoundingBox;
CGRect bounds = layer.innerBoundingBox;
CGRect bounds = CGRectApplyAffineTransform(layer.innerBoundingBox,
[self userSpaceTransformForLayer:referenceLayer]);
CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
ceilf(frame.size.width*scale),
ceilf(frame.size.height*scale),
@@ -30,6 +30,7 @@
[storage setBit:IJSVGNodeAttributeClipPath];
[storage setBit:IJSVGNodeAttributeMask];
[storage setBit:IJSVGNodeAttributeOpacity];
[storage setBit:IJSVGNodeAttributeBlendMode];
return storage;
}
@@ -12,6 +12,8 @@
@interface IJSVGRootNode : IJSVGGroup
@property (nonatomic, assign) CGSize clientSize;
@property (nonatomic, assign) BOOL viewBoxContainsRelativeUnits;
@property (nonatomic, assign) IJSVGIntrinsicDimensions intrinsicDimensions;
@property (nonatomic, strong) IJSVGUnitSize* intrinsicSize;
@property (nonatomic, readonly) CGRect bounds;
@@ -46,7 +46,7 @@
- (IJSVGRootNode *)rootNode
{
IJSVGRootNode* rootNode = nil;
if((rootNode = [super rootNode]) == self) {
if((rootNode = super.rootNode) == self) {
IJSVGNode* parent = self.parentNode;
if([parent isKindOfClass:IJSVGRootNode.class]) {
return (IJSVGRootNode*)parent;
@@ -153,6 +153,16 @@ CGFloat* _Nullable IJSVGParsePathDataStreamSequence(const char* commandChars, NS
// null value of the end of the string instead of nulling out
// with memset \0 - huzzah!
dataStream->charBuffer[bufferCount] = '\0';
// lets check to make the buffer actually has a valid float, and
// not either just a sign or white space or some random char.
if(strlen(dataStream->charBuffer) == 1 && !VALID_DIGIT(dataStream->charBuffer[0])) {
isDecimal = false;
bufferCount = 0;
continue;
}
// actually add the float into the buffer
dataStream->floatBuffer[counter++] = IJSVGParseFloat(dataStream->charBuffer);
// reset
@@ -166,7 +176,7 @@ CGFloat* _Nullable IJSVGParsePathDataStreamSequence(const char* commandChars, NS
if(commandsFound != NULL) {
*commandsFound = (NSInteger)round(counter / commandLength);
}
// allocate the new buffer from memory
CGFloat* floats = (CGFloat*)malloc(sizeof(CGFloat) * counter);
memcpy(floats, dataStream->floatBuffer, counter * sizeof(CGFloat));
@@ -127,9 +127,11 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers);
IJSVGStyleSheet* _styleSheet;
NSMutableDictionary<NSString*, NSXMLElement*>* _detachedReferences;
IJSVGThreadManager* _threadManager;
CGSize _rootSize;
IJSVGRootNode* _rootNode;
}
@property (nonatomic, strong, readonly) IJSVGRootNode* rootNode;
@property (nonatomic, assign) CGSize defaultSize;
+ (BOOL)isDataSVG:(NSData*)data;
@@ -142,4 +144,6 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers);
+ (IJSVGParser*)parserForFileURL:(NSURL*)aURL
error:(NSError**)error;
- (IJSVGRootNode*)rootNodeWithSize:(CGSize)size;
@end
@@ -121,6 +121,8 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
error:(NSError**)error
{
if((self = [super init]) != nil) {
// just some generic value to get it up n running.
_defaultSize = CGSizeMake(200.f, 200.f);
// use NSXMLDocument as its the easiest thing to do on OSX
NSError* anError = nil;
@@ -138,19 +140,12 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
error:error];
}
// attempt to parse the file
[self begin];
// check the actual parsed SVG
anError = nil;
if([self _validateParse:&anError] == NO) {
*error = anError;
return nil;
}
// we have actually finished with the document at this point
// so just get rid of it
_document = nil;
}
return self;
}
@@ -211,7 +206,36 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
return YES;
}
- (void)begin
- (IJSVGRootNode*)rootNodeWithSize:(CGSize)size
{
__weak IJSVGParser* weakSelf = self;
[self beginWithSetup:^{
// if we have passed in a value that is not zero, we can just set it to that
// else we need to compute it, as we can treat zero as auto.
IJSVGParser* strongSelf = weakSelf;
CGSize computeSize = size;
if(!CGSizeEqualToSize(CGSizeZero, computeSize)) {
strongSelf->_rootSize = size;
return;
}
// compute the value, if the value is still a nil size, we just need to
// fallback to some generic value, which is against this object.
IJSVGRootNode* node = [self rootNode:NO];
if(node.viewBox == nil) {
strongSelf->_rootSize = strongSelf->_defaultSize;
return;
}
// at this point we can just compute it again from the viewBox size.
computeSize = [node.viewBox.size computeValue:CGSizeZero];
strongSelf->_rootSize = CGSizeEqualToSize(CGSizeZero, computeSize) ?
strongSelf->_defaultSize : computeSize;
}];
return _rootNode;
}
- (void)beginWithSetup:(dispatch_block_t __nullable)setup
{
// setup basics to begin with
_styleSheet = [[IJSVGStyleSheet alloc] init];
@@ -219,12 +243,17 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
_threadManager = manager;
_commandDataStream = manager.pathDataStream;
_detachedReferences = [[NSMutableDictionary alloc] init];
if(setup != nil) {
setup();
}
_rootNode = [[IJSVGRootNode alloc] init];
_rootNode.clientSize = _rootSize;
IJSVGNodeParserPostProcessBlock postProcessBlock = nil;
[self parseSVGElement:_document.rootElement
ontoNode:_rootNode
parentNode:nil
postProcessBlock:&postProcessBlock];
postProcessBlock:&postProcessBlock
recursive:YES];
if(postProcessBlock != nil) {
postProcessBlock();
}
@@ -232,6 +261,23 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
_detachedReferences = nil;
}
- (IJSVGRootNode*)rootNode:(BOOL)recursive
{
IJSVGNodeParserPostProcessBlock postProcessBlock = nil;
IJSVGRootNode* node = [[IJSVGRootNode alloc] init];
node.clientSize = _rootSize;
[self parseSVGElement:_document.rootElement
ontoNode:node
parentNode:nil
postProcessBlock:&postProcessBlock
recursive:NO];
if(postProcessBlock != nil) {
postProcessBlock();
}
[node postProcess];
return node;
}
- (void)computeDefsForElement:(NSXMLElement*)element
parentNode:(IJSVGNode*)parentNode
{
@@ -250,22 +296,26 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
- (void)computeViewBoxForRootNode:(IJSVGRootNode*)node
{
if(node.viewBox == nil) {
CGFloat width = node.width.value;
CGFloat height = node.height.value;
IJSVGUnitLength* width = node.width;
IJSVGUnitLength* height = node.height;
CGFloat cw = [width computeValue:_rootSize.width];
CGFloat ch = [height computeValue:_rootSize.height];
if(height == 0.f && width != 0.f) {
height = width;
} else if(width == 0.f && height != 0.f) {
width = height;
if(ch == 0.f && cw != 0.f) {
ch = cw;
} else if(cw == 0.f && ch != 0.f) {
cw = ch;
}
// nothing we can do, its a nil viewBox and has
// no width or height
if(width == 0.f && height == 0.f) {
if(cw == 0.f && ch == 0.f) {
return;
}
node.viewBox = [IJSVGUnitRect rectWithX:0.f y:0.f
width:width
height:height];
IJSVGUnitSize* size = [IJSVGUnitSize sizeWithWidth:width
height:height];
node.viewBox = [IJSVGUnitRect rectWithOrigin:IJSVGUnitPoint.zeroPoint
size:size];
}
IJSVGIntrinsicDimensions dimensions = IJSVGIntrinsicDimensionNone;
@@ -279,11 +329,25 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
dimensions |= IJSVGIntrinsicDimensionHeight;
hl = node.height;
}
// make note if we are using relative units for the width or height.
if(wl.type == IJSVGUnitLengthTypePercentage ||
hl.type == IJSVGUnitLengthTypePercentage) {
node.viewBoxContainsRelativeUnits = YES;
}
// store the width and height
node.intrinsicDimensions = dimensions;
node.intrinsicSize = [IJSVGUnitSize sizeWithWidth:wl
height:hl];
// compute the new width and height based on the passed in size as the fall
// back for all the percentage values.
CGSize computedSize = CGSizeMake([wl computeValue:_rootSize.width],
[hl computeValue:_rootSize.height]);
node.intrinsicSize = [IJSVGUnitSize sizeWithCGSize:computedSize];
// compute the viewbox
CGRect computedViewBox = [node.viewBox computeValue:_rootSize];
node.viewBox.size = [IJSVGUnitSize sizeWithCGSize:computedViewBox.size];
}
- (IJSVGNodeParserPostProcessBlock)computeAttributesFromElement:(NSXMLElement*)element
@@ -1004,6 +1068,21 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
attribute = attribute.copy;
[copy addAttribute:attribute];
}
// if we merge an element, we need to also maintain its children, if the
// reference element has children and the referencing element does not,
// use those else use the referencing element children.
if (element.childCount != 0) {
// remove any old children
for(__strong NSXMLElement* child in copy.children) {
[copy removeChildAtIndex:child.index];
}
// add the new ones from the copy
for(__strong NSXMLElement* child in element.children) {
[copy addChild:child.copy];
}
}
return copy;
}
@@ -1385,6 +1464,7 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
ontoNode:(IJSVGRootNode*)node
parentNode:(IJSVGNode*)parentNode
postProcessBlock:(IJSVGNodeParserPostProcessBlock*)postProcessBlock
recursive:(BOOL)recursive
{
node.type = IJSVGNodeTypeSVG;
node.name = element.localName;
@@ -1414,8 +1494,10 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
[self computeViewBoxForRootNode:node];
// recursively compute children
[self computeElement:element
parentNode:node];
if(recursive == YES) {
[self computeElement:element
parentNode:node];
}
}
- (IJSVGNode*)parseSVGElement:(NSXMLElement*)element
@@ -1426,7 +1508,8 @@ void IJSVGParserMallocBuffersFree(IJSVGParserMallocBuffers* buffers)
[self parseSVGElement:element
ontoNode:node
parentNode:parentNode
postProcessBlock:postProcessBlock];
postProcessBlock:postProcessBlock
recursive:YES];
return node;
}
@@ -606,10 +606,12 @@
// we need to move all the layers back if they are into the userSpace
// coordinate system
for(CALayer<IJSVGDrawableLayer> *childLayer in maskLayer.sublayers) {
childLayer.frame = CGRectApplyAffineTransform(childLayer.frame,
userSpaceTransform);
CGRect innerBoundingBox = childLayer.innerBoundingBox;
CGAffineTransform innerTransform = CGAffineTransformMakeTranslation(-innerBoundingBox.origin.x,
-innerBoundingBox.origin.y);
childLayer.frame = CGRectApplyAffineTransform(childLayer.frame, userSpaceTransform);
childLayer.frame = CGRectApplyAffineTransform(childLayer.frame, innerTransform);
}
}
if(maskNode.units == IJSVGUnitUserSpaceOnUse) {
@@ -94,7 +94,7 @@ IJSVGParsingStringMethod** IJSVGParsingMethodParseString(const char* string,
// now we can add
if(methodCount + 1 > currentBufferSize) {
currentBufferSize += defBufferSize;
*methods = *(IJSVGParsingStringMethod**)realloc(methods, sizeof(IJSVGParsingStringMethod*)*currentBufferSize);
methods = (IJSVGParsingStringMethod**)realloc(methods, sizeof(IJSVGParsingStringMethod*)*currentBufferSize);
}
methods[methodCount++] = method;
method = NULL;
@@ -61,7 +61,7 @@ char* IJSVGTimmedCharBufferCreate(const char* buffer)
while(length-1 > 0 && isspace(buffer[length-1])) {
length--;
}
while(isspace(buffer[start])) {
while(isspace(buffer[start]) && start < length) {
start++;
}
char* chars = (char*)malloc(sizeof(char)*((length-start)+1) ?: sizeof(char));