Fabulous

Version:0.2
Copyright:Copyright (c) 2010 J.A. Roberts Tunney

Installation

Run the following commands:

sudo apt-get install gcc python-imaging python-setuptools
sudo easy_install -U fabulous

Command Line Interface

If you’re not a Python programmer, you can use fabulous on the command line to accomplish certain tasks. When printing images or fancy text, the image will automatically be resized to fit inside your terminal.

Print an image:

python -m fabulous.image lolcat.png

Save image to a file: (perhaps to include in your /etc/motd)

python -m fabulous.image lolcat.png >lolcat.ansi.txt

Print fancy text:

python -m fabulous.text hello kitty
python -m fabulous.text --skew=4 --shadow hello kitty
python -m fabulous.text --help

Modules

The following information is for Python programmers who wish to use Fabulous’ developer API.

fabulous.color

I allow you to print colorful and stylized text to your terminal. This module implements a high-level API for 4-bit and 8-bit colors.

Every wonder how this works? It’s actually really simple to do on your own so you might not even need fabulous:

>>> BOLD = '\x1b[1m'
>>> RED = '\x1b[31m'
>>> RESET = '\x1b[0m'

>>> BOLD + "~*~HELLO KITTY~*~" + RESET
'\x1b[22m~*~HELLO KITTY~*~\x1b[0m'

>>> BOLD + "~*~HELLO " + RED + "KITTY~*~" + RESET
'\x1b[22m~*~HELLO \x1b[31mKITTY~*~\x1b[0m'

If you do choose to use fabulous (and we hope you do!) here are some tips to make the most out of terminal colors.

Normally you’ll want to use the 4-bit palette (which consists of eight colors (black, red, green, yellow, blue, magenta, cyan, and white) because they work on all commonly used terminals and generally look good. These colors may change depending on your terminal theme and may also become brighter when used with bold text.

8-bit (256-color) gives you much more freedom to customize your program’s appearance. They work with the following terminals: gnome-terminal, kterm, xterm, and putty. These colors also do not change based on the terminal’s color theme. When distributing a program that uses these colors, you might want to take special care to choose colors that will be readable on both white and black backgrounds, unless of course you’re using “highlighted” color pairs which always seem to look good ;)

When printing colorized strings please remember that in some cases the user might be redirecting output to a log file. To avoid printing garbled data in these cases you can use sys.stdout.isatty() or sys.stderr.isatty() to make sure output is actually being sent to a terminal.

class fabulous.color.ColorString(*items)

Colorized string-like object parent class

I helps strings with color code pretend the ANSI escape codes aren’t there until the string is actually printed (like a unicode string.) This is important when you need to determine length or slice the string.

Examples:

>>> str(red("hello"))
'\x1b[31mhello\x1b[39m'
>>> len(red("hello"))
5
>>> len(str(red("hello")))
15
>>> str(bold(red("hello")))
'\x1b[1m\x1b[31mhello\x1b[39m\x1b[22m'
>>> len(bold(red("hello")))
5
>>> len(bold("hello ", red("world")))
11

Functions (well, technically classes) that inherit from this class accept a list of arguments which may consist of:

  • Strings (both raw and unicode)
  • ColorString objects
  • Any object that can be converted into a string
as_utf8
A more readable way to say unicode(color).encode('utf8')
class fabulous.color.ColorString256(color, *items)
Variant of ColorString for 256-bit color codes
class fabulous.color.bg256(color, *items)

8-bit Background Color

See ColorString256 for usage details

class fabulous.color.black(*items)
4-bit Black Foreground (ANSI Code 30, Reverse 39)
class fabulous.color.black_bg(*items)
4-bit Black Background Color

Strike-through Text (ANSI Code 5, Reverse 25)

Supported: kterm, xterm, Terminal.app, iTerm

Notes:

  • In Putty this looks like a dark highlight (flip)
class fabulous.color.blue(*items)

4-bit Blue Foreground (ANSI Code 34, Reverse 39)

