在QuartzCore中沿路径绘制文本

时间:2010-01-14 19:57:50

标签: objective-c cocoa-touch uiview core-graphics quartz-graphics

假设我有一系列形成一条线和一条文字的点。如何在

中沿着这一行绘制文本
 - (void)drawRect:(CGRect)rect 
一个UIView的?

我能够毫无问题地绘制路径。是否存在我忽略的标准方法或允许我沿着该路径绘制文本的框架?理想情况下,我只想使用QuartzCore / CoreGraphics。

我尝试计算每个角色的宽度并旋转每个角色。这种作品,但我想知道是否有更优雅的方法。

3 个答案:

答案 0 :(得分:11)

我相信你可以在Mac OS X中做到这一点,但你最接近iPhone的是CGContextShowGlyphsWithAdvances,这甚至不会旋转。

使用循环并使用以下内容绘制每个字符应该不会太难。这是根据Apple的文档改编而来的,未经过测试,因此请注意:

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);
}

编辑:以下是PositionAlongPath函数的概念。同样,他们没有经过测试,但应该接近。 originAlong ...如果路径用完,则返回(-1,-1)。

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);
}

答案 1 :(得分:1)

这可能不相关,但您可以使用SVG(http://www.w3.org/TR/SVG/text.html#TextOnAPath)沿着路径发短信,并且iPhoneOS支持它。

答案 2 :(得分:0)

上面的例子很遗憾没有按预期工作。 我现在终于找到了在路径上绘制文本的正确方法。

我们走了:

您不能将此代码1:1作为我的应用程序摘录,但我会通过一些评论来澄清。

// 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);
}


}