Fabulous

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

Installation

Run the following commands:

sudo apt-get install python-imaging
sudo python setup.py install

Modules

fabulous.color

I implement support for standard 16-color color terminals.

class fabulous.color.ColorString(*items)

A colorized string-like object that gives correct length

If anyone knows a way to be able to make this behave like a string object without creating a bug minefield let me know:

>>> 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
as_utf8

A more readable way to say unicode(color).encode('utf8')

fabulous.color.complement(color)

Gives you the polar opposite of your color

This isn’t guaranteed to look good >_> (especially with brighter, higher intensity colors.)

>>> complement('red')
(0, 255, 76)
>>> complement((0, 100, 175))
(175, 101, 0)
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

>>> 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

>>> len(plain("hello ", bold("kitty")))
11
fabulous.color.section(title, bar=u'\u203e', strm=<open file '<stdout>', mode 'w' at 0x2aaaaaaf1150>)

Helper function for testing demo routines

fabulous.xterm256

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

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.

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
fabulous.xterm256.rgb_to_xterm(r, g, b)

Quantize RGB values to an xterm 256-color ID

This works by envisioning the RGB values for all 256 xterm colors as 3D euclidean space and brute-force searching for the nearest neighbor.

This is very slow. If you’re very lucky, compile_speedup() will replace this function automatically with routines in _xterm256.c.

fabulous.xterm256.xterm_to_rgb(xcolor)

Convert xterm Color ID to an RGB value

All 256 values are precalculated and stored in COLOR_TABLE

fabulous.logs

I provide utilities for making your logs look fabulous.

class fabulous.logs.TransientStreamHandler(strm=<open file '<stderr>', mode 'w' at 0x2aaaaaaf11e0>, 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

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.

Parameters: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

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.gotham

I implement functions to satisfy your darker side.

fabulous.gotham.lorem_gotham()

Cheesy 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