Notes:

  • On some terminal themes with black backgrounds (in particular Terminal.app) this color may be difficult to read.
class fabulous.color.blue_bg(*items)
4-bit Blue Background Color
class fabulous.color.bold(*items)

Bold Text (ANSI Code 1, Reverse 22)

This may also brighten 4-bit colors.

Supported: gnome-terminal, kterm, xterm, Terminal.app, iTerm, putty

Notes:

  • iTerm makes bold look red by default.
fabulous.color.complement(color)

Gives you the polar opposite of your color

This isn’t guaranteed to look good, especially with brighter or high intensity colors.

For example:

>>> complement('red')
(0, 255, 76)
>>> complement((0, 100, 175))
(175, 101, 0)
class fabulous.color.complement256(color, *items)
Bold + 8-bit Foreground Color + Flip
class fabulous.color.cyan(*items)
4-bit Cyan or Turquoise Foreground (ANSI Code 36, Reverse 39)
class fabulous.color.cyan_bg(*items)
4-bit Cyan Background Color
fabulous.color.esc(*codes)

I wrap the ANSI escape code around a list of numbers.

For example:

>>> esc(22, 39)
'\x1b[22;39m'
class fabulous.color.faint(*items)

Makes Text Dimmer (ANSI Code 2, Reverse 22)

Supported: gnome-terminal, putty

class fabulous.color.fg256(color, *items)

8-bit Foreground Color

See ColorString256 for usage details

class fabulous.color.flip(*items)

Swap Foreground and Background Colors (ANSI Code 7, Reverse 27)

Supported: gnome-terminal, kterm, xterm, Terminal.app, iTerm, putty

class fabulous.color.green(*items)
4-bit Green Foreground (ANSI Code 32, Reverse 39)
class fabulous.color.green_bg(*items)
4-bit Green Background Color
class fabulous.color.highlight256(color, *items)
Bold + 8-bit Foreground Color + Flip
class fabulous.color.highlight_black(*items)
4-bit Bold + Black + Flip
class fabulous.color.highlight_blue(*items)
4-bit Bold + Blue + Flip
class fabulous.color.highlight_cyan(*items)
4-bit Bold + Cyan + Flip
class fabulous.color.highlight_green(*items)
4-bit Bold + Green + Flip
class fabulous.color.highlight_magenta(*items)
4-bit Bold + Magenta + Flip
class fabulous.color.highlight_red(*items)
4-bit Bold + Red + Flip
class fabulous.color.highlight_white(*items)
4-bit Bold + White + Flip
class fabulous.color.highlight_yellow(*items)
4-bit Bold + Yellow + Flip
class fabulous.color.italic(*items)

Italic Text (ANSI Code 3, Reverse 23)

Supported: none

class fabulous.color.magenta(*items)
4-bit Magenta or Pinkish Purple Foreground (ANSI Code 35, Reverse 39)
class fabulous.color.magenta_bg(*items)
4-bit Magenta Background Color
fabulous.color.main(args)
I provide a command-line interface for this module
fabulous.color.parse_color(color)

Turns a color into an (r, g, b) tuple

The color argument may be specified in many common formats. Here are some examples:

>>> parse_color('white')
(255, 255, 255)
>>> parse_color('#ff0000')
(255, 0, 0)
>>> parse_color('#f00')
(255, 0, 0)
>>> parse_color((255, 0, 0))
(255, 0, 0)
>>> import grapefruit
>>> parse_color(grapefruit.Color((0.0, 1.0, 0.0)))
(0, 255, 0)
class fabulous.color.plain(*items)

A passive wrapper that preserves proper length reporting

For example:

>>> len(plain("hello ", bold("kitty")))
11
class fabulous.color.red(*items)
4-bit Red Foreground (ANSI Code 31, Reverse 39)
class fabulous.color.red_bg(*items)
4-bit Red Background Color
fabulous.color.section(title, bar=u'u203e', strm=<open file '<stdout>', mode 'w' at 0x2b38e203a150>)
Helper function for testing demo routines
class fabulous.color.strike(*items)

