I want to print a document which is consists of multiple pages with different orientations. Let's say every odd page is in portrait orientation and every even page is in landscape orientation. Below is the code for my PrintView
class:
// PrintView.h
@interface PrintView : NSView
{
NSPrintInfo* printInfo;
int nPages;
}
- (id)initWithParams:(CGRect)frame pages:(int)pagesCount;
- (void)setPrintInfo:(NSPrintInfo *)pi;
@end
// PrintView.m
#import "PrintView.h"
@implementation PrintView
- (id)initWithParams:(CGRect)frame pages:(int)pagesCount
{
self = [super initWithFrame:frame];
if (self)
{
nPages = pagesCount;
}
return self;
}
- (void)setPrintInfo:(NSPrintInfo *)pi
{
if (printInfo != pi)
{
printInfo = pi;
}
}
// print support
- (BOOL)knowsPageRange:(NSRangePointer)range
{
range->location = 1;
range->length = nPages;
return YES;
}
- (NSRect)rectForPage:(NSInteger)page
{
if (page % 2)
[printInfo setOrientation:NSPaperOrientationPortrait];
else
[printInfo setOrientation:NSPaperOrientationLandscape];
NSRect r = NSMakeRect(0, 0, printInfo.paperSize.width, printInfo.paperSize.height);
[self setFrame:r];
return r;
}
@end
This code simply produces blank pages without any drawings. Page orientations are applied in rectForPage:
.
When I run print operation (more details in function onBtnClick
in MRE below) in built-in preview I see that all pages are shown in portrait orientation:
though actually the second page should be in landscape orientation. Moreover, if I print this to PDF I can see that in PDF all page orientations are correct:
So the problem is in built-in Cocoa preview as I think. If the last page is in landscape orientation then all pages in preview will be shown in landscape orientation too. Seems that only last page orientation is applied to preview.
Is there any workarounds for showing pages with different orientations in default Cocoa printing preview? Or am I doing something wrong?
Below is the code for simple application with just one button labeled "Print". Pressing on that button starts printing process. You can run and compile it in Xcode (I use Xcode version 15.2). Files PrintView.m
and PrintView.h
are listed above so here are the rest:
// main.m
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char *argv[])
{
@autoreleasepool
{
AppDelegate *delegate = [[AppDelegate alloc] init];
NSApplication *app = [NSApplication sharedApplication];
app.delegate = delegate;
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp activateIgnoringOtherApps:YES];
NSApplicationMain(argc, argv);
}
return 0;
}
// AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "ViewController.h"
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (nonatomic, strong) NSWindow* window;
@property (nonatomic, strong) NSViewController* controller;
@end
// AppDelegate.m
#import "AppDelegate.h"
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
int mask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
_window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 200) styleMask:mask backing:2 defer:NO];
_controller = [[ViewController alloc] init];
[_window.contentView addSubview:_controller.view];
[_window makeKeyAndOrderFront:nil];
}
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
return YES;
}
@end
// ViewController.h
#import <Cocoa/Cocoa.h>
@interface ViewController : NSViewController
@property (nonatomic, strong) NSButton* button;
-(void) onBtnClick;
@end
// ViewController.m
#import "ViewController.h"
#import "PrintView.h"
@implementation ViewController
- (void)loadView
{
self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 200)];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_button = [NSButton buttonWithTitle:@"Print" target:self action:@selector(onBtnClick)];
[_button setFrame:NSMakeRect(50, 50, 100, 100)];
[self.view addSubview:_button];
}
- (void)onBtnClick
{
int nPages = 3; // here you can change the number of page being printed
NSLog(@"printint %d pages...", nPages);
PrintView* pView = [[PrintView alloc] initWithParams:NSMakeRect(0, 0, 100, 100) pages:nPages];
NSPrintInfo* printInfo = [NSPrintInfo sharedPrintInfo];
NSPrintPanelOptions options = NSPrintPanelShowsCopies;
options |= NSPrintPanelShowsPageRange;
options |= NSPrintPanelShowsPaperSize;
options |= NSPrintPanelShowsPrintSelection;
options |= NSPrintPanelShowsPreview;
NSPrintOperation* pro;
if (printInfo)
pro = [NSPrintOperation printOperationWithView:pView printInfo:printInfo];
else
pro = [NSPrintOperation printOperationWithView:pView];
[pView setPrintInfo:[pro printInfo]];
[pro setShowsPrintPanel:YES];
[[pro printPanel] setOptions:options];
[pro runOperation];
}
@end
After doing some research I have figured out that there is actually no way to apply landscape orientation only to some pages while leaving other pages as portrait in default printing preview. One either can apply landscape orientation or portrait orientation to ALL pages.
So I have solved my problem by rotating content inside of pages instead. That it is the way Preview.app works, for example.
You can distinguish between printing to preview and printing to file using printInfo.jobDisposition
which is NSPrintSpoolJob
when printing to a screen or an actual printer and NSPrintSaveJob
when saving to PDF. That way you can save your actual page orientations while printing to PDF.
Here is modified rectForPage:
function:
- (NSRect)rectForPage:(NSInteger)page
{
currentPage = (int)page - 1;
if (printInfo.jobDisposition == NSPrintSaveJob)
{
// apply my page orientation only when saving to file
[printInfo setOrientation:[self getCurrentPageOrientation]];
}
NSRect r = NSMakeRect(0, 0, printInfo.paperSize.width, printInfo.paperSize.height);
[self setFrame:r];
return r;
}
Function getCurrentPageOrientation
simply return 0 for odd pages and 1 for even pages as before.
And here is overriden drawRect:
function:
- (void)drawRect:(NSRect)dirtyRect
{
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] CGContext];
if (printInfo.jobDisposition == NSPrintSpoolJob && (int)printInfo.orientation != [self getCurrentPageOrientation])
{
// rotate content only when drawing on preview AND current page is in wrong orientation
CGAffineTransform transform = CGAffineTransformMake(0, -1, 1, 0, 0, dirtyRect.size.height);
CGContextConcatCTM(context, transform);
}
// rect sizes
int rectW = 200;
int rectH = 100;
// bottom left rectangle
CGRect rect1 = NSMakeRect(0, 0, rectW, rectH);
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextFillRect(context, rect1);
}
I draw a red rectangle 100 x 200 at the left bottom corner of every page. For every page that is in wrong orientation I apply transform matrix to rotate it by 90 degrees clockwise (like in Preview.app) and move it so it fits in view rect.
Here the screenshot of how print preview looks now:
And since I've added NSPrintPanelShowsOrientation
to NSPrintPanelOptions
you can even see how it looks in landscape orientation:
And if I print it to PDF I get all pages in specified orientation:
It's really weird and unintuitive that Apple didn't add functionality of rotating some pages in default print preview. Hope they'll add it sometime. And I also hope this answer help those who are facing similar problem.