Question

I'm struggling now quite a while with the printing interface of iOS. What I have is one view controller with a custom UIView that does its drawing in drawRect:

An example how the drawing looks on an iPad is here (including some colored test lines):

enter image description here

I've setup a UIPrintRenderer subclass that prints header and footer and supplies the content rect drawContentForPageAtIndex:inRect: for the drawRect: method of the custom UIView. The debugger tells me, that drawRect: is called always - it doesn't matter if rect drawContentForPageAtIndex:inRect: exists in the renderer class or not. So I don't do the drawing in rect drawContentForPageAtIndex:inRect: and I use drawRect: for that.

When the printer simulator supplies A4 paper format I get a paper rect of [0, 0, 841.89, 595.276], a printable rect of [11.9905, 11.9906, 817.909, 571.294] and a content rect of [11.9905, 33.9906, 817.909, 530.294]. What I indeed don't understand is, that drawRect: gets a rect of [0, 0, 695, 530.294] from printing interface. That is the height of the content rect but the width is that from the rect when drawing is done normally w/out printing: [0, 0, 695, 648]. It looks like this in the simulator output page (there are also added some test frames, lines and circles of course):

enter image description here

Some code follows here, first the part in the view controller that sets up printing:

- (IBAction)pressedPrint:(id)sender
{
    UIPrintInteractionController*   printCtrl = [UIPrintInteractionController sharedPrintController];
    UIPrintInfo*                    printInfo = [UIPrintInfo printInfo];

    NSLog(@"%s prepare printing parameters etc.", __func__);
    printCtrl.delegate       = self;
    printInfo.outputType     = UIPrintInfoOutputPhoto;
    printInfo.orientation    = UIPrintInfoOrientationLandscape;
    printInfo.jobName        = [NSString stringWithFormat:@"%@ - %@", NSLocalizedString(@"NavTitleMain", @""), self.title];
    printInfo.duplex         = UIPrintInfoDuplexNone;
    printCtrl.printInfo      = printInfo;
    printCtrl.showsPageRange = NO;

    // This code uses a custom UIPrintPageRenderer so that it can draw a header and footer.
    DVPrintPageRenderer*    myRenderer = [[DVPrintPageRenderer alloc] init];

    // The DVPrintPageRenderer class provides a jobtitle that it will label each page with.
    myRenderer.jobTitle      = printInfo.jobName;
    myRenderer.isTwoPageView = NO;
    myRenderer.footerText    = [centralDocument sharedInstance].docTitle;
    myRenderer.drawView      = self.drawArea;

    // To draw the content of each page, a UIViewPrintFormatter is used.
    UIViewPrintFormatter*   viewFormatter = [self.drawArea viewPrintFormatter];
    UIFont*                 titleFont     = [UIFont fontWithName:@"Helvetica" size:HEADER_TEXT_HEIGHT];
    CGSize                  titleSize     = [myRenderer.jobTitle getSizeWithFont:titleFont];
    UIFont*                 footerFont    = [UIFont fontWithName:@"Helvetica" size:FOOTER_TEXT_HEIGHT];
    CGSize                  footerSize    = [myRenderer.footerText getSizeWithFont:footerFont];

    viewFormatter.startPage = 0;
    myRenderer.headerHeight = titleSize.height  + HEADER_FOOTER_MARGIN_PADDING;
    myRenderer.footerHeight = footerSize.height + HEADER_FOOTER_MARGIN_PADDING;
    [myRenderer addPrintFormatter:viewFormatter startingAtPageAtIndex:0];
    // Set our custom renderer as the printPageRenderer for the print job.
    printCtrl.printPageRenderer = myRenderer;
    drawArea.isPrinting     = YES;
    drawArea.printRenderer  = myRenderer;
    NSLog(@"%s before presenting the printer dialog", __func__);

    void (^completionHandler)(UIPrintInteractionController*, BOOL, NSError*) =
    ^(UIPrintInteractionController* printController, BOOL completed, NSError* error)
    {
        drawArea.isPrinting    = NO;
        drawArea.printRenderer = nil;

        if (!completed && error)
        {
            NSLog(@"Printing could not complete because of error: %@", error);
        }
    };

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
    {
        [printCtrl presentFromBarButtonItem:sender animated:YES completionHandler:completionHandler];
    }
    else
    {
        [printCtrl presentAnimated:YES completionHandler:completionHandler];
    }
}

The first code part of drawRect: might also be relevant here:

