Files
IJSVG/source/IJSVGExporterPathInstruction.m
T
2017-09-07 13:17:48 +01:00

375 lines
13 KiB
Objective-C

//
// IJSVGExporterPathInstruction.m
// IconJar
//
// Created by Curtis Hard on 08/01/2017.
// Copyright © 2017 Curtis Hard. All rights reserved.
//
#import "IJSVGExporterPathInstruction.h"
#import "IJSVGExporter.h"
@implementation IJSVGExporterPathInstruction
- (void)dealloc
{
if(_data != NULL) {
free(_data);
}
[super dealloc];
}
- (id)initWithInstruction:(char)instruction
dataCount:(NSInteger)floatCount
{
if((self = [super init]) != nil) {
_instruction = instruction;
// only allocate if not zero
if(floatCount != 0) {
_data = (CGFloat *)calloc(sizeof(CGFloat), floatCount);
}
}
return self;
}
- (NSInteger)dataLength
{
return _dataCount;
}
- (void)setInstruction:(char)newInstruction
{
_instruction = newInstruction;
}
- (char)instruction
{
return _instruction;
}
- (CGFloat *)data
{
return _data;
}
+ (NSString *)pathStringFromInstructions:(NSArray<IJSVGExporterPathInstruction *> *)instructions
{
NSMutableArray * pathData = [[[NSMutableArray alloc] init] autorelease];
for(IJSVGExporterPathInstruction * instruction in instructions) {
CGFloat * data = instruction.data;
NSString * str = nil;
switch(instruction.instruction) {
// move
case 'M':
case 'm': {
char * buffer;
asprintf(&buffer, "%c%g,%g", instruction.instruction, data[0], data[1]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// vertical and horizonal line
case 'V':
case 'v':
case 'H':
case 'h': {
char * buffer;
asprintf(&buffer, "%c%g", instruction.instruction, data[0]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// line
case 'L':
case 'l': {
char * buffer;
asprintf(&buffer, "%c%g,%g",instruction.instruction, data[0], data[1]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// curve
case 'C':
case 'c': {
char * buffer;
asprintf(&buffer, "%c%g,%g %g,%g %g,%g", instruction.instruction,
data[0], data[1], data[2], data[3], data[4], data[5]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// quadratic curve
case 'Q':
case 'q': {
char * buffer;
asprintf(&buffer, "%c%g,%g %g,%g", instruction.instruction,
data[0], data[1], data[2], data[3]);
str = [NSString stringWithCString:buffer
encoding:NSUTF8StringEncoding];
free(buffer);
[pathData addObject:str];
break;
}
// close path
case 'Z':
case 'z': {
str = [NSString stringWithFormat:@"%c",instruction.instruction];
[pathData addObject:str];
}
}
}
return [pathData componentsJoinedByString:@""];
}
+ (void)convertInstructionsToRelativeCoordinates:(NSArray<IJSVGExporterPathInstruction *> *)instructions
{
CGFloat point[2] = {0,0};
CGFloat subpathPoint[2] = {0,0};
NSInteger index = 0;
for(IJSVGExporterPathInstruction * anInstruction in instructions) {
char instruction = anInstruction.instruction;
CGFloat * data = anInstruction.data;
NSInteger length = anInstruction.dataLength;
if(data != NULL) {
// already relative
if(instruction == 'm' || instruction == 'c' || instruction == 's' ||
instruction == 'l' || instruction == 'q' || instruction == 't' ||
instruction == 'a') {
point[0] += data[length-2];
point[1] += data[length-1];
if(instruction == 'm') {
subpathPoint[0] = point[0];
subpathPoint[1] = point[1];
}
} else if(instruction == 'h') {
point[0] += data[0];
} else if(instruction == 'v') {
point[1] += data[0];
}
// convert absolute to relative
if(instruction == 'M') {
if(index > 0) {
instruction = 'm';
}
data[0] -= point[0];
data[1] -= point[1];
subpathPoint[0] = point[0] += data[0];
subpathPoint[1] = point[1] += data[1];
} else if(instruction == 'L' || instruction == 'T') {
instruction = tolower(instruction);
data[0] -= point[0];
data[1] -= point[1];
point[0] += data[0];
point[1] += data[1];
} else if(instruction == 'C') {
instruction = 'c';
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
data[4] -= point[0];
data[5] -= point[1];
point[0] += data[4];
point[1] += data[5];
} else if(instruction == 'S' || instruction == 'Q') {
instruction = tolower(instruction);
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
point[0] += data[2];
point[1] += data[3];
} else if(instruction == 'A') {
instruction = 'a';
data[5] -= point[0];
data[6] -= point[1];
point[0] += data[5];
point[1] += data[6];
} else if(instruction == 'H') {
instruction = 'h';
data[0] -= point[0];
point[0] += data[0];
} else if(instruction == 'V') {
instruction = 'v';
data[0] -= point[1];
point[1] += data[0];
}
// reset the instruction
[anInstruction setInstruction:instruction];
} else if(instruction == 'Z' || instruction == 'z') {
point[0] = subpathPoint[0];
point[1] = subpathPoint[1];
}
// increment index
index++;
}
}
+ (NSArray<IJSVGExporterPathInstruction *> *)instructionsFromPath:(CGPathRef)path
{
// keep track of the current point
__block CGPoint currentPoint = CGPointZero;
NSMutableArray * instructions = [[[NSMutableArray alloc] init] autorelease];
// create the path callback
IJSVGCGPathHandler callback = ^(const CGPathElement * pathElement) {
IJSVGExporterPathInstruction * instruction = nil;
// work out what to do
switch(pathElement->type) {
case kCGPathElementMoveToPoint: {
// move to command
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'M'
dataCount:2] autorelease];
CGPoint point = pathElement->points[0];
instruction.data[0] = point.x;
instruction.data[1] = point.y;
currentPoint = point;
[instructions addObject:instruction];
break;
}
case kCGPathElementAddLineToPoint: {
// line to command
CGPoint point = pathElement->points[0];
if(point.x == currentPoint.x) {
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'V'
dataCount:1] autorelease];
instruction.data[0] = point.y;
} else if(point.y == currentPoint.y) {
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'H'
dataCount:1] autorelease];
instruction.data[0] = point.x;
} else {
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'L'
dataCount:2] autorelease];
instruction.data[0] = point.x;
instruction.data[1] = point.y;
}
currentPoint = point;
[instructions addObject:instruction];
break;
}
case kCGPathElementAddQuadCurveToPoint: {
// quad curve to command
CGPoint controlPoint = pathElement->points[0];
CGPoint point = pathElement->points[1];
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'Q'
dataCount:4] autorelease];
instruction.data[0] = controlPoint.x;
instruction.data[1] = controlPoint.y;
instruction.data[2] = point.x;
instruction.data[3] = point.y;
currentPoint = point;
[instructions addObject:instruction];
break;
}
case kCGPathElementAddCurveToPoint: {
// curve to command
CGPoint controlPoint1 = pathElement->points[0];
CGPoint controlPoint2 = pathElement->points[1];
CGPoint point = pathElement->points[2];
currentPoint = point;
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'C'
dataCount:6] autorelease];
instruction.data[0] = controlPoint1.x;
instruction.data[1] = controlPoint1.y;
instruction.data[2] = controlPoint2.x;
instruction.data[3] = controlPoint2.y;
instruction.data[4] = point.x;
instruction.data[5] = point.y;
[instructions addObject:instruction];
break;
}
case kCGPathElementCloseSubpath: {
// close command
instruction = [[[IJSVGExporterPathInstruction alloc] initWithInstruction:'Z'
dataCount:0] autorelease];
[instructions addObject:instruction];
break;
}
}
};
// apply the
CGPathApply(path, (__bridge void*)callback, IJSVGExporterPathCaller);
// remove last instruction if it was Z -> M
IJSVGExporterPathInstruction * lastInstruction = instructions.lastObject;
if(lastInstruction.instruction == 'M' ||
lastInstruction.instruction == 'm') {
if(instructions.count >= 2) {
NSInteger index = [instructions indexOfObject:lastInstruction]-1;
IJSVGExporterPathInstruction * prevInstruction = instructions[index];
if(prevInstruction.instruction == 'z' ||
prevInstruction.instruction == 'Z') {
[instructions removeLastObject];
}
}
}
return instructions;
}
@end