jontelang
jontelang

Reputation: 609

Split a CGPathRef into multiple paths

I am trying to divide text into all the different parts it has though CGPathRefs. For example, L has one path and ? has two (the dot and the rest).

Currently, I am able to get each letter as a path however in the example of "?" the path contains both the dot and the rest in one. I want to divide them up but I can't seem to get it to work.

To divide text into paths I used some answer from stack overflow, it works well and here is the part where I got each letter as a path. Here I also run it through the "CGPathApply" function to look at each individual point

CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
NSMutableArray *pathElements = [NSMutableArray array];
CGPathApply(letter, (__bridge void * _Nullable)(pathElements), getPointsFromBezier);

I then look at each point in the getPointsFromBezier like this:

void getPointsFromBezier(void *info, const CGPathElement *element){
    CGPoint *p = element->points;
    NSString *name;
    switch (element->type) {
        case kCGPathElementMoveToPoint: name = @"kCGPathElementMoveToPoint"; break;
        case kCGPathElementAddLineToPoint: name = @"kCGPathElementAddLineToPoint"; break;
        case kCGPathElementAddQuadCurveToPoint: name = @"kCGPathElementAddQuadCurveToPoint"; break;
        case kCGPathElementAddCurveToPoint: name = @"kCGPathElementAddCurveToPoint"; break;
        case kCGPathElementCloseSubpath: name = @"kCGPathElementCloseSubpath"; break;
        default: name = @"default"; break;
    }
    NSLog(@"Type: %@ || Point: %@", name, NSStringFromCGPoint(*p));
}

This gives me output like this:

Type: kCGPathElementMoveToPoint || Point: {11.75, 0}
Type: kCGPathElementAddLineToPoint || Point: {11.75, 9.729}
Type: kCGPathElementAddQuadCurveToPoint || Point: {11.75, 11.26}
..
..
Type: kCGPathElementAddQuadCurveToPoint || Point: {13.00, 10.52}
Type: kCGPathElementAddLineToPoint || Point: {13.00, 0}
Type: kCGPathElementCloseSubpath || Point: {13.00, 0}
Type: kCGPathElementMoveToPoint || Point: {12.75, 18.75}
Type: kCGPathElementAddQuadCurveToPoint || Point: {11.5, 18.94}
Type: kCGPathElementAddLineToPoint || Point: {11.5, 19.95}
Type: kCGPathElementAddQuadCurveToPoint || Point: {11.5, 20.41}
..
..
Type: kCGPathElementAddQuadCurveToPoint || Point: {12.75, 18.169}
Type: kCGPathElementCloseSubpath || Point: {12.75, 18.169}

The character I inputted here was ก็ and we can clearly see that it has the two different paths in the output (i.e. two kCGPathElementMoveToPoint and two kCGPathElementCloseSubpath).

I am not sure how I can properly or nicely create two separate CGPathRefs from this.

Upvotes: 1

Views: 844

Answers (1)

jontelang
jontelang

Reputation: 609

Actually, while writing the question I almost solved it.

I create a static mutable CGPath, then I add points as I go in the function called through CGPathApply. Like this:

static CGMutablePathRef path;

void getPointsFromBezier(void *info, const CGPathElement *element){
    CGPoint *p = element->points;

    if ( element->type == kCGPathElementMoveToPoint ){
        path = CGPathCreateMutable();
        CGPathMoveToPoint(path, nil, p->x, p->y);
    }
    else if ( element->type == kCGPathElementAddLineToPoint ){
        CGPathAddLineToPoint(path, nil, p->x, p->y);
    }
    else if ( element->type == kCGPathElementAddQuadCurveToPoint ){
        CGPathAddQuadCurveToPoint(path, nil, p->x, p->y, p->x, p->y);
    }
    else if ( element->type == kCGPathElementAddCurveToPoint ){
        // ...
    }
    else if ( element->type == kCGPathElementCloseSubpath ){
        CGPathCloseSubpath(path);
        [((__bridge NSMutableArray*)info) addObject:(__bridge id _Nonnull)(path)];
        path = nil;
        CGPathRelease(path);
    }
}

Then I simply loop through each path in the original method and draw them instead of the original "letter" CGPath.

CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
NSMutableArray *pathElements = [NSMutableArray array];
CGPathApply(letter, (__bridge void * _Nullable)(pathElements), getPointsFromBezier);

for (id e in pathElements) {
    CGContextSaveGState(ctx);
    CGContextTranslateCTM(ctx,position.x,0 - 40 + CGRectGetMidY(rect) - self.font.descender + position.y);
    CGContextScaleCTM(ctx, 1, -1);
    CGContextAddPath(ctx, (CGPathRef)e);
    [[self r] setFill];
    CGContextFillPath(ctx);
    CGContextRestoreGState(ctx);
}

CGPathRelease(letter);

This works kind of. One issue is that I don't get the proper curves, so the resulting paths are not as smooth as they should be. I will update if I solve it.

This is the result so far and you can see that the image is a bit blocky compared to the bottom original one. However, they have two different colors which was the initial problem to solve.

enter image description here

UPDATE: I found that each point (element->points) contains the remaining data (x and y's) to properly draw the points. For example:

if ( element->type == kCGPathElementAddQuadCurveToPoint ){
    CGPoint *p0 = &points[0]; // Control points for bezier path
    CGPoint *p1 = &points[1]; // Actual points
    CGPathAddQuadCurveToPoint(path,
                              nil,
                              p0->x,
                              p0->y,
                              p1->x,
                              p1->y);
}

This will draw each glyph properly.

Upvotes: 2

Related Questions