Strike-through Text (ANSI Code 9, Reverse 29)

Supported: gnome-terminal

class fabulous.color.underline(*items)

Underlined Text (ANSI Code 4, Reverse 24)

Supported: gnome-terminal, kterm, xterm, Terminal.app, iTerm, putty

class fabulous.color.underline2(*items)

Double-Underlined Text (ANSI Code 21, Reverse 24)

Supported: None

Notes:

  • Putty will apply a single underline
class fabulous.color.white(*items)
4-bit White Foreground (ANSI Code 37, Reverse 39)
class fabulous.color.white_bg(*items)
4-bit White Background Color
class fabulous.color.yellow(*items)
4-bit Yellow Foreground (ANSI Code 33, Reverse 39)
class fabulous.color.yellow_bg(*items)
4-bit Yellow Background Color

fabulous.logs

I provide utilities for making your logs look fabulous.

class fabulous.logs.TransientStreamHandler(strm=<open file '<stderr>', mode 'w' at 0x2b38e203a1e0>, level=30)

Standard Python logging Handler for Transient Console Logging

Logging transiently means that verbose logging messages like DEBUG will only appear on the last line of your terminal for a short period of time and important messages like WARNING will scroll like normal text.

This allows you to log lots of messages without the important stuff getting drowned out.

This module integrates with the standard Python logging module.

fabulous.logs.basicConfig(level=30, transient_level=0)

Shortcut for setting up transient logging

I am a replica of logging.basicConfig which installs a transient logging handler to stderr.

fabulous.text

I let you print TrueType text to your terminal. The easiest way to get started with me is by running:

jart@compy:~$ python -m fabulous.text --help

To make things simple, Fabulous comes with my favorite serif, non-serif, and monospace fonts:

  • IndUni-H-Bold: Open Source Helvetica Bold clone (sans-serif)

    This is the real deal and not some cheap ripoff like Verdana. IndUni-H-Bold is the default because not only does it look great, but also renders perfectly. and is also used for the Fabulous logo. Commonly found on stret signs.

    This font is licensed under the GPL. If you’re developing proprietary software you might want to ask its author or a lawyer if Fabulous’ use of IndUni-H would be considered a “GPL Barrier.”

  • cmr10: Computer Modern (serif)

    Donald Knuth wrote 23,000 lines for the sole purpose of bestowing this jewel upon the world. This font is commonly seen in scholarly papers.

  • DejaVuSansMono: DejaVu Sans Mono (formerly Bitstream Vera Sans Mono)

    At point size 8, this is my favorite programming/terminal font.

For other fonts, I’ll try my best to figure out where your font files are stored. If I have trouble finding your font, try using an absolute path with the extension. You could also try putting the font in your ~/.fonts folder and running fc-cache -fv ~/.fonts.

exception fabulous.text.FontNotFound

I get raised when the font-searching hueristics fail

This class extends the standard ValueError exception so you don’t have to import me if you don’t want to.

class fabulous.text.Text(text, fsize=20, color='#0099ff', shadow=False, skew=None, font='IndUni-H-Bold')

Renders TrueType Text to Terminal

I’m a sub-class of fabulous.image.Image. My job is limited to simply getting things ready. I do this by:

  • Turning your text into an RGB-Alpha bitmap image using PIL
  • Applying way cool effects (if you choose to enable them)

For example:

>>> assert Text("Fabulous", shadow=True, skew=5)

>>> txt = Text("lorem ipsum", font="IndUni-H-Bold")
>>> len(str(txt)) > 0
True
>>> txt = Text("lorem ipsum", font="cmr10")
>>> len(str(txt)) > 0
True
>>> txt = Text("lorem ipsum", font="DejaVuSansMono")
>>> len(str(txt)) > 0
True
Parameters:
  • text – The text you want to display as a string.
  • fsize – The font size in points. This obviously end up looking much larger because in fabulous a single character is treated as one horizontal pixel and two vertical pixels.
  • color – The color (specified as you would in HTML/CSS) of your text. For example Red could be specified as follows: red, #00F or #0000FF.
  • shadow – If true, render a simple drop-shadow beneath text. The Fabulous logo uses this feature.
  • skew – Skew size in pixels. This applies an affine transform to shift the top-most pixels to the right. The Fabulous logo uses a five pixel skew.
  • font – The TrueType font you want. If this is not an absolute path, Fabulous will search for your font by globbing the specified name in various directories.
