Issue 4-1, January 6, 1999

Be Engineering Insights: BFont Improvements in R4

By Pierre Raynaud-Richard

Each release of the BeOS includes some new APIs (such as the Media Kit in R4), along with a bunch of minor to major API "improvements" (or so we like to think :-). You'll notice quite a few "extras" in the R4 BFont class, many of them inspired by feature requests from developers (thanks for taking the time to send them).

New Support for Font Faces

The SetFace(uint16 face) and uint16 Face() APIs have been around for awhile, but they haven't really been supported. We initially chose to define a font by its family name (a free-form string, universally used) and its style name (another free-form string, far less common). The main advantage of this was that such a name could be extended without limits. But this turned into a disadvantage, as the lack of a standard implied that the style name and the family name could not be set as two orthogonal options, because there was no easy way to know what styles would be found in the given font's family.

The font face provides such an orthogonal choice by defining the set of possibilities once for all, based on the OS/2 TrueType table standard: italic, underscore, negative, outlined, strikeout, bold, and regular. The first six can be freely combined (they are defined as one bit in a bit mask). Regular is reserved for the standard appearance of the font and can't be mixed with any of the other ones.

For completeness, SetFamilyAndStyle() has been extended into SetFamilyAndFace(font_family family, uint16 face). Finally, setting a face operates by the closest match. If a perfect match isn't available, there will be no emulation of the missing attributes. So for example, if you ask for Bold StrikeOut but the proper font is not installed, then you may just get Bold. The system will not strike it out for you. That may be improved in future releases.

New Support for Postscript™ Type 1

In R4, we enabled support for Postscript Type 1 fonts. So you may now want to identify the file format used by a given font family: font_file_format FileFormat(), which returns B_TRUETYPE_WINDOWS or B_POSTSCRIPT_TYPE1_WINDOWS. Every time the font file manager updates your list of installed font files, it sorts them by family, creating a list of available styles per family. You can't do this across different file formats, though. So if, for example, you have Baskerville Regular and Baskerville Bold in TrueType and Baskerville Italic in Postscript, you'll see two families: Baskerville(TT) with two styles and Baskerville(PS) with one style. This is required, as different sets of font files sharing the same family name don't always share the same exact design and metric.

Also, even if Postscript Type 1 fonts are transparently supported by the BeOS font system, we don't recommend using them as regularly as TrueType fonts. First, because of their limited hinting, they usually render poorly at small sizes, so it's best to avoid them for screen display. Second, our TrueType engine's performance is still significantly better and remains the best choice when font rendering speed is critical. Last, because of their limitation in character encoding, you may see problems with non-ASCII 7-bit or special characters. The current compatibility with UTF8 is limited, and it's not clear what future improvements can be made, if any at all.

New APIs to Identify Glyph Availability

Many people rightly complained that there was no easy way—if any way at all—to know which glyphs were available in a given font. The system would transparently return the expected glyph or the default box, with no complaints. As part of our increased support for localized/international apps, we created the unicode_block class. This object is basically a 128-bit bitfield, capable of handling basic logic operations (AND and OR), tests (EQUAL and NOT EQUAL), and the more sophisticated "Includes".

The Unicode range has been cut into 70 blocks (see list in UnicodeBlockObjects.h), and new blocks may be added in the future. For example, you'll find the basic ASCII 7-bit block, B_BASIC_LATIN_BLOCK (Unicode values 0x0000 to 0x007F), and more exotic ones like B_TIBETAN_BLOCK (0x0F00 to 0x0FBF). Blocks() returns a mask of all unicode_blocks that are even partially available in a given font. So for example, a simple test like:

if (aFont->Blocks().Includes(B_TYPICAL_JAPANESE_BLOCKS))

where B_TYPICAL_JAPANESE_BLOCKS is a bitmask (yet to be defined) that contains all the regular Japanese unicode_blocks, indicates that aFont should include most common Japanese characters (though specific glyphs may be missing). It's what a text editor needs to switch fonts dynamically when the user switches between different input methods (Hiroshi will give more details about this in a future article).

