代码之家  ›  专栏  ›  技术社区  ›  Felix Lamouroux

在QuartzCore中沿着路径绘制文本

  •  8
  • Felix Lamouroux  · 技术社区  · 15 年前

    假设我有一组点组成一条线和一个文本。我该怎么画这条线的文字呢

     - (void)drawRect:(CGRect)rect 
    

    一个UIVIEW?

    我可以毫无问题地画出这条路。是否有一个我忽略的标准方法,或者一个允许我沿着这条路径绘制文本的框架?理想情况下,我只想使用QuartzCore/Coregraphics来完成这项工作。

    我试着计算每个字符的宽度并旋转每个字符。这类作品,但我想知道是否有更优雅的方法。

    3 回复  |  直到 13 年前
        1
  •  11
  •   David Kanarek    15 年前

    我相信你可以在MacOSX上做到这一点,但你最接近iPhone的是cContextShowGlyphSwithAdvances,它甚至不会旋转。

    使用一个循环并使用类似下面的内容绘制每个字符不应该太难。这是根据苹果的文档改编的,没有经过测试,因此请注意:

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

    编辑: 下面是positionalLongPath函数的一个概念。同样,它们没有经过测试,但应该很接近。起源于…如果路径用完,则返回(-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);
    }
    
        2
  •  1
  •   kennytm    15 年前

    这可能与SVG无关,但您可以使用SVG沿路径发送文本( http://www.w3.org/TR/SVG/text.html#TextOnAPath )iPhoneOS支持它。

        3
  •  0
  •   Daniel Cagara    13 年前

    不幸的是,上面的例子并没有按预期工作。 现在,我终于找到了沿着路径绘制文本的正确方法。

    我们走到这里:

    您不能将这段代码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);
    }
    
    
    }