Tuesday, December 23, 2008

lab 91 - using freetype


lab 91 - using freetype


While fiddling with Charon's fonts and wondering what work would be involved to replace the whole set I decided to take a quick look at the freetype module. This lab documents some of my progress.

A recent post to the acme-sac mail-list pointed me to the DejaVu fonts. They are derived from Bitstream Vera Fonts but with more characters. It includes various styles: Sans, Serif, Italic, Oblique, Bold, and Mono, making it a good choice for Charon. At first I considered converting the whole set over to Inferno format.

There is a program to convert TrueType fonts to the inferno format. But the program is designed to run on Plan 9 and I don't have a ready Plan 9 environment anymore. So the effort of setting up an environment, compiling and fixing problems I know exist in the conversion tool, creating font files for all the styles, and in a variety of sizes, and I was ready to look for an easier solution.

The Freetype library is compiled into the inferno-os emulator and exports a builtin limbo interface. There are no programs in inferno-os that use Freetype. And there is no documentation describing the limbo interface. There have, however, been a few posts to the inferno-list describing its use. Also, the Freetype documentation from its source website is good. The tutorial basically describes what needs to be done within inferno to use the freetype module.

The first example from inferno-list, testfreetype.b, shows the use of the library for rotation and scaling.

% cd lab 91
% limbo testfreetype.b
% testfreetype fonts/DejaVuSans.ttf 'Hello World'

In this screenshot, the Inferno logo image is the background, and the word 'freetype' is scaled and rotated above it, with some transparency.


The example dbft2.b is a simplification of the above that demonstrates writing a string to a window. I took the dbft2.b code an tried to adapt it to the frame module, a port a libframe from Plan 9, and used by Acme in inferno-os,

First of my own demos is an application called term that accepts keyboard input and uses frame to display the entered text inside a window. This uses inferno fonts. Using frame requires some setup code,

 framem = load Framem Framem->PATH;
 # setup a window client
 win := wmclient->window(ctxt, "Term", Wmclient->Appl);

 font = Font.open(display, "/fonts/lucidasans/unicode.8.font");
 textcols = array[NCOL] of ref Draw->Image;
 textcols[BACK] = display.black;
 textcols[HIGH] = display.color(Draw->Darkyellow);
 textcols[BORD] = display.color(Draw->Yellowgreen);
 textcols[TEXT] = display.color(Draw->Medgreen);
 textcols[HTEXT] = display.black;
 frame = framem->newframe();
 win.image.draw(win.image.r, textcols[BACK], nil, ZP);
 framem->frclear(frame, 0);
 framem->frinit(frame, win.image.r,  font, win.image, textcols);

Its not documented in Inferno, but see the Plan 9 manual page.

On input from the keyboard we append it to a buffer and pass the buffer to frame:

 c := <-w.ctxt.kbd =>
  buf[len buf] = c;
  framem->frinsert(frame, buf[len buf - 1:], 1, frame.p0);

Once I got that baseline working, the next program is frame2.b and term2.b that uses freetype. In the init function I load the freetype module and load a new face.

 freetype = load Freetype Freetype->PATH;
 face = freetype->newface("./fonts/DejaVuSerif-BoldItalic.ttf", 0);
 face.setcharsize(20<<6, 72, 72);
 glyphsimg = ctxt.display.newimage(Rect((0,0), (20,20)), Draw->GREY8, 0, Draw->Black);

I rewrote the three functions used by frame to display strings, stringx, charwidth, and strwidth. Stringx does the work of loading the glyph and drawing it.

stringx(d : ref Image, p : Point, f : ref Font, s : string, c : ref Image)
 origin := Point(p.x<<6, (p.y+face.ascent)<<6);
 for (i := 0; i < len s; i++)
  g := face.loadglyph(s[i]);
  if (g == nil){
   sys->print("No glyph for char [%c]\n", s[i]);
  drawpt := Point((origin.x>>6)+g.left, (origin.y>>6)-g.top);
  r := Rect((0,0), (g.width, g.height));
  r = r.addpt(drawpt);
  glyphsimg.writepixels(Rect((0,0), (g.width, g.height)), g.bitmap);
  d.draw(r, c, glyphsimg, (0,0));
  origin.x += g.advance.x;

In this screenshot, the term application is running inside inferno-os, with some typed text. (Note, there is a error in my render of lowercase 'f', the top of the 'f' has been chopped off.)


Acme uses frame so we can quickly experiment with using a TTF file for the Acme font. The above three functions are in another module in the acme source called graph.b. I've included a graph2.b file in this lab that can be bound over the existing graph.dis and will load a hardcoded TTF file for the font. Below is a screenshot of acme running the DejaVuSans.ttf font.


Now back to my original aim: changing the charon fonts. I created a module that defined a new Font ADT to replace the one defined in the draw module. I changed the interface slightly, to include the freetype face and added the stringx function.

All the code for drawing text in charon is in layout.b. In this labs code I've included a replacement that uses the freetype Font adt. I changed all calls to Image.text() to call a local function that itself calls Font.stringx(). Layout.b locally defines a Fontinfo that specifies all the font files and sizes. I modified it to point to all the DejuVu fonts with sizes as appropriate. To run this yourself inside inferno-os you'll need to extract the DejuVu ttf files directly under /fonts, then,

% cd /appl/charon
% bind -bc '/n/local/inferno-lab/91' .
% limbo layout.b
% limbo ftfont.b
% bind layout.dis /dis/charon/layout.dis
% charon&

And here is an screenshot of the results.


I haven't looked at converting Tk.



1 comment:

arvindh tamilmani said...

thanks for this lab caerwyn.

i checked whether indic languages are rendered correctly using graph2.b.
it turns out glyph substitution is not supported by freetype and is implemented elsewhere.