Write upside down using Unicode IPA characters

¡ǝpoɔıun oʇ sʞuɐɥʇ uʍop ǝpısdn ǝʇıɹʍ uɐɔ noʎ 'ʇou ɹo ʇı ǝʌǝılǝq.

This is nothing new (see [1] or [2]), but I wrote a small Python program for this to pipe my instant messaging through it. Needless to say that with KDE's Kopete I found yet another application that seems not to work with non-ASCII characters.

Anyway, here's the code:

#!/usr/bin/python
# -*- coding: utf8 -*-

"""
Simple program that flips the input on stdin for latin characters.

Support for diacritics through combining diacritical marks. Depends on proper
rendering though.

2008 Christoph Burgmer (uyhc@stud.uni-karlsruhe.de)

      ˙ǝɹɐʍʇɟos ǝɥʇ uı sƃuılɐǝp ɹǝɥʇo ɹo ǝsn ǝɥʇ ɹo ǝɹɐʍʇɟos ǝɥʇ ɥʇıʍ uoıʇɔǝuuoɔ
         uı ɹo ɟo ʇno 'ɯoɹɟ ƃuısıɹɐ 'ǝsıʍɹǝɥʇo ɹo ʇɹoʇ 'ʇɔɐɹʇuoɔ ɟo uoıʇɔɐ uɐ uı
  ɹǝɥʇǝɥʍ 'ʎʇılıqɐıl ɹǝɥʇo ɹo sǝƃɐɯɐp 'ɯıɐlɔ ʎuɐ ɹoɟ ǝlqɐıl ǝq sɹǝploɥ ʇɥƃıɹʎdoɔ
  ɹo sɹoɥʇnɐ ǝɥʇ llɐɥs ʇuǝʌǝ ou uı ˙ʇuǝɯǝƃuıɹɟuıuou puɐ ǝsodɹnd ɹɐlnɔıʇɹɐd ɐ ɹoɟ
ssǝuʇıɟ 'ʎʇılıqɐʇuɐɥɔɹǝɯ ɟo sǝıʇuɐɹɹɐʍ ǝɥʇ oʇ pǝʇıɯıl ʇou ʇnq ƃuıpnlɔuı 'pǝıldɯı
      ɹo ssǝɹdxǝ 'puıʞ ʎuɐ ɟo ʎʇuɐɹɹɐʍ ʇnoɥʇıʍ '"sı sɐ" pǝpıʌoɹd sı ǝɹɐʍʇɟos ǝɥʇ
                                 ˙ǝɹɐʍʇɟos ǝɥʇ ɟo suoıʇɹod lɐıʇuɐʇsqns ɹo sǝıdoɔ
  llɐ uı pǝpnlɔuı ǝq llɐɥs ǝɔıʇou uoıssıɯɹǝd sıɥʇ puɐ ǝɔıʇou ʇɥƃıɹʎdoɔ ǝʌoqɐ ǝɥʇ
                                            :suoıʇıpuoɔ ƃuıʍolloɟ ǝɥʇ oʇ ʇɔǝɾqns
 'os op oʇ pǝɥsıuɹnɟ sı ǝɹɐʍʇɟos ǝɥʇ ɯoɥʍ oʇ suosɹǝd ʇıɯɹǝd oʇ puɐ 'ǝɹɐʍʇɟos ǝɥʇ
ɟo sǝıdoɔ llǝs ɹo/puɐ 'ǝsuǝɔılqns 'ǝʇnqıɹʇsıp 'ɥsılqnd 'ǝƃɹǝɯ 'ʎɟıpoɯ 'ʎdoɔ 'ǝsn
    oʇ sʇɥƃıɹ ǝɥʇ uoıʇɐʇıɯıl ʇnoɥʇıʍ ƃuıpnlɔuı 'uoıʇɔıɹʇsǝɹ ʇnoɥʇıʍ ǝɹɐʍʇɟos ǝɥʇ
   uı lɐǝp oʇ '("ǝɹɐʍʇɟos" ǝɥʇ) sǝlıɟ uoıʇɐʇuǝɯnɔop pǝʇɐıɔossɐ puɐ ǝɹɐʍʇɟos sıɥʇ
 ɟo ʎdoɔ ɐ ƃuıuıɐʇqo uosɹǝd ʎuɐ oʇ 'ǝƃɹɐɥɔ ɟo ǝǝɹɟ 'pǝʇuɐɹƃ ʎqǝɹǝɥ sı uoıssıɯɹǝd

                                                            ǝsuǝɔıl ʇıɯ :ǝsuǝɔıl
"""

import sys
import locale
import unicodedata

BASE_LATIN_FLIP = u"ɐqɔpǝɟƃɥıɾʞlɯuodbɹsʇnʌʍxʎz"

OTHERS_FLIP = {'!': u'¡', '?': u'¿', '(': ')', '{': '}', ';': u'؛', '>': '<',
    '\'': ',','.': u'˙'}

# See:
# http://de.wikipedia.org/wiki/Unicode-Block_Kombinierende_diakritische_Zeichen
UNICODE_COMBINING_DIACRITICS = {u'̈': u'̤', u'̊': u'̥', u'́': u'̗', u'̀': u'̖',
    u'̇': u'̣', u'̃': u'̰', u'̄': u'̱', u'̂': u'̬', u'̆': u'̯', u'̌': u'̭',
    u'̑': u'̮', u'̍': u'̩'}

TRANSLITERATIONS = {u'ß': 'ss'}

# character lookup
charLookup = dict([(unichr(charOrd), BASE_LATIN_FLIP[i]) for i, charOrd \
    in enumerate(range(ord('a'), ord('z') + 1))])
charLookup.update(OTHERS_FLIP)
for char in charLookup.copy():
    charLookup[charLookup[char]] = char

# lookup for diacritical marks
diacriticsLookup = dict([(UNICODE_COMBINING_DIACRITICS[char], char) \
    for char in UNICODE_COMBINING_DIACRITICS])
diacriticsLookup.update(UNICODE_COMBINING_DIACRITICS)

_, default_encoding = locale.getdefaultlocale()

line = sys.stdin.readline().decode(default_encoding)

while line:
    line = line.strip("\n").lower()
    for char in TRANSLITERATIONS:
        line = line.replace(char, TRANSLITERATIONS[char])

    input = list(line)
    input.reverse()

    output = []
    for char in input:
        if char in charLookup:
            output.append(charLookup[char])
        else:
            charNormalised = unicodedata.normalize("NFD", char)

            for c in charNormalised:
                if c in charLookup:
                    charNormalised = charNormalised.replace(c, charLookup[c])
                elif c in diacriticsLookup:
                    charNormalised = charNormalised.replace(c,
                        diacriticsLookup[c])

            output.append(unicodedata.normalize("NFC", charNormalised))

    print "".join(output).encode(default_encoding)

    line = sys.stdin.readline().decode(default_encoding)

Update: It seem that Fonts currently only support a fixed set of common character-diacritics combinations. There is a discussion currently on the Unicode mailing list about a more general algorithm that could place diacritical marks, then allowing for a wider range of support.