开发者

Drawing a text along a path in QuartzCore

Say I have an array of points that form a line and a text. How can I go about drawing the t开发者_如何学Cext along this line in

 - (void)drawRect:(CGRect)rect 

of a UIView?

I am able to draw the path without a problem. Is there a standard method that I overlooked or a framework that would allow me to draw the text along that path? Ideally I would like to do this using only QuartzCore/CoreGraphics.

I tried calculating the width of each character and rotating every character. This kind of works, but I was wondering if there is a more elegant method.


I believe you can do this in Mac OS X, but the closest you'll come on the iPhone is CGContextShowGlyphsWithAdvances and this wont even rotate.

It shouldn't be too hard to use a loop and draw each character using something like the following. This is adapted from Apple's documentation and not tested, so beware:

CGContextSelectFont(myContext, "Helvetica", 12, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(myContext, 10);
CGContextSetTextDrawingMode(myContext, kCGTextFillStroke);
CGContextSetRGBFillColor(myContext, 0, 0, 0, 1);
CGContextSetRGBStrokeColor(myContext, 0, 0, 0, 1);

NSUInteger charIndex = 0;
for(NSString *myChar in arrayOfChars) {
    char *cString = [myChar UTF8String];
    CGPoint charOrigin = originForPositionAlongPath(charIndex, myPath);
    CGFloat slope = slopeForPositionAlongPath(charIndex, myPath);

    CGContextSetTextMatrix(myContext, CGAffineTransformMakeRotation(slope));
    CGContextShowTextAtPoint(myContext, charOrigin.x, charOrigin.y, cString, 1);
}

Edit: Here's an idea of the PositionAlongPath functions. Again, they aren't tested, but should be close. originAlong... returns (-1, -1) if you run out of path.

CGPoint originForPositionAlongPath(int index, NSArray *path) {
    CGFloat charWidth = 12.0;
    CGFloat widthToGo = charWidth * index;

    NSInteger i = 0;
    CGPoint position = [path objectAtIndex:i];

    while(widthToGo >= 0) {
            //out of path, return invalid point
        if(i >= [path count]) {
            return CGPointMake(-1, -1);
        }

        CGPoint next = [path objectAtIndex:i+1];

        CGFloat xDiff = next.x - position.x;
        CGFloat yDiff = next.y - position.y;
        CGFloat distToNext = sqrt(xDiff*xDiff + yDiff*yDiff);

        CGFloat fracToGo = widthToGo/distToNext
            //point is before next point in path, interpolate the answer
        if(fracToGo < 1) {
            return CGPointMake(position.x+(xDiff*fracToGo), position.y+(yDiff*fracToGo));
        }

            //advance to next point on the path
        widthToGo -= distToNext;
        position = next;
        ++i;
    }
}


CGFloat slopeForPositionAlongPath(int index, NSArray *path) {
    CGPoint begin = originForPositionAlongPath(index, path);
    CGPoint end = originForPositionAlongPath(index+1, path);

    CGFloat xDiff = end.x - begin.x;
    CGFloat yDiff = end.y - begin.y;

    return atan(yDiff/xDiff);
}


This is probably irrelevant but you can text along a path with SVG (http://www.w3.org/TR/SVG/text.html#TextOnAPath), and the iPhoneOS supports it.


Above example unfortunately didn't work as expected. I have now finally found the correct way to paint a text along the path.

Here we go:

You cannot take this code 1:1 as its just excerpted from my application, but i will make things clear with some comments.

// MODIFIED ORIGIN FUNCTION

CGPoint originForPositionAlongPath(float *l, float nextW, int index, NSArray *path) {
CGFloat widthToGo = *l + nextW;


NSInteger i = 0;
CGPoint position = [[path objectAtIndex:i] CGPointValue];

while(widthToGo >= 0) {
    //out of path, return invalid point
    if(i+1 >= [path count]) {
        return CGPointMake(-1, -1);
    }

    CGPoint next = [[path objectAtIndex:i+1] CGPointValue];

    CGFloat xDiff = next.x - position.x;
    CGFloat yDiff = next.y - position.y;
    CGFloat distToNext = sqrt(xDiff*xDiff + yDiff*yDiff);

    CGFloat fracToGo = widthToGo/distToNext;
    //point is before next point in path, interpolate the answer
    if(fracToGo < 1) {
        return CGPointMake(position.x+(xDiff*fracToGo), position.y+(yDiff*fracToGo));
    }

    //advance to next point on the path
    widthToGo -= distToNext;
    position = next;
    ++i;
}
}

// MODIFIED SLOPE FUNCTION

CGFloat slopeForPositionAlongPath(float* l, float nextW, int index, NSArray *path) {
CGPoint begin = originForPositionAlongPath(l, 0, index, path);
CGPoint end = originForPositionAlongPath(l, nextW, index+1, path);

CGFloat xDiff = end.x - begin.x;
CGFloat yDiff = end.y - begin.y;

return atan(yDiff/xDiff);
}


// IMPORTANT: CHARACTER WIDTHS FOR HELVETICA, ABOVE EXAMPLE USES FIXED WIDTHS WHICH IS NOT ACCURATE

float arraychars[] = {
0,     0,     0,     0,     0,     0,     0,     0,    
0,     0,     0,     0,     0,     0,     0,     0,    
0,     0,     0,     0,     0,     0,     0,     0,    
0,     0,     0,     0,     0,     0,     0,     0,    
0.278, 0.333, 0.474, 0.556, 0.556, 0.889, 0.722, 0.278,
0.333, 0.333, 0.389, 0.584, 0.278, 0.584, 0.278, 0.278,
0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.556,
0.556, 0.556, 0.333, 0.333, 0.584, 0.584, 0.584, 0.611,
0.975, 0.722, 0.722, 0.722, 0.722, 0.667, 0.611, 0.778,
0.722, 0.278, 0.556, 0.722, 0.611, 0.833, 0.722, 0.778,
0.667, 0.778, 0.722, 0.667, 0.611, 0.722, 0.667, 0.944,
0.667, 0.667, 0.611, 0.333, 0.278, 0.333, 0.584, 0.556,
0.278, 0.556, 0.611, 0.556, 0.611, 0.556, 0.333, 0.611,
0.611, 0.278, 0.278, 0.556, 0.278, 0.889, 0.611, 0.611,
0.611, 0.611, 0.389, 0.556, 0.333, 0.611, 0.556, 0.778,
0.556, 0.556, 0.5,   0.389, 0.28,  0.389, 0.584, 0,    
0,     0,     0,     0,     0,     0,     0,     0,    
0,     0,     0,     0,     0,     0,     0,     0,    
0.278, 0.333, 0.333, 0.333, 0.333, 0.333, 0.333, 0.333,
0.333, 0,     0.333, 0.333, 0,     0.333, 0.333, 0.333,
0.278, 0.333, 0.556, 0.556, 0.556, 0.556, 0.28,  0.556,
0.333, 0.737, 0.37,  0.556, 0.584, 0.333, 0.737, 0.333,
0.4,   0.584, 0.333, 0.333, 0.333, 0.611, 0.556, 0.278,
0.333, 0.333, 0.365, 0.556, 0.834, 0.834, 0.834, 0.611,
0.722, 0.722, 0.722, 0.722, 0.722, 0.722, 1,     0.722,
0.667, 0.667, 0.667, 0.667, 0.278, 0.278, 0.278, 0.278,
0.722, 0.722, 0.778, 0.778, 0.778, 0.778, 0.778, 0.584,
0.778, 0.722, 0.722, 0.722, 0.722, 0.667, 0.667, 0.611,
0.556, 0.556, 0.556, 0.556, 0.556, 0.556, 0.889, 0.556,
0.556, 0.556, 0.556, 0.556, 0.278, 0.278, 0.278, 0.278,
0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.611, 0.584,
0.611, 0.611, 0.611, 0.611, 0.611, 0.556, 0.611, 0.556,
                 };    

void o_DrawContourLabel(void *myObjectInstance, TransBuffer* transBuffer,     MapPainterIphone* mp,const Projection& projection,
                    const MapParameter& parameter,
                    const LabelStyle& style,
                    const std::string& text,
                    size_t transStart, size_t transEnd){
// HERE WE Initialize the font etc.

CGContextSelectFont(context, "Helvetica-Bold", 10, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(context, 0);
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
CGContextSetLineWidth(context, 3.0);
CGContextSetRGBFillColor(context, style.GetTextR(), style.GetTextG(), style.GetTextB(), style.GetTextA());
CGContextSetRGBStrokeColor(context, 1, 1, 1, 1);

// Here we prepare a NSArray holding all waypoints of our path.
// I fill it from a "transBuffer" but you may fill it with whatever you want

NSMutableArray* path = [[NSMutableArray alloc] init];
if (transBuffer->buffer[transStart].x<transBuffer->buffer[transEnd].x) {
    for (size_t j=transStart; j<=transEnd; j++) {
        if (j==transStart) {
            [path addObject:[NSValue valueWithCGPoint:CGPointMake(transBuffer->buffer[j].x,transBuffer->buffer[j].y)]];
        }
        else {
            [path addObject:[NSValue valueWithCGPoint:CGPointMake(transBuffer->buffer[j].x,transBuffer->buffer[j].y)]];
        }
    }
}
else {
    for (size_t j=0; j<=transEnd-transStart; j++) {
        size_t idx=transEnd-j;

        if (j==0) {
            [path addObject:[NSValue valueWithCGPoint:CGPointMake(transBuffer->buffer[idx].x,transBuffer->buffer[idx].y)]];

        }
        else {
            [path addObject:[NSValue valueWithCGPoint:CGPointMake(transBuffer->buffer[idx].x,transBuffer->buffer[idx].y)]];

        }
    }
}

// if path is too short for "estimated text length" then exit

if (pathLength(path)<text.length()*7) {
    // Text is longer than path to draw on
    return;
}

// NOW PAINT CHAR FOR CHAR USING CHARACTER WIDTHS TABLE

float lenUpToNow = 0;

CGAffineTransform transform=CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
NSUInteger charIndex = 0;
for(int i=0;i<text.length();i++) {
    char *cString = (char*)malloc(2*sizeof(char));
    cString[0] = text.at(i);
    cString[1]=0;

    float nww = arraychars[cString[0]]*8*1.4;

    CGPoint charOrigin = originForPositionAlongPath(&lenUpToNow, 0, charIndex, path);
    CGFloat slope = slopeForPositionAlongPath(&lenUpToNow, nww, charIndex, path);
    std::cout << " CHARACTER " << cString << " placed at " << charOrigin.x << "," << charOrigin.y << std::endl;

    // DO NOT FORGET TO DO THIS (TWO TRANSFORMATIONS) .. one for the rotation
    // and one for mirroring, otherwise the text will be mirrored due to a
    // crappy coordinate system in QuartzCore.

    CGAffineTransform ct = CGAffineTransformConcat(transform,CGAffineTransformMakeRotation(slope));
    CGContextSetTextMatrix(context, ct);
    CGContextSetTextDrawingMode(context, kCGTextStroke);
    CGContextShowTextAtPoint(context, charOrigin.x, charOrigin.y, cString, 1);

    CGContextSetTextDrawingMode(context, kCGTextFill);
    CGContextShowTextAtPoint(context, charOrigin.x, charOrigin.y, cString, 1);
    std::cout << " ... len (" << (int)cString[0] << ") = " << arraychars[cString[0]] << " up to now: " << lenUpToNow << std::endl;
    lenUpToNow += nww;

    charIndex++;
    free(cString);
}


}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