|
|
Table of Contents
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 Bezier 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
"Developers' Workshop" is a weekly feature that provides
answers to our developers' questions, or topic requests.
To submit a question, visit
http://www.be.com/developers/suggestion_box.html.
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:
MoveTo() moves to the specified point.
LineTo() creates a line between the current point and the specified point.
BezierTo() describes a cubic Bezier curve which starts at the current point and ends at the third point specified.
Close() creates a line between the current point and the first point of the BShape.
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 Bezier
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,
const BMessage *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.
Recent Be Newsletters |
1998 Be Newsletters
1997 Be Newsletters |
1995 & 1996 Be Newsletters
Copyright ©1998 Be, Inc.
Be is a registered trademark, and BeOS, BeBox, BeWare, GeekPort, the Be logo and the BeOS logo
are trademarks of Be, Inc. All other trademarks mentioned are the property of their respective owners.
Comments about this site? Please write us at webmaster@be.com.
|