For people who want to go even further and know if a specific glyph is available, GetHasGlyphs() is what you need.

Note: The APIs discussed here are not very well supported for PS Type 1 fonts. Blocks() may return approximated results, and GetHasGlyphs() is not currently supported at all.

Getting the Shape of a Glyph

The new GetGlyphShapes() function returns the shape(s) of one or more glyph(s), described using Bézier curves and lines. The shapes have their origin at (0.0, 0.0) to allow easy linear transformation. To draw them at the same position that a DrawString() would, you need to offset them, using the following formula:

OffsetBy(floor(pen0.x+0.5), floor(pen0.y+0.5)-1.0);

also written as:

OffsetBy(floor(pen0.x+0.5), floor(pen0.y-0.5));

floor(+0.5) is the rounding rule used by the app_server. The offset -1.0 on the Y axis is required to compensate for a historical mistake, now tied to the API forever (for compatibility reasons): the bitmap images generated by the font engine use an origin in (0.0, -1.0), one line over the normal baseline of the font. So all font drawing is done one line higher than expected. Since all texts are placed so that they look good, no one has noticed or complained about this during the last 18 months. All font-related APIs take that error into account, with the exception of this one and the font-wide bounding box (see the next paragraph), which both describe linearly scalable objects, and must originate at (0.0, 0.0).

New APIs to Get Font and Glyph Bounding Boxes

How do we know where a DrawString() is going to draw and what area we should erase to get a correct refresh?

Until R4, the only way to figure this out was by using approximated rules based on the escapements of the glyphs; the ascent, descent, and leading of the font, along with empirical safety margins. Such solutions had two serious flaws. First, the ascent, descent, and leading of the font give detailed measurements of how tall a text line should be, but don't guarantee that the font designer didn't intentionally create glyphs so tall that they will infringe outside their text line. Second, the escapement does give a measurement of how much the pen position will move after drawing the glyph, but doesn't guarantee anything about where the glyph is really going to be drawn.

That's why R4 introduces new APIs, to allow efficient and accurate processing of those bounding boxes. The first function is global to a font: BRect BoundingBox(). If you draw all the glyphs of a given font and size, one on another, you get a big blob. BoundingBox() is the bounding box of that blob. It's a floating-point scalable rectangle, the value returned corresponding to point size 1.0. By scaling it, you can get good approximations of the global font's printing bounding box at print time.

