Search code examples
iosobjective-ccgpath

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.


Solution

  • 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.