- (void)drawRect: (CGRect)rect
{
    // Drawing code.
    NSLog(@"%s entered for %@", __func__, (isPrinting ? @"printing" : @"screen drawing"));
    NSInteger   colorCount = (colorArray  == nil) ? -1 : [colorArray count];
    NSInteger   graphCount = (graphPoints == nil) ? -1 : [graphPoints count];

    CGContextRef    context = UIGraphicsGetCurrentContext();

    CGContextSaveGState(context);

    if (isPrinting)
    {
        // make sure that we'll print the frame rectangle
        CGRect  newRect = CGRectInset(printRenderer.rectContent, 2.0, 2.0);

        saveGraphRect = drawGraphRect;
        saveBackColor = viewBackColor;
        saveTextColor = viewBackColor;
        CGContextTranslateCTM(context, printRenderer.rectContent.origin.x, -printRenderer.rectContent.origin.y);
        NSLog(@"%s content = [%g, %g, %g, %g]", __func__,
              printRenderer.rectContent.origin.x, printRenderer.rectContent.origin.y,
              printRenderer.rectContent.size.width, printRenderer.rectContent.size.height);
        NSLog(@"%s rect(1) = [%g, %g, %g, %g]", __func__,
              rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
        rect          = CGRectMake(ceilf(newRect.origin.x),    ceilf(newRect.origin.y),
                                   floorf(newRect.size.width), floorf(newRect.size.height));
        CGContextTranslateCTM(context, rect.origin.x, rect.origin.y);
        rect.origin   = CGPointMake(0.0, 0.0);

        usedBounds    = rect;
        drawGraphRect = [self makeInsetDrawRectFrom:rect];
        viewBackColor = [UIColor whiteColor];
        axisTextColor = [UIColor blackColor];
        NSLog(@"%s prepared for printing", __func__);
        NSLog(@"%s bounds = [%g, %g, %g, %g]", __func__,
              self.bounds.origin.x, self.bounds.origin.y,
              self.bounds.size.width, self.bounds.size.height);
        NSLog(@"%s paper = [%g, %g, %g, %g]", __func__,
              printRenderer.paperRect.origin.x, printRenderer.paperRect.origin.y,
              printRenderer.paperRect.size.width, printRenderer.paperRect.size.height);
        NSLog(@"%s rect(2) = [%g, %g, %g, %g]", __func__,
              rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
        NSLog(@"%s draw = [%g, %g, %g, %g]", __func__,
              drawGraphRect.origin.x, drawGraphRect.origin.y, drawGraphRect.size.width, drawGraphRect.size.height);
    }
    else
    {
        usedBounds = self.bounds;
        ...

The NSLog output for the rects in drawRect: are also relevant:

2014-01-21 17:15:39.906 iELANA[19015:a0b] -[DrawView drawRect:] entered for screen drawing
2014-01-21 17:15:39.906 iELANA[19015:a0b] -[DrawView drawRect:] bounds = [0, 0, 695, 648]
2014-01-21 17:15:39.906 iELANA[19015:a0b] -[DrawView drawRect:] rect = [0, 0, 695, 648]
2014-01-21 17:15:39.906 iELANA[19015:a0b] -[DrawView drawRect:] draw = [62, 2, 631, 584]
2014-01-21 17:15:40.645 iELANA[19015:a0b] -[viewDiagram pressedPrint:] prepare printing parameters etc.
2014-01-21 17:15:40.645 iELANA[19015:a0b] -[viewDiagram pressedPrint:] before presenting the printer dialog
2014-01-21 17:15:46.131 iELANA[19015:a0b] printableRect = [11.9905, 11.9906, 817.909, 571.294]
2014-01-21 17:15:46.131 iELANA[19015:a0b] headerRect = [11.9905, 11.9906, 817.909, 22]
2014-01-21 17:15:46.131 iELANA[19015:a0b] header text size = [297, 17]
2014-01-21 17:15:46.133 iELANA[19015:a0b] -[DVPrintPageRenderer drawContentForPageAtIndex:inRect:] page = 0, rect [11.9905, 33.9906, 817.909, 530.294]
2014-01-21 17:15:46.133 iELANA[19015:a0b] paper     rect = [0, 0, 841.89, 595.276]
2014-01-21 17:15:46.133 iELANA[19015:a0b] printable rect = [11.9905, 11.9906, 817.909, 571.294]
2014-01-21 17:15:46.133 iELANA[19015:a0b] -[DrawView drawRect:] entered for printing
2014-01-21 17:15:46.134 iELANA[19015:a0b] -[DrawView drawRect:] content = [11.9905, 33.9906, 817.909, 530.294]
2014-01-21 17:15:46.134 iELANA[19015:a0b] -[DrawView drawRect:] rect(1) = [0, 0, 695, 530.294]
2014-01-21 17:15:46.134 iELANA[19015:a0b] -[DrawView drawRect:] prepared for printing
2014-01-21 17:15:46.134 iELANA[19015:a0b] -[DrawView drawRect:] bounds = [0, 0, 695, 648]
2014-01-21 17:15:46.134 iELANA[19015:a0b] -[DrawView drawRect:] paper = [0, 0, 841.89, 595.276]
2014-01-21 17:15:46.134 iELANA[19015:a0b] -[DrawView drawRect:] rect(2) = [0, 0, 813, 526]
2014-01-21 17:15:46.135 iELANA[19015:a0b] -[DrawView drawRect:] draw = [62, 2, 749, 462]
2014-01-21 17:15:46.140 iELANA[19015:a0b] printableRect = [11.9905, 11.9906, 817.909, 571.294]
2014-01-21 17:15:46.140 iELANA[19015:a0b] footerRect = [11.9905, 564.285, 817.909, 19]
2014-01-21 17:15:46.140 iELANA[19015:a0b] footer text size = [296, 14]

The drawing code is using Core Graphics and UIKit for frames, lines and so on. Text output is using Core Text. So there is still some work applying the correct CTM transformations. But the main first question is:

Why does drawRect: get a weird rect supplied instead of the content rect from the printing interface? If I can't break this rule, things will run out very bad for the iPhone printing instead of the iPad - it looks much worse on the iPhone with the current coding.

Should I do the printing drawing code even better in the print renderer's drawContentForPageAtIndex:inRect:?

Your help is very appreciated :-)

Konran

Added:

It seems indeed so, that the graphics context passed to drawRect: is not usable for the requirements of printing. When I return directly from drawRect: in case of printing and add the printing code to drawContentForPageAtIndex:inRect:, I get a much better (test) output:

enter image description here

But what I don't understand and where I still need your help is the positioning code for the axis texts, which are drawn with Core Text. I've tried very different CTM transformation combinations ... but any I use will lead to a mis-positioning of the texts. That's what I've done for the text output on the x-axis:

- (void)drawAxisValueXin: (CGContextRef)context withText: (NSString*)axisValueX dockToPoint: (CGPoint)dockPoint
{
    BOOL        isRetina  = (!isPrinting && AfxGetApp().isRetina);
    CGFloat     rMul      = (isRetina) ? 2.0 : 1.0;
    CGFloat     fontSize  = 10.0;
    CTFontRef   helvetica = CTFontCreateWithName(CFSTR("Helvetica"), fontSize, NULL);

    if (isRetina)
    {
        dockPoint.x *= 2.0;
        dockPoint.y *= 2.0;
    }

    // flip the coordinate system
    CGContextSaveGState(context);

    if (isPrinting)
    {
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);
        CGContextTranslateCTM(context, 0.0f, CGRectGetHeight(printRenderer.paperRect));
        CGContextScaleCTM(context, 1.0f, -1.0f);
    }
    else
    {
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);
        CGContextTranslateCTM(context, 0.0f, CGRectGetHeight(usedBounds) * rMul);
        CGContextScaleCTM(context, 1.0f, -1.0f);
    }

    // make the attributed string
    NSMutableAttributedString*  attrString = [[NSMutableAttributedString alloc] initWithString:axisValueX];
    NSRange                     strRange   = NSMakeRange(0, [attrString length]);

    [attrString addAttribute: (id)kCTFontAttributeName
                       value: (__bridge id)helvetica
                       range: strRange];
    [attrString addAttribute: (id)kCTForegroundColorAttributeName
                       value: (id)axisTextColor.CGColor
                       range: strRange];

    // draw the text
    CTLineRef   ctLine    = CTLineCreateWithAttributedString((CFAttributedStringRef)attrString);
    CGFloat     ascent;
    CGFloat     descent;
    CGFloat     width     = ceilf(CTLineGetTypographicBounds(ctLine, &ascent, &descent, NULL));
    CGFloat     height    = ceilf(ascent + descent);
    CGSize      textSize  = CGSizeMake(width, height);
    CGPoint     atPoint   = CGPointMake(dockPoint.x + (1.0 - textSize.width) * rMul,
                                        dockPoint.y + (10.0 - 2.0) * rMul); // x = text right edge, y = text mid
    CGPoint     userPoint = CGContextConvertPointToUserSpace(context, atPoint);

    if (fabsf(tiltLeftAxisX) > 1.0e-8)
    {
        CGContextRotateCTM(context, -tiltLeftAxisX);
        dockPoint.y += 2.0 * rMul;      // offset y 2 points down
        dockPoint = CGContextConvertPointToUserSpace(context, dockPoint);
        dockPoint.x = nearbyintf(dockPoint.x);
        dockPoint.y = nearbyintf(dockPoint.y);
        userPoint = CGPointMake(dockPoint.x - width,
                                dockPoint.y - height / 2.0);
    }

    CGContextSetTextPosition(context, userPoint.x, userPoint.y);
    CTLineDraw(ctLine, context);

    CGRect  rectTempl = CGRectMake(-5.0, -5.0, 10.0, 10.0);
    CGRect  rectDot   = CGRectOffset(rectTempl, userPoint.x, userPoint.y);

    [[UIColor redColor] set];
    CGContextFillEllipseInRect(context, rectDot);
    rectDot = CGRectOffset(rectTempl, atPoint.x, atPoint.y);
    [[UIColor greenColor] set];
    CGContextFillEllipseInRect(context, rectDot);

    // clean up
    CFRelease(ctLine);
    CFRelease(helvetica);
    CGContextRestoreGState(context);
}
Was it helpful?

Solution

Printing context is really very different from drawing context. So the conclusion for the method above that finally works is like this one:

- (void)drawAxisValueXin: (CGContextRef)context withText: (NSString*)axisValueX dockToPoint: (CGPoint)dockPoint
{
    BOOL        isRetina  = (!isPrinting && AfxGetApp().isRetina);
    CGFloat     rMul      = (isRetina) ? 2.0 : 1.0;
    CGFloat     fontSize  = 10.0;
    CTFontRef   helvetica = CTFontCreateWithName(CFSTR("Helvetica"), fontSize, NULL);

    if (isRetina)
    {
        dockPoint.x *= 2.0;
        dockPoint.y *= 2.0;
    }
    else if (isPrinting)
    {
        dockPoint.y += 2.0;
    }

    // flip the coordinate system
    CGContextSaveGState(context);

    if (isPrinting)
    {
        CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0, -1.0));
    }
    else
    {
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);
        CGContextTranslateCTM(context, 0.0f, CGRectGetHeight(usedBounds) * rMul);
        CGContextScaleCTM(context, 1.0f, -1.0f);
    }

    // make the attributed string
    NSMutableAttributedString*  attrString = [[NSMutableAttributedString alloc] initWithString:axisValueX];
    NSRange                     strRange   = NSMakeRange(0, [attrString length]);

    [attrString addAttribute: (id)kCTFontAttributeName
                       value: (__bridge id)helvetica
                       range: strRange];
    [attrString addAttribute: (id)kCTForegroundColorAttributeName
                       value: (id)axisTextColor.CGColor
                       range: strRange];

    // draw the text
    CTLineRef   ctLine    = CTLineCreateWithAttributedString((CFAttributedStringRef)attrString);
    CGFloat     ascent;
    CGFloat     descent;
    CGFloat     width     = ceilf(CTLineGetTypographicBounds(ctLine, &ascent, &descent, NULL));
    CGFloat     height    = ceilf(ascent + descent);
    CGSize      textSize  = CGSizeMake(width, height);
    CGPoint     atPoint   = CGPointMake(dockPoint.x + (1.0 - textSize.width) * rMul,
                                        dockPoint.y + (10.0 - 2.0) * rMul); // x = text right edge, y = text mid
    CGPoint     userPoint = (isPrinting) ? atPoint : CGContextConvertPointToUserSpace(context, atPoint);

    if (fabsf(tiltLeftAxisX) > 1.0e-8)
    {
        if (isPrinting)
        {
            CGFloat xMove = textSize.height * sin(tiltLeftAxisX);
            CGFloat yMove = textSize.width  * sin(tiltLeftAxisX);

            userPoint.x -= xMove;
            userPoint.y -= yMove;
            userPoint    = CGContextConvertPointToDeviceSpace(context, userPoint);
            CGContextRotateCTM(context, tiltLeftAxisX);
            userPoint    = CGContextConvertPointToUserSpace(context, userPoint);
        }
        else
        {
            CGContextRotateCTM(context, -tiltLeftAxisX);
            dockPoint.y += 2.0 * rMul;      // offset y 2 points down
            dockPoint    = CGContextConvertPointToUserSpace(context, dockPoint);
            dockPoint.x  = nearbyintf(dockPoint.x);
            dockPoint.y  = nearbyintf(dockPoint.y);
            userPoint    = CGPointMake(dockPoint.x - width,
                                       dockPoint.y - height / 2.0);
        }
    }

    CGContextSetTextPosition(context, userPoint.x, userPoint.y);
    CTLineDraw(ctLine, context);

    // clean up
    CFRelease(ctLine);
    CFRelease(helvetica);
    CGContextRestoreGState(context);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top