Sadly, when you draw on screen, you don't get the x4, x8, or x16 resolution increase of a printer before rounding to an integer. Also at small sizes, hinting may try to reduce readability problems by distorting glyphs. As a result, calculating the screen- approximated bounding box for a given point size, based on the font's real bounding box, is a non-trivial task. This gets even worse when you consider that around 20% of the font files out there provide incorrect bounding box information for the left side (don't ask me why!). As a result of extensive tests that I ran on hundreds of common and less common fonts, I propose the following formulas:

Notations and comments:

  • fBBox is the BoundingBox() BRect. Please note that the Y axis is oriented from bottom to top, as defined in font files. It must be inverted to be used in a standard screen or printer coordinate system.

  • pSize is the selected point size.

  • sBBox is the estimated screen bounding box. All borders are included.

Regular formulas:

sBBox.left = floor(fBBox.left * pSize)-1;
sBBox.top = floor(-fBBox.bottom * pSize)-1;
sBBox.right = floor(fBBox.right * pSize)+2;
sBBox.bottom = floor(-fBBox.top * pSize)+2;

These formulas work fine with the 80% of font files that include accurate bounding box info. Fonts provided by operating system vendors seem to be always compliant.

Limited correction:

sBBox.right = floor(fBBox.right * pSize * 1.333)+2;

This formula solves the problem with 90% of the "bad fonts."

Advanced correction:

sBBox.right = floor(fBBox.right * pSize * 2.0)+2;

This works with all the fonts I tested, except one that was also wrong on the Y axis (I wonder why they cared about setting the bounding box information at all...). That global bounding box allows you to calculate an estimate of the drawing area used by DrawString().

Notations and comments:

  • total_escape: sum of the escapements of all the glyphs of the string, from the first one (included) to the last one (excluded).

  • drawRect: resulting bounding box for the DrawString().

  • pen0: pen position when calling DrawString().

  • As sBBox is a scaled version of the theoretical font bounding box, we have to take the -1.0 correction for bitmap fonts into account ourselves (that's the only other case with GetGlyphShapes()).

Formulas:

drawRect = sBBox.left;
drawRect.right += total_escape;
drawRect.OffsetBy(floor(pen0.x+0.5), floor(pen0.y-0.5));

The validity of drawRect will be as good as that of sBBox (as discussed earlier). Most of the time, it won't be the best value, but will be many pixels wider and taller. Its advantage, though, is that it can be processed completely on the client side by caching only one rectangle by font (be careful, BoundingBox() is not cached, so it will make a synchronous call to the server).

For applications where performance isn't critical, other APIs allow you to calculate the minimal bounding box of a glyph or a string, either on the screen (the rectangle is rounded to an integer and takes all distortions into account) or as printed (original floating-point values). You can switch between the two modes by using the font_metric_mode parameter (B_SCREEN_METRIC or B_PRINTING_METRIC).

The function exists in different flavors: bounding boxes for individual glyphs (GetBoundingBoxesAsGlyphs()) or whole words (GetBoundingBoxesAsString() for a single string, or GetBoundingBoxesForStrings() to process many strings in one call). To convert those rectangles to screen coordinates, you need to apply the regular offset formula (no -1.0 needed):

drawRect.OffsetBy(floor(pen0.x+0.5), floor(pen0.y+0.5));

Results are demonstrated in the new fontDemo application shipped with R4, by enabling the Bounding boxes option.

A More Complete API to Access Font Escapements

Some developers noticed an inconsistency in our GetEscapements() API. There was no way to get the real 2D escapement when using a rotated font. Worse than that, the 1D value returned in that case was wrong. One work around was to get the non-rotated escapement and apply the rotation yourself, but then rounding errors were unavoidable. As we want perfectly accurate positions on screen to be possible, we created a new version of GetEscapements() that returns one BPoint per glyph.

Even beyond that, a few spacing modes (for example using dynamic kerning) modify the real drawing origin of glyphs without changing their escapements. For example, the width reserved for a glyph stays the same, but it will be drawn a little more to the left to improve the overall appearance of the string. So the most advanced version of GetEscapements() returns two BPoints per glyph, one for the escapement, the other one for the small drawing origin offset, if any.

A Few Facts That May Interest Some of You...

The BFont object knows about four different spacing modes. Those were created to allow optimal font display on screen in different cases. B_CHAR_SPACING has been improved in R4 to reduce collisions between characters at small point sizes. B_STRING_SPACING was not behaving very well in Release 3, so it was almost completely rewritten for R4. The new dynamic kerning engine is much smarter than the previous one and now protects spaces with great care. That makes it a clear winner if you want nice WYSIWYG text display only. It can also be used for text editing, but that requires special, non-trivial processing to reduce jittering. Contact me directly if you want more details...

Awhile ago, when we failed to meet our quality expectations with our current fonts and B&W rasterizer, we chose to keep anti-aliasing always enabled. So B_DISABLE_ANTIALIASING was added for applications with special needs. Recently, we experimented with an automatic way of enabling/disabling anti-aliasing based on the point size and font-specific information. The results were not satisfying, so the option isn't in R4. The new flag B_FORCE_ANTIALIASING, added for completeness, was not removed but just disabled. More news in future releases.

B_TRUNCATE_SMART has not been implemented yet... For now, it still defaults to B_TRUNCATE_MIDDLE.

BFont::IsFullAndHalfFixed() isn't a spelling mistake. Since Kanji characters are much wider than most Roman characters, it's not reasonable to create a fixed-width font with both Kanji and Roman glyphs. The solution lies in designing Roman glyphs half the width of Kanji. Sadly, we didn't have time to implement it for R4, and even less to add proper support in system applications. But these things will come with time...


Developers' Workshop: Getting into (B)Shape

By Michael Morrissey

Long-time reader and BeOS fanatic Tim Dolan recently wrote to me regarding the new BShape and BShapeIterator classes. “From the header file, I can see that the BShape class is just what I need to draw smooth curves,” Tim wrote. “But I need help getting started. Can you provide some no-frills sample code which covers the basics?” Here you go, Tim:

ftp://ftp.be.com/pub/samples/interface_kit/Iterview.zip

This sample code shows how to get the outline of a text string as a BShape and manipulate the control points of the BShape through BShapeIterator, in order to distort the text.

Before you dig into the sample code, take a minute to examine the Shape.h header file. The BShape class has four central functions, which are used to describe a curve or path:

The BShapeIterator class has four corresponding functions: IterateMoveTo(), IterateLineTo(), IterateBezierTo(), and IterateClose(). An additional function, Iterate(), binds them all together by stepping through each point of the given BShape, calling the appropriate Iterate...To() function, and passing it a pointer to the BPoint or BPoints which describe that segment of the path. For this to be useful, you need to derive a class from the BShapeIterator, replacing the Iterate...To() functions with functions that do something interesting, such as displaying the BPoints or relocating them.

In the sample code, we start by creating the IterView class, which inherits from both the BShapeIterator class and the BView class. We'll override the Iterate...To() functions and have each one draw the control points in the view. We'll also keep lists of the control points (one list for each of the glyphs in the text string), which will allow the MouseDown() and MouseMoved() functions to manipulate the BShape.

We start by obtaining the outlines for the glyphs of our text in the InitializeShapes() routine:

void
IterView::InitializeShapes()
{
  BFont font;
  GetFont(&font);
  font.SetSize(fontSize);

  delta.nonspace = 0.0;
  delta.space = 0;

  font.GetGlyphShapes(text, textlen, shapes);
  font.GetEscapements(text, textlen, &delta, esc,
    esc+textlen);
}

We also get the escapement values for the text, which (when multiplied by the font size) lets us determine the placement of each glyph. This is important, as the coordinates of each glyph shape are in absolute terms, not relative to one another.

The Draw() function is the heart of the matter, as it calls the Iterate() function of the BShapeIterator class. Each time through the loop, the offset point is adjusted to determine the starting point of the glyph shape:

void
IterView::Draw(BRect R)
{
  BPoint where(initialPoint);

  for (int32 i=0, curShape=0; i<textlen; i++, curShape++)
  {
    offset.Set(floor(where.x+esc[i+textlen].x+0.5),
         floor(where.y+esc[i+textlen].y+0.5)-1.0);

    MovePenTo(offset);
    SetHighColor(0,0,0);
    SetPenSize(2);
    StrokeShape(shapes[i]);
    SetPenSize(1);

    Iterate(shapes[i]);

    where.x += esc[i].x * fontSize;
    where.y += esc[i].y * fontSize;
  };

  if(firstPass) firstPass = false;
}

For simplicity's sake, we're drawing directly to the view, rather than off-screen. This results in flicker when dragging a control point, and is certainly the first thing you'll want to take care of in a real application.

The first time you call the Iterate() function, the firstPass flag is true, and each Iterate...To() function adds the point or points and the offset to a BList of glyphPts associated with that BShape. For example:

status_t
IterView::IterateLineTo(int32 lineCount, BPoint *linePts)
{
  SetHighColor(255,0,0);
  for(int i=0; i < lineCount; i++, linePts++)
  {
    FillEllipse(*linePts+offset, 2, 2);
    if(firstPass)
    {
      shapePts[curShape].AddItem(new glyphPt(linePts,
        offset));
    }
  }

  currentPoint = *(linePts-1)+offset;
  return B_OK;
}

Note that each Iterate...To() function also sets the currentPoint, which is the last point of the BShape that was drawn.

The IterateBezierTo() function needs a little explanation regarding its control points. BShape uses cubic Bézier curves, which means that the curve is described by four control points; but note that BezierTo() and IterateBezierTo() are given BPoints in groups of three, not four. The first control point is the point of the BShape immediately preceding the BezierTo() call; this point is not explicitly passed into BezierTo() or IterateBezierTo(). The last control point is the endpoint of the curve. The two intermediate control points determine the shape and amplitude of the curve between the first and fourth control points.

The MouseDown() function starts by determining if the mouse down point falls inside the bounding box of a BShape. If it does, it then searches the list of glyphPts associated with that BShape to determine if the mouse point is within a small distance of one of the control points. During this search, we need to take into account the offset of our BShape. If a point is found, we set the isTracking flag to true, and set dragPoint to the selected control point.

If isTracking is true and we enter the MouseMoved() function, we change the position of the chosen control point (once again taking into account the offset) and Invalidate the view:

void
IterView::MouseMoved(BPoint pt, uint32 code,
  constBMessage *msg)
{
  if(isTracking == false) return;

  *(dragPoint->pt) = (pt - dragPoint->offset);
  Invalidate();
}

There are many interesting things you can do with BShapes and BShapeIterators. Most obviously, they make excellent drawing primitives, which let you accurately and flexibly describe complex curves. You can create wonderful effects or even animations by applying various transformations to the BShape, especially for text. You can also clip to BShapes: just create a BPicture for your BShape, display an interesting pattern or bitmap, call BView::ClipToPicture(), and you've got an amazing effect for just a few lines of code.

Now that you've seen the possibilities, make it your New Year's resolution to get into BShape!


New Year Business Models

By Jean-Louis Gassée

I spent some time in the Old Country over the holidays, seeing family and friends, visiting Mont Saint-Michel and the Louvre, and sharing the merriment at the birth of the Euro. While I was there, I also got to enjoy the feedback our work gets in Europe. As I write this, I've just heard from Jean Calmon that the BeOS got the top award from PC Expert, the French version of PC Magazine, coming in ahead of Windows 98 and DirectX. We appreciate the recognition, not least of all because it helps us further our gains with software developers, resellers, and end users.

Not that there aren't still skeptics, but this time they weren't humming the "Microsoft über alles" leitmotif. No, they were cheekier and a little more creative. One doubter offered to license his secret plan for going public in six months or less. Instead of continuing with the obsolete French Farmer business model, you build a business, make money, and then sell shares of future profits in order to finance growth and make your anticipated earnings real.

The plan goes like this: build a simple but tasteful Web site and use it to sell dollar bills, one at a time, for 70 cents each. Yes, he agrees, you'll lose money on each transaction, but think of the traffic you'll build! From that high-volume traffic, you'll generate revenues well above your transaction losses. Even taking postage and handling into account, you'll be profitable before the investment bankers can park their BMWs behind your office. Then you can have an old-fashioned IPO to finance further growth. You can vary the plan—sell stamps at less than face value, offer special editions for advertisers. The Web frenzy will keep the stock climbing post-IPO.

When I protested, I was chided for not seeing that this was equivalent to free e-mail. You provide something for less than it costs, but with better returns. Dollar bills scale up more easily than mail servers. They require little maintenance and users need no tech support.

Another (French) acquaintance had an explanation for eBay's greater-than 5000 P/E ratio. He described the following "virtuous" circle. In order to keep the stock price in the stratosphere, the founders sell a little stock, enough to buy everything that's not snapped up by eBay users. They put all the junk in containers and dump it in the Bay (hence the company name). Users are thrilled. The word of mouth is terrific and attracts more users. The stock keeps climbing and the loop is closed.

Now, there are a couple of professional flea markets around Paris, but garage sales aren't part of the culture. Merchants who need stuff to sell advertise that they'll come to your house and, as a service, empty your cellar and attic of all its bizarre and embarrassing junk. eBay, my wine bistro companion explained, performs a similarly valuable service by taking a modest amount of money from the stock market and using it as an incentive for people to clean their closets and garages. Everyone benefits from putting the stock market and cyberspace at the service of New Year resolutions.

My own resolution is never to appear to endorse such unseemly views of respectable industry trends. As we say in my adopted language, this is a game of confidence, or something like that. We cannot allow disparaging satire or infelicitous irony to undermine the glorious edifice of trust in the new business models.

More seriously, a very Happy New Year to all the members of the Be extended family, with our best wishes for personal and professional realization.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.