PyObjC Core Graphics

macOS 版 comipoli も PDF 対応にしたい。

NSPDFImageRep – AppKit | Apple Developer Documentation

こんなものがあったのか!
しかも NSImage からの継承。
ということは NSImage のメソッドもそのまま使えるってことだ。

elif is_pdf and re.search(r'\.pdf$', path, re.I):
    self.status = 3
    self.namelist.clear()
    data = NSData.dataWithContentsOfFile_(path)
    self.pdf = NSPDFImageRep.alloc().initWithData_(data)

######

elif self.status == 3:
    self.pdf.setCurrentPage_(num)
    return self.pdf

を前々回のソースに追記するだけで本当にイケた!
ただ cairo と Poppler の時と同じように背景は白で塗りつぶす必要があった。

しかし、画像だけの PDF だとトンデモなく表示が重いんですけど。。。。。
テキストだけの PDF なら余裕なんだけど。
筆者の mac は最新機とはいえ Air だし、pro なら。。。。。

もしかして Core Graphics を直で使ったら軽くなるかな?
と思ったので試すことに。
PyObjC の CoreGraphics は以下に入っていた
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Quartz

PDF Document Creation, Viewing, and Transforming

上記を PyObjC で簡単に使ってみる。
PATH は自前で持っている PDF に書き換えてください。

#!/usr/bin/env python3

import objc
from AppKit import *
from Quartz.CoreGraphics import *

#PATH = '/Users/sasakima-nao/Documents/ProgrammingWithObjectiveC.pdf'
PATH = '/Users/sasakima-nao/Documents/big_picture.pdf'

class ComipoliView(NSView):
    def init(self):
        objc.super(ComipoliView, self).init()
        # PDF Document
        url = NSURL.fileURLWithPath_isDirectory_(PATH, False)
        self.pdfdoc = CGPDFDocumentCreateWithURL(url)
        self.max_page = CGPDFDocumentGetNumberOfPages(self.pdfdoc)
        self.page_number = 1
        return self

    def drawRect_(self, rect):
        NSColor.blackColor().set()
        NSRectFill(rect)
        # CGContext
        ctx = NSGraphicsContext.currentContext().CGContext()
        page = CGPDFDocumentGetPage(self.pdfdoc, self.page_number)
        #
        # Draw (x1)
        #CGContextDrawPDFPage(ctx, page)
        #
        # Resize Draw
        CGContextSaveGState(ctx)
        # start
        m = CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, rect, 0, True)
        CGContextConcatCTM(ctx, m)
        # background color is white
        cgrect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox)
        NSColor.whiteColor().set()
        CGContextFillRect(ctx, cgrect)
        # clip
        CGContextClipToRect(ctx, cgrect)
        CGContextDrawPDFPage(ctx, page)
        # end
        CGContextRestoreGState(ctx)

class ComipoliWindow(NSWindow):
    def init(self):
        frame = NSMakeRect(100, 400, 900, 600)
        objc.super(ComipoliWindow, self).initWithContentRect_styleMask_backing_defer_(
            frame,
            NSTitledWindowMask |
            NSClosableWindowMask |
            NSResizableWindowMask |
            NSMiniaturizableWindowMask,
            NSBackingStoreBuffered,
            False)
        self.setTitle_('draw PDF')
        self.setDelegate_(self)
        # view
        self.canvas = ComipoliView.new()
        self.canvas.setFrameSize_(frame.size)
        self.contentView().addSubview_(self.canvas)
        return self

    def nextPage(self):
        if self.canvas.max_page > self.canvas.page_number:
            self.canvas.page_number += 1
            self.canvas.display()
        if self.canvas.max_page == self.canvas.page_number:
            self.setTitle_('end')

    def windowDidResize_(self, sender):
        aw = self.contentView().frame().size.width
        ah = self.contentView().frame().size.height
        size = NSMakeSize(aw, ah)
        self.canvas.setFrameSize_(size)

class ComipoliMenu(NSMenu):
    def init(self):
        objc.super(ComipoliMenu, self).init()
        item_app  = NSMenuItem.new()
        self.addItem_(item_app)
        menu_app = NSMenu.new()
        item_app.setSubmenu_(menu_app)
        item_next = NSMenuItem.new()
        item_next.initWithTitle_action_keyEquivalent_('NextPage', 'nextPage:', 'n')
        item_next.setTarget_(self)
        menu_app.addItem_(item_next)
        item_quit = NSMenuItem.new()
        item_quit.initWithTitle_action_keyEquivalent_('Quit', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

    def nextPage_(self, sender):
        NSApp.keyWindow().nextPage()

NSApplication.sharedApplication()
window = ComipoliWindow.new()
window.makeKeyAndOrderFront_(window)
NSApp.setMainMenu_(ComipoliMenu.new())
NSApp.activateIgnoringOtherApps_(True)
NSApp.run()

command+N で次ページ

って、ほとんど変わらないヤン!
結局この作業を NSPDFImageRep は内部でやっているだけなんだろう。
軽くしたきゃ cbz に変換してくれで済ませよう。

そんなことより。
PyObjC ってこんなことまでできるのかい!
書いた筆者が驚いた、Xcode マジでいらね。