views:

260

answers:

2

Hey folks,

My blog has been mentioned here before as an answer to questions. I can't link to one of them, because this form claims I'm a spammer. Sigh. Anyway, now I want to write the definitive blog post on drawing rotated text, so it can also be used for answers here (heh). Here is my first try:

http://www.platinumball.net/blog/2009/06/01/drawing-nsstrings-in-unusual-rotations/

That works well, it does everything I want, except for the fact that it uses CGContextSelectFont(), which limits text output to MacRoman. That is Teh Sukk.

I've been searching for an answer to this problem for almost a year now, including searching this site several times, without success. I've seen examples that seem close to what I want, but apparently I'm too stupid to bend them to my will. I'm including the main method from the NSString category I wrote, which shows the guts of what I'm doing. If you need to see the rest of the code, you can download it from the link to my blog I gave above. Thanks ...

-(void)drawAtPoint:(CGPoint)point withFont:(UIFont*)font
 orientation:(WBOrientation)orient
{
    CGContextRef       ctxt = [self contextSave];
    const std::string  tbuf = toStdString(self, NSMacOSRomanStringEncoding);
    CGAffineTransform  tran = CGAffineTransformMake(1, 0, 0, -1, 0, 0);

    switch (orient)
    {
        case WBOrientUp:
        // nothing to do
        break;

        case WBOrientDown:
        tran = CGAffineTransformRotate(tran, degreesToRadians(180.0));
        break;

        case WBOrientLeft:
        tran = CGAffineTransformRotate(tran, degreesToRadians(90.0));
        break;

        case WBOrientRight:
        tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0));
        break;

        default:
        assert(false);
        break;
    }

    contextFont(ctxt, font);
    CGContextSetTextDrawingMode(ctxt, kCGTextFill);
    CGContextSetTextMatrix(ctxt, tran);
    CGContextSetTextPosition(ctxt, point.x, point.y);
    CGContextShowText(ctxt, tbuf.c_str(), tbuf.length());

    [self contextRestore:ctxt];
}
+1  A: 

only a few days after i did it myself =)

this is how i solved it:

view.layer.transform = CATransform3DRotate(view.layer.transform, 3.1415, 0,0,1); // this is for 180 degrees.
Tomen
Can you clarify how I would use that? I probably have to create a layer, which I am not familiar with. And how would I set a rotation other than 180 degrees. I guess I should have mentioned that I am a complete math idiot. Probably why I haven't been able to fix this in a year of trying.
Allen Brunson
@Allen: All UIViews are backed by a Core Animation layer, which is accessible via the .layer property. 360 degrees equals 2*pi radians. 180 degrees is pi, or 3.1415.
Darren
Okay, so that would work if I were drawing into a UIView. But it would appear that I can't make my original API work, i.e., drawing into the current context, because it might *not* be a view.If anybody knows how to make this work when drawing into a context that is not necessarily a view, I'd love to hear about it.
Allen Brunson
Well, the basic concept of this line of code is that you manipulate the transform of a core animation layer. So this code will work if you make use of Core Animation layers. (I am not very experienced with Core Animation YET, but this was a nice little starter for me =)
Tomen
I'd suggest using the M_PI constant for a more accurate slice of pi.
pixel
+1  A: 

Set the CGContext as the current graphics context using the UIGraphicsPushContext function, then draw the string using UIKit, then pop the context back off the stack using the UIGraphicsPopContext function.

The AppKit (Mac) solution is essentially the same, using NSGraphicsContext for step 1 and AppKit's NSString additions for step 2. For window contexts in -[NSView drawRect:], step 3 goes away (you would obtain the CGContext from the NSGraphicsPort in step 1, meaning it's already set); for other contexts, such as bitmap or PDF contexts, you would use the same NSGraphicsPort method (setCurrentContext:) in both step 1 and step 3.

Peter Hosey
Sounds good, but I can't make it work. I removed this line: CGContextShowText(ctxt, tbuf.c_str(), tbuf.length());and replaced it with these: UIGraphicsPushContext(ctxt); [self drawAtPoint:point withFont:font]; UIGraphicsPopContext();With these changes in place, the text is drawn, but it is no longer rotated. I also tried removing the contextFont() call, but that has no effect either way. I'm doing my tests in the iPhone simulator.
Allen Brunson
Allen Brunson: Well, now that there's code in your question, I can see that you don't need to set the current context, because you got your context from `UIGraphicsGetCurrentContext`—i.e., it's already current. So, cut the `PushContext` and `PopContext` calls and just use the UIKit drawing method. “UIKit does not provide methods for drawing text in any other orientation except standard horizontal.” …in the context's co-ordinate system, which you rotate by changing its context's current transformation matrix.
Peter Hosey
Sorry, but that doesn't work either! (And that code sample has been in the question since I first posted it.) I'm trying to re-engage my brain, and I think I tried this back when I was originally writing the category. The UIStringDrawing methods just don't seem to respect the current context's rotation. Which is why I dropped down to the low-level CG methods.I'd love to be proven wrong here. Maybe there's some other detail I'm getting wrong? Write a method that replaces mine, but draws the text at different rotations, and you win the internets.
Allen Brunson
Can you show the revised method with the UIKit string-drawing statements in it?
Peter Hosey
here you go:http://www.platinumball.net/txt/textdrawing.txtto recap: the method i pasted into the question works, except text is restricted to MacRoman. the method shown in the link i gave right there works, except the text isn't rotated.
Allen Brunson
Why set the text position? You tell it where to draw the text in the `drawAtPoint:withFont:` message. I'm dubious about your use of the text matrix as well (I think that only applies to Core Graphics text-drawing, which, as you're aware, has serious drawbacks). I suggest dropping the `CGContextSetTextPosition` call and concatenating the matrix with the regular current transformation matrix, not the text matrix.
Peter Hosey
Yeah, that code was vestigial, so I took it out. If I use CGContextConcatCTM(), then the text is indeed rotated, but it's also mirror-flipped, making it unintelligible. I also tried CGContextRotateCTM() with a number of radians, dispensing with the CGAffineTransform object altogether. In that scenario, the text isn't printed at all, or perhaps it's printed somewhere that's far outside the view I'm using for testing. I looked in UIGraphics.h to see if there was a UIGraphicsSomething() method that might apply, but nothing in that header appears to deal with rotations. Any other methods?
Allen Brunson
Mirror-flipped? As in flipped *horizontally* (i.e., written backwards)? As for the text not being printed/being printed outside the view, the latter explanation is plausible: Rotation goes around the origin. So, my suggestion is to change the origin: Translate the CTM by the point, then rotate, then draw at 0, 0.
Peter Hosey