Rich Text Clipboard
I am currently trying to replicate some of my Mac workflow to iOS, and the biggest hurdle I've run into is code highliting for keynote presentations.
I tried copy and pasting from Safari (and chrome, Firefox , and opera) but it would simply paste as plain text into keynote — I was able to paste as rich text into a couple of other apps but none that would let me then get it into keynote as RTF.
On Mac I use pygments to output RTF and pipe it into
pbcopyand now have pygments up and running inside Pythonista successfully. However, I can't get it into the clipboard as RTF.
It looks like in 1.6 I could use the native Obj-C clipboard stuff, but this release isn't out yet — any suggestions for how I might accomplish this in 1.5?
I'm sorry, but there's no way to do this in 1.5.
@omz will 2.0 be a free upgrade (whenever apple approves it)? I just bought 1.5 today…
@dshafik That is the plan, yes. I'm not sure if there will be a cost increase for new users buying the app, but the upgrade should be free for existing users
So, 2.0 is now out, and I've continued working on this. I've tried multiple approaches and none of them work:
from objc_util import * from pygments import highlight from pygments.lexers import PhpLexer from pygments.formatters import RtfFormatter code = '<?php echo "hello world"; ?>' rtf = highlight(code, PhpLexer(), RtfFormatter()) fp = open('highlight.rtf', 'w+') fp.write(rtf) data = NSData.dataWithContentsOfFile_('highlight.rtf') c = ObjCClass('UIPasteboard') pasteBoard = c.generalPasteboard() pasteBoard.setValue_forPasteboardType_(data, 'public.rtf')
This one results in:
> pasteBoard.pasteboardTypes() <__NSCFArray: ( "public.rtf" )>
But I can't seem to paste it into say, Keynote (or a Word, etc.). Maybe this is an issue with Keynote , I guess.
Maybe I could generate an image to insert? Thoughts?
Generating an image shouldn't be hard. Try rendering with PIL ImageDraw.
You forgot to close your file pointer.
with open(...) as fp: fp.write(.....)
worked for me.
Also, it was not needed here, but I find it useful to use os.path.abspath, and/ or nsurl(filepath) when sending file names to objc.
@JonB I've not seen that syntax before, I assume creates a block (or some sort of scoped section) in which
fpis made available and then when the block is left it is automatically closed?
Anyhow, I do now see data in
databut I still can't get it to paste in. Were you able to do that?
The "with" syntax creates a context, which automatically does some clean up when the block ends. For file handles, this means it calls .close() (even if there is an exception, etc), so many people advocate for this style of file handling. You could have also just called fp.close().
I have not tried pasting anything. It is not clear to me that public.rtf is supported in UIpasteboard. Does pygments support an html formatter? You might try public.html.
I've experimented a little bit with this, and it seems to be harder than I thought. The iWork apps apparently copy rich text in some internal format that I can't figure out (com.apple.iWork.TSPNativeData), and neither RTF nor HTML appear to be supported.
Copying formatted text from Safari or Notes, and pasting it into Pages/Keynote doesn't work either (the formatting is discarded).
If an image is acceptable for you, this might work:
from objc_util import * from pygments import highlight from pygments.lexers import PhpLexer from pygments.formatters import RtfFormatter import ui import clipboard NSAttributedString = ObjCClass('NSAttributedString') code = '<?php echo "hello world"; ?>' rtf = highlight(code, PhpLexer(), RtfFormatter()) rtf_data = ns(bytearray(rtf)) attr_str = NSAttributedString.alloc().initWithData_options_documentAttributes_error_(rtf_data, None, None, None).autorelease() size = attr_str.size() with ui.ImageContext(size.width, size.height) as ctx: attr_str.drawAtPoint_(CGPoint(0.0, 0.0)) img = ctx.get_image() clipboard.set_image(img)
I'm using an NSAttributedString that is initialized from RTF data. Unfortunately, pygment's own method for rendering into images (GifImageFormatter etc.) don't work correctly in Pythonista because the method that it uses for finding system fonts fails on iOS.
I managed to get Pygments image drawing working by monkey patching the font stuff (ick!):
import clipboard import os import _font_cache from objc_util import * from pygments import highlight from pygments.lexers import PhpLexer from pygments.formatters import ImageFormatter from pygments.formatters.img import FontManager from pygments.styles import monokai monokai.MonokaiStyle.background_color = '#000000' def _get_nix_font_path(self, name, style): if style == 'bold' or style == 'italic': return _font_cache.get_font_path('%s %s' % (name, style.capitalize())) elif style == '': return _font_cache.get_font_path(name) else: return FontManager._get_nix_font_path = _get_nix_font_path ''' Replace Unicode newline chars with regular ones ''' code = clipboard.get().replace(unichr(8232), os.linesep) png = highlight(code, PhpLexer(startinline=True), ImageFormatter(style='monokai', font_name='Source Code Pro', font_size=60, line_numbers=False, line_pad=4)) with open('highlight.png', 'w+') as fp: fp.write(png) data = NSData.dataWithContentsOfFile_('highlight.png') c = ObjCClass('UIPasteboard') pasteBoard = c.generalPasteboard() pasteBoard.setData_forPasteboardType_(data, 'public.png') os.remove('highlight.png')
Resulting in an image like so:
I've logged extensive hours working on a similar objective for the past two week -- outputting formatted text to the clipboard for a custom dictionary wrapper I've built for StarDict data -- but so far I've had no success.
Is there anything in the works that would enable this functionality? It would seem to be such a basic feature.
This works for me, sending data to OneNote:
from objc_util import * from pygments import highlight from pygments.lexers import PhpLexer from pygments.formatters import RtfFormatter p=ObjCClass('UIPasteboard').generalPasteboard() code = b'<?php echo "hello world"; ?>' rtf = highlight(code, PhpLexer(), RtfFormatter()) rtf_data = ns(bytes(rtf,'ascii')) p.setData_forPasteboardType_(rtf_data,'public.RTF')
Jon - thanks, you set me on the right track -- the app I want to paste into (MarginNote) accepts rich text only if it's formatted as an Apple Web Archive type. Here's how I ultimately got it to work for Python 2.7, thanks to this guide on coercing HTML into an archive type:
from objc_util import * import os import base64 theDefinition = '<html><head></head><body>''<font size="5">' + '<b>Hello</b> <i>world!2</i>' + '</font></body></html>' theData = base64.encodestring(theDefinition) theData = str(theData) theArchive = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>WebMainResource</key> <dict> <key>WebResourceData</key> <data> """ + theData + """ </data> <key>WebResourceFrameName</key> <string></string> <key>WebResourceMIMEType</key> <string>text/html</string> <key>WebResourceTextEncodingName</key> <string>UTF-8</string> <key>WebResourceURL</key> <string>about:blank</string> </dict> </dict> </plist>""" thePasteBoard = ObjCClass('UIPasteboard') thePasteBoard = thePasteBoard.generalPasteboard() theType = "Apple Web Archive pasteboard type" thePasteBoard.setValue_forPasteboardType_(theArchive,theType)