假设我有一个由点组成的线数组和一段文本。如何在这条线上绘制文本?
- (void)drawRect:(CGRect)rect
如何在UIView中绘制文本?
我已经成功绘制了路径。我是否忽略了某个标准方法或框架,可以让我在该路径上绘制文本?理想情况下,我希望只使用QuartzCore/CoreGraphics实现此目的。
我尝试计算每个字符的宽度并旋转每个字符。这种方法有点可行,但我想知道是否有更优雅的方法。
假设我有一个由点组成的线数组和一段文本。如何在这条线上绘制文本?
- (void)drawRect:(CGRect)rect
如何在UIView中绘制文本?
我已经成功绘制了路径。我是否忽略了某个标准方法或框架,可以让我在该路径上绘制文本?理想情况下,我希望只使用QuartzCore/CoreGraphics实现此目的。
我尝试计算每个字符的宽度并旋转每个字符。这种方法有点可行,但我想知道是否有更优雅的方法。
我相信在Mac OS X上你可以做到这一点,但在iPhone上最接近的方法是使用CGContextShowGlyphsWithAdvances,但它甚至不能旋转。
使用循环并使用类似以下内容绘制每个字符不应该太难。这是从苹果的文档中改编而来,未经测试,请注意:
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);
}
这可能与主题无关,但是您可以使用SVG(http://www.w3.org/TR/SVG/text.html#TextOnAPath)沿路径进行文本编写,而iPhoneOS支持它。
上述示例不幸地没有按预期工作。
现在我终于找到了沿路径绘制文本的正确方法。
让我们开始吧:
您不能将此代码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);
}
}
NSString
方法来确定不同字体字符的宽度(例如 sizeWithFont:
)。我们的主要问题是 CGContextShowTextAtPoint
不支持非拉丁字符。 - Felix Lamouroux