fabulous.text.get_font_files()

Returns a list of all font files we could find

Returned as a list of dir/files tuples:

get_font_files() -> [('/some/dir', ['font1.ttf', ...]), ...]

For example:

>>> fabfonts = os.path.join(os.path.dirname(__file__), 'fonts')
>>> 'IndUni-H-Bold.ttf' in get_font_files()[fabfontdir]
True
>>> 'DejaVuSansMono.ttf' in get_font_files()[fabfontdir]
True
>>> 'cmr10.ttf' in get_font_files()[fabfontdir]
True

>>> assert len(get_font_files()) > 0
>>> for dirname, filename in get_font_files():
...     assert os.path.exists(os.path.join(dirname, filename))
...
fabulous.text.main(args)
I provide a command-line interface for this module
fabulous.text.resolve_font(name)

Sloppy way to turn font names into absolute filenames

This isn’t intended to be a proper font lookup tool but rather a dirty tool to not have to specify the absolute filename every time.

For example:

>>> path = resolve_font('IndUni-H-Bold')

>>> fontdir = os.path.join(os.path.dirname(__file__), 'fonts')
>>> indunih_path = os.path.join(fontdir, 'IndUni-H-Bold.ttf')
>>> assert path == indunih_path

This isn’t case-sensitive:

>>> assert resolve_font('induni-h') == indunih_path

Raises FontNotFound on failure:

>>> resolve_font('blahahaha')
Traceback (most recent call last):
...
FontNotFound: Can't find 'blahahaha' :'(  Try adding it to ~/.fonts

fabulous.image

Module for printing images to the terminal.

class fabulous.image.Image(path, width=None)

Printing image files to a terminal

I use PIL to turn your image file into a bitmap, resize it so it’ll fit inside your terminal, and implement methods so I can behave like a string or iterable.

When resizing, I’ll assume that a single character on the terminal display is one pixel wide and two pixels tall. For most fonts this is the best way to preserve the aspect ratio of your image.

All colors are are quantized by fabulous.xterm256 to the 256 colors supported by modern terminals. When quantizing semi-transparant pixels (common in text or PNG files) I’ll ask TerminalInfo for the background color I should use to solidify the color. Fully transparent pixels will be rendered as a blank space without color so we don’t need to mix in a background color.

I also put a lot of work into optimizing the output line-by-line so it needs as few ANSI escape sequences as possible. If your terminal is kinda slow, you’re gonna want to buy me a drink ;) You can use DebugImage to visualize these optimizations.

The generated output will only include spaces with different background colors. In the future routines will be provided to overlay text on top of these images.

convert()
Yields xterm color codes for each pixel in image
reduce(colors)

Converts color codes into optimized text

This optimizer works by merging adjacent colors so we don’t have to repeat the same escape codes for each pixel. There is no loss of information.

Parameter:colors – Iterable yielding an xterm color code for each

pixel, None to indicate a transparent pixel, or 'EOL' to indicate th end of a line.

Returns:Yields lines of optimized text.
resize(width=None)

Resizes image to fit inside terminal

Called by the constructor automatically.

size
Returns size of image
fabulous.image.main(args)
I provide a command-line interface for this module

fabulous.utils

This module provides tools for probing terminal settings and configuring Fabulous.

class fabulous.utils.TerminalInfo(bgcolor='black')

Quick and easy access to some terminal information

I’ll tell you the terminal width/height and it’s background color.

You don’t need to use me directly. Just access the global term instance:

>>> assert term.width > 0
>>> assert term.height > 0

