Some simple Services

All the cool kids are writing workflows for Editorial, but since I don’t have Editorial or an iPad to run it on, I’m writing goofy Services to amuse myself.

I started with one that strikes through the selected characters. There are web pages with fields that’ll do this for you (like these two), but I wanted to

  1. Avoid the copying and pasting that’s necessary when you use these pages.
  2. Learn something about Unicode combining characters.

Strikethroughs are easy in HTML. You just surround the text with <s> and </s> so that <s>this text</s> displays as this text. But you can’t do HTML in places like Twitter; there, you have to rely on Unicode.

The key Unicode character is U+0336, the COMBINING LONG STROKE OVERLAY. Put this after every regular character you want struck through and voila! S̶t̶r̶i̶k̶e̶t̶h̶r̶o̶u̶g̶h̶ ̶m̶a̶g̶i̶c̶.

(You may find the magic a little less magical than you’d like. Unlike the HTML, which makes a nice clean stroke through all the characters, Unicode U+0336 adjusts its position according to the height of the character it’s striking through, leading to a sort of wavy line. Also, not every font supports U+0336, so you may find yourself forced to use a font you don’t like. We must suffer for our art.)

Here’s an Automator screenshot of the Service I made to strike through the selected text:

Strikethrough Service

It receives text in any application and replaces it with the output. The transformation is done by this Python script:

python:
1:  import sys
2:  
3:  unstruck = sys.stdin.read()
4:  struck = u'\u0336'.join(unstruck)
5:  sys.stdout.write(struck.encode('utf-8')[:-1])

When debugging this script, I noticed something weird about Automator workflows. The standard input that the first step feeds to this script isn’t the selected text—it’s the selected text with a linefeed added to the end. That’s why Line 4 doesn’t put U+0336 after the last character of unstruck and why Line 5 doesn’t write the last character of struck: in each case, that last character is a linefeed that wasn’t in the original selection.1 I have no idea why this happens, but it seems like a bug to me.

Update 9/18/13
The original version of the script imported the subprocess library, which you can still see in the screenshot. This was a a holdover from an earlier version the script that used pbcopy to get the clipboard. Thanks to James Cash for pointing this out.

To use this Service, I select the text I want struck through and choose “Strikethrough selection” from the Services submenu. Or, more quickly, I use the ⌃⌥⇧⌘- keyboard shortcut I’ve mapped to that menu item.

Keyboard shortcuts for Services

That would be a lot of modifier keys to hold down at once, but I’ve mapped my keyboard to get that modifier bundle when I press the Caps Lock key, so it’s actually easier to press than a lot of keyboard combinations.

If you want, you could modify the script to use another strikethrough character. Unicode U+0338, COMBINING LONG SOLIDUS OVERLAY, would give you s̸o̸m̸e̸t̸h̸i̸n̸g̸ ̸l̸i̸k̸e̸ ̸t̸h̸i̸s̸, which I find a little too busy but you might like.

After making the strikethrough Service, I decided to add a couple of others. A few years ago, I wrote a TextExpander snippet that used Unicode phonetic symbols to make text that looked uʍop ǝpısdn. Turning it into a Service was pretty simple. The structure of this Service is the same as that above but with this Python script:

python:
 1:  # -*- coding: UTF-8 -*-
 2:  
 3:  from sys import stdin, stdout
 4:  
 5:  pchars = u"abcdefghijklmnopqrstuvwxyz,.?!'()[]{}"
 6:  fchars = u"ɐqɔpǝɟƃɥıɾʞlɯuodbɹsʇnʌʍxʎz'˙¿¡,)(][}{"
 7:  flipper = dict(zip(pchars, fchars))
 8:  
 9:  def flip(s):
10:    charList = [ flipper.get(x, x) for x in s.lower() ]
11:    charList.reverse()
12:    return "".join(charList)
13:  
14:  stdout.write(flip(stdin.read()[:-1]).encode('utf8'))

The [:-1] in Line 14 works around the standard input bug mentioned above.

I also changed a ROT13 snippet into a Service. The structure is the same as before but with guvf Clguba fpevcg:

python:
 1:  from string import maketrans
 2:  from sys import stdin, stdout
 3:  
 4:  alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
 5:  rot13 = 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'
 6:  r13table = maketrans(alpha, rot13)
 7:  
 8:  orig = stdin.read()[:-1]
 9:  stdout.write(orig.translate(r13table))

The main advantage of using a Service instead of a snippet is speed. With the snippets, I had to copy the text to be transformed onto the clipboard and then type the snippet abbreviation. With a Service, I just select the text and hit the appropriate keyboard combination. A secondary advantage is that using the Service doesn’t clutter up my clipboard history.

I have a feeling these would be easy to convert into Editorial workflows, but workflow probably wouldn’t be the right word for them.


  1. As it happens, Automator seems to strip the last character from the output if it’s a linefeed, so I get the same result whether the [:-1] is in Line 5 or not. I decided to keep it there to remind me of this weird behavior.