Skip to content

Instantly share code, notes, and snippets.

@mattt
Forked from natecook1000/ RecipePageRenderer.swift
Last active February 28, 2023 03:22

Revisions

  1. @natecook1000 natecook1000 created this gist Nov 17, 2014.
    122 changes: 122 additions & 0 deletions RecipePageRenderer.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    //
    // 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)
    }
    }
    }
    26 changes: 26 additions & 0 deletions NSString+NCPrinting.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    //
    // 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
    49 changes: 49 additions & 0 deletions NSString+NCPrinting.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,49 @@
    //
    // 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
    20 changes: 20 additions & 0 deletions RecipePrintPageRenderer.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    //
    // 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

    85 changes: 85 additions & 0 deletions RecipePrintPageRenderer.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,85 @@
    //
    // 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