Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//
// RecipePageRenderer.swift
// (c) 2014 Nate Cook, licensed under the MIT License
//
import UIKit
import AVFoundation
/// Units for printing content insets
let POINTS_PER_INCH: CGFloat = 72
/// The alignment for drawing an NSString inside a bounding rectangle.
enum NCStringAlignment {
case LeftTop
case CenterTop
case RightTop
case LeftCenter
case Center
case RightCenter
case LeftBottom
case CenterBottom
case RightBottom
}
extension NSString {
/// Draw the `NSString` inside the bounding rectangle with a given alignment.
func drawAtPointInRect(rect: CGRect, withAttributes attributes: [NSObject: AnyObject]?, andAlignment alignment: NCStringAlignment) {
let size = self.sizeWithAttributes(attributes)
var x, y: CGFloat
switch alignment {
case .LeftTop, .LeftCenter, .LeftBottom:
x = CGRectGetMinX(rect)
case .CenterTop, .Center, .CenterBottom:
x = CGRectGetMidX(rect) - size.width / 2
case .RightTop, .RightCenter, .RightBottom:
x = CGRectGetMaxX(rect) - size.width
}
switch alignment {
case .LeftTop, .CenterTop, .RightTop:
y = CGRectGetMinY(rect)
case .LeftCenter, .Center, .RightCenter:
y = CGRectGetMidY(rect) - size.height / 2
case .LeftBottom, .CenterBottom, .RightBottom:
y = CGRectGetMaxY(rect) - size.height
}
self.drawAtPoint(CGPoint(x: x, y: y), withAttributes: attributes)
}
}
class RecipePrintPageRenderer: UIPrintPageRenderer {
let authorName: NSString
let recipe: Recipe
let pageNumberAttributes = [NSFontAttributeName: UIFont(name: "Georgia-Italic", size: 11)!]
let nameAttributes = [NSFontAttributeName: UIFont(name: "Georgia", size: 11)!]
init(authorName: String, recipe: Recipe) {
self.authorName = authorName
self.recipe = recipe
super.init()
self.headerHeight = 0.5 * POINTS_PER_INCH
// create formatter with the recipe's HTML text
// per page insets should be one-inch, with 3.5 inches on the right to make room for photos
let formatter = UIMarkupTextPrintFormatter(markupText: recipe.html)
formatter.perPageContentInsets = UIEdgeInsets(top: POINTS_PER_INCH, left: POINTS_PER_INCH, bottom: POINTS_PER_INCH,
right: POINTS_PER_INCH * 3.5)
addPrintFormatter(formatter, startingAtPageAtIndex: 0)
}
override func drawHeaderForPageAtIndex(pageIndex: Int, var inRect headerRect: CGRect) {
var headerInsets = UIEdgeInsets(top: CGRectGetMinY(headerRect), left: POINTS_PER_INCH, bottom: CGRectGetMaxY(paperRect) - CGRectGetMaxY(headerRect), right: POINTS_PER_INCH)
headerRect = UIEdgeInsetsInsetRect(paperRect, headerInsets)
// author name on left
authorName.drawAtPointInRect(headerRect, withAttributes: nameAttributes, andAlignment: .LeftCenter)
// page number on right
let pageNumberString: NSString = "\(pageIndex + 1)"
pageNumberString.drawAtPointInRect(headerRect, withAttributes: pageNumberAttributes, andAlignment: .RightCenter)
}
func drawImages(images: [UIImage], var inRect sourceRect: CGRect) {
// we'll use 1/8 of an inch of vertical padding between each image
let imagePadding = UIEdgeInsets(top: pointsPerInch / 8, left: 0, bottom: 0, right: 0)
for image in images {
// get the aspect-fit size of the image
var sizedRect = AVMakeRectWithAspectRatioInsideRect(image.size, sourceRect)
// if the new width of the image doesn't match the source rect, there wasn't enough vertical room: bail
if sizedRect.width != sourceRect.width {
return
}
// use divide to separate the image rect from the rest of the column
CGRectDivide(sourceRect, &sizedRect, &sourceRect, sizedRect.height, .MinYEdge)
// draw the image
image.drawInRect(sizedRect)
// inset the source rect to make a little padding before the next image
sourceRect = UIEdgeInsetsInsetRect(sourceRect, imagePadding)
}
}
override func drawContentForPageAtIndex(pageIndex: Int, inRect contentRect: CGRect) {
if pageIndex == 0 {
// only use rightmost two inches of contentRect
let imagesRectWidth = POINTS_PER_INCH * 2
let imagesRectHeight = paperRect.height - POINTS_PER_INCH - (CGRectGetMaxY(paperRect) - CGRectGetMaxY(contentRect))
let imagesRect = CGRect(x: CGRectGetMaxX(paperRect) - imagesRectWidth - POINTS_PER_INCH, y: paperRect.origin.y + POINTS_PER_INCH, width: imagesRectWidth, height: imagesRectHeight)
drawImages(recipe.images, inRect: imagesRect)
}
}
}
//
// NSString+NCPrinting.h
// (c) 2014 Nate Cook, licensed under the MIT License
//
#import <UIKit/UIKit.h>
// The alignment for drawing an NSString inside a bounding rectangle.
typedef NS_ENUM(NSInteger, NCStringAlignment) {
NCStringAlignmentLeftTop,
NCStringAlignmentCenterTop,
NCStringAlignmentRightTop,
NCStringAlignmentLeftCenter,
NCStringAlignmentCenter,
NCStringAlignmentRightCenter,
NCStringAlignmentLeftBottom,
NCStringAlignmentCenterBottom,
NCStringAlignmentRightBottom
};
@interface NSString (NCPrinting)
// Draw the `NSString` inside the bounding rectangle with a given alignment.
- (void)drawAtPointInRect:(CGRect)rect withAttributes:(NSDictionary *)attrs andAlignment:(NCStringAlignment)alignment;
@end
//
// NSString+NCPrinting.m
// (c) 2014 Nate Cook, licensed under the MIT License
//
#import "NSString+NCPrinting.h"
@implementation NSString (NCPrinting)
- (void)drawAtPointInRect:(CGRect)rect withAttributes:(NSDictionary *)attrs andAlignment:(NCStringAlignment)alignment {
CGSize size = [self sizeWithAttributes:attrs];
CGFloat x, y;
// x position
switch (alignment) {
case NCStringAlignmentLeftTop:
case NCStringAlignmentLeftCenter:
case NCStringAlignmentLeftBottom:
x = CGRectGetMinX(rect); break;
case NCStringAlignmentCenterTop:
case NCStringAlignmentCenter:
case NCStringAlignmentCenterBottom:
x = CGRectGetMidX(rect) - size.width / 2; break;
case NCStringAlignmentRightTop:
case NCStringAlignmentRightCenter:
case NCStringAlignmentRightBottom:
x = CGRectGetMaxX(rect) - size.width; break;
}
// y position
switch (alignment) {
case NCStringAlignmentLeftTop:
case NCStringAlignmentCenterTop:
case NCStringAlignmentRightTop:
y = CGRectGetMinY(rect); break;
case NCStringAlignmentLeftCenter:
case NCStringAlignmentCenter:
case NCStringAlignmentRightCenter:
y = CGRectGetMidY(rect) - size.height / 2; break;
case NCStringAlignmentLeftBottom:
case NCStringAlignmentCenterBottom:
case NCStringAlignmentRightBottom:
y = CGRectGetMaxY(rect) - size.height; break;
}
[self drawAtPoint:CGPointMake(x, y) withAttributes:attrs];
}
@end
//
// RecipePrintPageRenderer.h
// (c) 2014 Nate Cook, licensed under the MIT License
//
#import <UIKit/UIKit.h>
#define POINTS_PER_INCH 72
@class Recipe;
@interface RecipePrintPageRenderer : UIPrintPageRenderer
@property (nonatomic, strong) NSString *authorName;
@property (nonatomic, weak) Recipe *recipe;
- (id)initWithAuthorName:(NSString *)authorName recipe:(Recipe *)recipe;
@end
//
// RecipePrintPageRenderer.m
// (c) 2014 Nate Cook, licensed under the MIT License
//
#import "RecipePrintPageRenderer.h"
#import "NSString+NCPrinting.h"
#import <AVFoundation/AVFoundation.h>
@interface RecipePrintPageRenderer ()
@property (nonatomic, strong) NSDictionary *pageNumberAttributes;
@property (nonatomic, strong) NSDictionary *nameAttributes;
@end
@implementation RecipePrintPageRenderer
- (id)initWithAuthorName:(NSString *)authorName recipe:(Recipe *)recipe {
if (self = [super init]) {
self.authorName = authorName;
self.recipe = recipe;
self.headerHeight = 0.5;
self.pageNumberAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"Georgia-Italic" size: 11] };
self.nameAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"Georgia" size: 11] };
// create formatter with the recipe's HTML text
// per page insets should be one-inch, with 3.5 inches on the right to make room for photos
UIMarkupTextPrintFormatter *formatter = [[UIMarkupTextPrintFormatter alloc] initWithMarkupText:recipe.html];
formatter.perPageContentInsets = UIEdgeInsetsMake(POINTS_PER_INCH, POINTS_PER_INCH, POINTS_PER_INCH, POINTS_PER_INCH * 3.5);
[self addPrintFormatter:formatter startingAtPageAtIndex:0];
}
return self;
}
- (void)drawHeaderForPageAtIndex:(NSInteger)index inRect:(CGRect)headerRect {
UIEdgeInsets headerInsets = UIEdgeInsetsMake(CGRectGetMinY(headerRect), POINTS_PER_INCH, CGRectGetMaxY(self.paperRect) - CGRectGetMaxY(headerRect), POINTS_PER_INCH);
headerRect = UIEdgeInsetsInsetRect(self.paperRect, headerInsets);
// author name on left
[self.authorName drawAtPointInRect:headerRect withAttributes:self.nameAttributes andAlignment:NCStringAlignmentLeftCenter];
// page number on right
NSString *pageNumberString = [NSString stringWithFormat:@"%ld", index + 1];
[pageNumberString drawAtPointInRect:headerRect withAttributes:self.pageNumberAttributes andAlignment:NCStringAlignmentRightCenter];
}
- (void)drawImages:(NSArray *)images inRect:(CGRect)sourceRect {
// we'll use 1/8 of an inch of vertical padding between each image
UIEdgeInsets imagePadding = UIEdgeInsetsMake(POINTS_PER_INCH / 8, 0, 0, 0);
for (UIImage *image in images) {
// get the aspect-fit size of the image
CGRect sizedRect = AVMakeRectWithAspectRatioInsideRect(image.size, sourceRect);
// if the new width of the image doesn't match the source rect, there wasn't enough vertical room: bail
if (CGRectGetWidth(sizedRect) != CGRectGetWidth(sourceRect)) {
return;
}
// use divide to separate the image rect from the rest of the column
CGRectDivide(sourceRect, &sizedRect, &sourceRect, CGRectGetHeight(sizedRect), CGRectMinYEdge);
// draw the image
[image drawInRect:sizedRect];
// inset the source rect to make a little padding before the next image
sourceRect = UIEdgeInsetsInsetRect(sourceRect, imagePadding);
}
}
- (void)drawContentForPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)contentRect {
if (pageIndex == 0) {
// only use rightmost two inches of contentRect
CGFloat imagesRectWidth = POINTS_PER_INCH * 2;
CGFloat imagesRectHeight = CGRectGetHeight(self.paperRect) - POINTS_PER_INCH - (CGRectGetMaxY(self.paperRect) - CGRectGetMaxY(contentRect));
CGRect imagesRect = CGRectMake(CGRectGetMaxX(self.paperRect) - imagesRectWidth - POINTS_PER_INCH, CGRectGetMinY(self.paperRect) + POINTS_PER_INCH, imagesRectWidth, imagesRectHeight);
[self drawImages:self.recipe.images inRect:imagesRect];
}
}
@end
@vineethvijayan

This comment has been minimized.

Copy link

commented Feb 14, 2016

Hey Matt, Thanks for this gist, I am trying to make this work for footer

screen shot 2016-02-14 at 6 44 55 pm

but it is getting sliced. like in the pic any idea why?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.