It’s important to know the background color when rendering PNG images with semi-transparency. Because there’s no way to detect this, black will be the default:

>>> term.bgcolor
(0.0, 0.0, 0.0, 1.0)
>>> import grapefruit
>>> isinstance(term.bgcolor, grapefruit.Color)
True

If you use a white terminal, you’ll need to manually change this:

>>> term.bgcolor = 'white'
>>> term.bgcolor
(1.0, 1.0, 1.0, 1.0)
>>> term.bgcolor = grapefruit.Color.NewFromRgb(0.0, 0.0, 0.0, 1.0)
>>> term.bgcolor
(0.0, 0.0, 0.0, 1.0)
dimensions

Returns terminal dimensions

Don’t save this information for long periods of time because the user might resize their terminal.

Returns:Returns (width, height). If there’s no terminal to be found, we’ll just return (79, 40).
height
Returns height of terminal in lines
termfd

Returns file descriptor number of terminal

This will look at all three standard i/o file descriptors and return whichever one is actually a TTY in case you’re redirecting i/o through pipes.

width
Returns width of terminal in characters
fabulous.utils.memoize(function)

A very simple memoize decorator to optimize pure-ish functions

Don’t use this unless you’ve examined the code and see the potential risks.

fabulous.utils.pil_check()

Check for PIL library, printing friendly error if not found

We need PIL for the fabulous.text and fabulous.image modules to work. Because PIL can be very tricky to install, it’s not listed in the setup.py requirements list.

Not everyone is going to have PIL installed so it’s best that we offer as much help as possible so they don’t have to suffer like I have in the past :’(

fabulous.xterm256

Implements Support for the 256 colors supported by xterm as well as quantizing 24-bit RGB color to xterm color ids.

This module does not provide a high level API but rather a means of turning RGB tuples into an ANSI color code.

Color quantization is very very slow so when this module is loaded, it’ll attempt to automatically compile a speedup module using gcc. A logging message will be emitted if it fails and we’ll fallback on the Python code.

When this module is loaded it’ll automatically try to load some optimized C code unless the environment variable FABULOUS_NO_SPEEDUP is set.

fabulous.xterm256.compile_speedup()

Tries to compile/link the C version of this module

Like it really makes a huge difference. With a little bit of luck this should just work for you.

You need:

  • Python >= 2.5 for ctypes library
  • gcc (sudo apt-get install gcc)

fabulous.gotham

I implement functions to satisfy your darker side.

fabulous.gotham.lorem_gotham()

Gothic Poetry Generator

Uses Python generators to yield eternal angst.

When you need to generate random verbiage to test your code or typographic design, let’s face it... Lorem Ipsum and “the quick brown fox” are old and boring!

What you need is something with flavor, the kind of thing a depressed teenager with a lot of black makeup would write.

fabulous.gotham.lorem_gotham_title()
Names your poem
fabulous.gotham.main(args)
I provide a command-line interface for this module

fabulous.rotating_cube

Completely pointless terminal renderer of rotating cube

Uses a faux 2D rendering technique to create what appears to be a wireframe 3d cube.

This doesn’t use the curses library, but rather prints entire frames sized to fill the entire terminal display.

class fabulous.rotating_cube.Frame

Canvas object for drawing a frame to be printed

line(x0, y0, x1, y1, c='*')

Draws a line

Who would have thought this would be so complicated? Thanks again Wikipedia <3

fabulous.rotating_cube.ellipse_point(degrees, width, height)
I hate math so much :’(
fabulous.rotating_cube.rotating_cube(degree_change=3, frame_rate=10)

Rotating cube program

How it works:

  1. Create two imaginary ellipses
  2. Sized to fit in the top third and bottom third of screen
  3. Create four imaginary points on each ellipse
  4. Make those points the top and bottom corners of your cube
  5. Connect the lines and render
  6. Rotate the points on the ellipses and repeat

fabulous.debug

class fabulous.debug.DebugImage(path, width=None)
Visualize optimization techniques used by Image
fabulous.debug.main(args)
I provide a command-line interface for this module