@DATABASE AmigaMail @NODE MAIN "V-23: Using Compugraphic Typefaces with Bullet" @TOC "Cats_CD:Amiga_Mail/Table_of_Contents/V" by John Orr One of the improvements made to the Amiga's operating system for Workbench Release 2.1 is programmatic control of AGFA's IntelliFontÒ scaling engine. With this engine, application programs can fully utilize Compugraphic (CG) typefaces installed by Fountain (the CG typeface install that comes with 2.04 and 2.1). Some of the features that the font scaling engine offers include: * Rasterization of a typeface to arbitrary vertical and horizontal resolutions. * Baseline rotation of glyphs to an arbitrary angle. * Glyph shearing (italicizing) to any angle from -45 to 45 degrees (inclusive). * Access to kerning tables. * Algorithmic emboldening. @{" Starting the Engine " link V-23-1} @{" Width Lists " link V-23-9} @{" Other Level 0 Tags " link V-23-12} @{" Rasterizing a Glyph " link V-23-7} @{" Rotating " link V-23-10} @{" The Otag File Tags " link V-23-13} @{" Kerning " link V-23-8} @{" Shearing " link V-23-11} @{" About the Examples " link V-23-14} @{" Engine.c " link Cats_CD:Amiga_Mail/V-23/Engine.c/MAIN} @ENDNODE @NODE V-23-1 "Starting the Engine" There are several steps involved in using a font outline on the Amiga. @{"Step 1." link V-23-2} Open the font contents file (the ".font" file) and verify that it has a corresponding outline tag file (an ".otag" file). @{"Step 2." link V-23-3} Open the otag file, verify that it is valid, load its tag list into memory, and resolve any memory indirections in the tag list. @{"Step 3." link V-23-4} Find out the name of the typeface's scaling engine and obtain a pointer to the engine's GlyphEngine structure. @{"Step 4." link V-23-5} Tell the engine which typeface to use. @{"Step 5." link V-23-6} Set other scaling engine parameters. @ENDNODE @NODE V-23-2 "Step 1" All system supported fonts on the Amiga have a font contents file. From this file, an application can determine a font's type, so the application knows how to utilize the font. The font contents file is a FontContentsHeader structure (defined in ). The first field of that structure (fch_FileID) contains an ID identifying the font's type. If that type is OTAG_ID (0x0F03), the font is an outline and it should have a corresponding otag file. The otag file should be in the same directory as the font contents file. @ENDNODE @NODE V-23-3 "Step 2" The otag file contains a tag list that describes the typeface. All of these tags are defined in as either level 1, level 2, or level 3 outline tags. Level 1 tags are required to be present in an otag file. Level 2 and 3 tags are optional. See the include file for more information on the tag levels. The first tag of the otag file is always OT_FileIdent. Its value is the size of the otag file. This tag is here to verify the validity of the otag file. If the first tag is not OT_FileIdent, or the OT_FileIdent tag value is not the size of the otag file, the otag file is invalid, so don't attempt to use it. If the file is valid, copy the entire file into a buffer. The tags from the otag file have a special OT_Indirect bit. If this bit is set, the tag's value is an indirect reference to data defined elsewhere in the otag file. The tag's value is the offset to the data (in bytes) from the beginning of the otag file. For example, the otag file fonts:CGTimes.otag that is on the 2.04 Release disks contains the tag OT_Family (0x80009003), which has its OT_Indirect bit set. The value of the OT_Family tag is 195, meaning that the data for it--the NULL terminated string ``CG Times''--is located 195 bytes into the otag file. Of course, if an application read the file fonts:CGTimes.otag into a memory buffer, the ``CG Times'' string would be 195 bytes from the beginning of the buffer. The OT_Family tag must point to the absolute address of its data, so when an application loads an otag file into memory, it has to resolve the indirection of the OT_Indirect tags in memory. The application can do this by adding the buffer address to each OT_Indirect tag value. @ENDNODE @NODE V-23-4 "Step 3" One of the level 1 outline tags is the OT_Engine tag. This tag refers to the name of this typeface's scaling engine. At present there is only one scaling engine available on the Amiga. It is named Bullet. This is the IntelliFont scaling engine. The name is left over from the original implementation of the IntelliFont engine used on the Amiga. The scaling engine itself is in its own Exec library, called bullet.library. To open the engine, build a complete library name by adding the string ".library" to the OT_Engine string, and open it with OpenLibrary(). Don't assume that OT_Engine will always be the string ``bullet''. In the future, Commodore or some third party developer may create additional scaling engines libraries that will allow the Amiga to use other types of outline typefaces (PostScript, Nimbus-Q, etc.). Using the proper library name will help ensure compatibility with future possible scaling engines. All scaling engine libraries contain several functions: OpenEngine() If successful, returns a pointer to the library's GlyphEngine structure. CloseEngine() Releases the GlyphEngine structure obtained in OpenEngine(). SetInfo()/SetInfoA() Sets current parameters of a scaling engine (the current typeface, the current point size, the current output resolution, etc.) ObtainInfo()/ObtainInfoA() Queries a scaling engine for glyph information (a glyph's bitmap, the kerning value between two glyphs, etc.). ReleaseInfo()/ReleaseInfoA() Releases data obtained with ObtainInfo()/ObtainInfoA(). To obtain a pointer to a GlyphEngine structure for a particular scaling library, use that library's OpenEngine() routine. The function takes no arguments. @ENDNODE @NODE V-23-5 "Step 4" Setting a scaling engine's current typeface involves the SetInfoA() (or SetInfo()) function and the Level 0 tags from . The SetInfoA() function takes two parameters, a pointer to the GlyphEngine structure, and a tag list of level 0 outline tags. The Level 0 tags act as commands for a scaling engine, some of which are for setting scaling engine parameters (with SetInfo() or SetInfoA()), and some of which are for querying information from a scaling engine (with ObtainInfo() or ObtainInfoA()). Two tags set a scaling engine's current typeface: OT_OTagPath and OT_TagList. The OT_OTagPath tag points to the full path name of a typeface's otag file (for example, fonts:CGTimes.otag). The OT_OTagList tag points to the tag list created in step 2 above. @ENDNODE @NODE V-23-6 "Step 5" There are three other parameters the scaling engine needs in order answer queries for information: OT_DeviceDPI OT_PointHeight OT_GlyphCode The OT_DeviceDPI tag refers to the resolution of the output device. The tag value's high word is the horizontal resolution and the low word is the vertical resolution. Both are unsigned words measured in dots per inch. The OT_PointHeight tag refers to the height of a typeface in points. One point is approximately equal to 1/72 of an inch (AGFA/Compugraphic defines the point to be 0.01383 inches). For CG typefaces, this height is the distance between baselines from one line to the next (without any leading adjustments). The point height is represented as a fixed point, two's complement binary number, with the point situated in the middle of a long word. This means the lower word is the fractional portion and the upper word the whole number portion (the number covers the powers of two from 215 through 2-16). For those who may not know, a two's complement number is a way of representing negative numbers. To find the two's complement of a negative number, find the one's complement of the absolute value of that number (change all the binary ones to zeros and all the zeros to ones) and then add one to the result. To change from two's complement, subtract one from the two's complement number and find the one's complement of the number. For example, Before conversion to two's complement, the absolute value of the one byte decimal quantity -32 is represented as: In binary 0010 0000 ($20) One's complement 1101 1111 ($DF) Add One 0000 0001 ($01) ----------------------------------- Two's complement 1110 0000 ($E0) So the number -32 is encoded as 1110 0000 or 0xE0 in hex. Notice that the high bit (the sign bit) of the encoded number is set if the number is negative. If the number is zero or greater, the high bit is clear. This procedure is independent of where the ``point'' is in the negative number (the ``point'' in this case is the divider between the whole portion of the number and the fractional portion). When the computer adds one to the one's complement, it does not consider where the ``point'' is in the one's complement, the computer just treats the one's complement value as a whole integer. For example, to encode the decimal quantity -531/256 as a two byte, fixed point, two's complement binary number where the point is situated in the middle of the two bytes: 531/256 = -(2 + 19/256) = -2.13 in hex In binary 0000 0010.0001 0011 ($02.13 in hex, with the point) One's complement 1111 1101 1110 1100 ($FDEC in hex ignore the point from now on) Add One 0000 0000 0000 0001 ($0001) ---------------------------------------- Two's complement 1111 1101 1110 1101 ($FDED) Notice that the one added to the one's complement is in the 2-8 (= 1/256) column. The OT_GlyphCode tag refers to the current glyph. When an application asks the scaling engine to rasterize a glyph, this is the glyph the engine renders. The scaling engine uses Unicode encoding to represent glyphs. Unicode is an international character encoding standard that encompasses many of the world's national scripts in a 16-bit code space. Conveniently, the Amiga's Latin-1, 8-bit character set corresponds to the same glyphs as the Unicode standard. To set the current glyph to a character from the Amiga character set, use the same Latin-1 code, but pad it out to a 16-bit value. Because the Compugraphic typefaces use their own character set, the scaling engine in the bullet.library has to map the Unicode glyph codes to Compugraphic glyph codes. Note that the Unicode standard encompasses many more glyphs than just the Latin-1 character set or the Compugraphic character set, so many of the characters in the Unicode set do not map to any glyphs in the Compugraphic set. For example, Unicode includes several Asian ideogram sets, that the Compugraphic set does not. The result is the vast majority of the Unicode characters are not available using Compugraphic typefaces. The Compugraphic character set covers roughly 400 glyphs. For more information on the UniCode standard, see the Unicode Consortium's book The Unicode Standard, Worldwide Character Encoding published by Addison-Wesley (ISBN 0-201-56788-1). @ENDNODE @NODE V-23-7 "Rasterizing a Glyph" Once an application has set up the scaling engine, obtaining a glyph is a matter of asking for it. As was mentioned earlier, the ObtainInfoA()/ObtainInfo() function queries a scaling engine for glyph information. This function accepts a pointer to a GlyphEngine structure and a tag list. To ask for a rasterization of a glyph, pass ObtainInfo() the OT_GlyphMap tag. The engine expects the OT_GlyphMap value to be an address where it can place the address of a GlyphMap structure. The GlyphMap structure (defined in ) is for reading only and looks like this: struct GlyphMap { UWORD glm_BMModulo; /* # of bytes in row: always multiple of 4 */ UWORD glm_BMRows; /* # of rows in bitmap */ UWORD glm_BlackLeft; /* # of blank pixel columns at left of */ /* glyph */ UWORD glm_BlackTop; /* # of blank rows at top of glyph */ UWORD glm_BlackWidth; /* span of non-blank columns (horizontal */ /* span of the glyph) */ UWORD glm_BlackHeight; /* span of non-blank rows (vertical span of */ /* the glyph) */ FIXED glm_XOrigin; /* distance from upper left corner of */ /* bitmap to lower */ FIXED glm_YOrigin; /* left of glyph (baseline), in fractional */ /* pixels */ WORD glm_X0; /* approximation of XOrigin in whole pixels */ WORD glm_Y0; /* approximation of YOrigin in whole pixels */ WORD glm_X1; /* approximation of XOrigin + Width */ WORD glm_Y1; /* approximation of YOrigin + Width */ FIXED glm_Width; /* character advance, as fraction of em */ /* width */ UBYTE *glm_BitMap; /* actual glyph bitmap */ }; The glm_BitMap field points to a single bitplane bitmap of the glyph. This bitmap is not necessarily in Chip RAM, so if an application needs to use the blitter to render the glyph, it has to copy the bitmap to a Chip RAM buffer. The fields glm_BMModulo and glm_BMRows are the dimensions of the whole bitmap. The glyph itself does not occupy the entire bitmap area. The fields glm_BlackLeft, glm_BlackTop, glm_BlackWidth, and glm_BlackHeight describe the position and dimension of the glyph within the bitmap. The fields glm_XOrigin and glm_YOrigin are the X and Y offsets from the bitmap's upper left corner to the glyph's lower left corner. The lower left corner lies on the glyph's baseline. These X and Y offsets are in fractional pixels. The fields glm_X0 and glm_Y0 are rounded versions of glm_XOrigin and glm_YOrigin, respectively. The glm_Width field is a measure of the width of the glyph in fractions of an em (pronounced like the letter 'M'). This value is a fixed point binary fraction. The em is a relative measurement. A distance of one em is equal to the point size of the typeface. For example, in a 36 point typeface, one em equals 36 points which is approximately equal to a half inch. For a 72 point typeface, one em equals 72 points which is approximately equal to one inch. When an application is finished with the GlyphMap structure, it must use the ReleaseInfoA() or ReleaseInfo() function to relinquish the GlyphMap structure. This function uses the same format as ObtainInfoA()/ObtainInfo(), except the data value of the OT_GlyphMap tag is a pointer to the GlyphMap structure (rather than a pointer to a pointer). @ENDNODE @NODE V-23-8 "Kerning" The IntelliFont scaling engine also supports two types of kerning. One type of kerning is called text kerning, which is for regular bodies of text. The other type of kerning is called design kerning, which is for more obvious displays, like headlines. The basic difference is that design kerning is generally more tightly spaced than text kerning. Before asking for a kerning pair, an application has to tell the engine which character pair to kern. It does this using one of the SetInfo() functions to set the primary glyph, OT_GlyphCode, and the secondary glyph code, OT_GlyphCode2. To ask the scaling engine for a kerning value, use one of the ObtainInfo() functions with the OT_TextKernPair (for text kerning) or OT_DesignKernPair (for design kerning) tags. The engine expects the value of the kerning tag to be an address where it can store a four byte long kerning value. The kerning value is a fixed point binary fraction of an em square (like the glm_Width field from the GlyphMap structure). This value is the distance to remove from the primary character advance (the glm_Width of OT_GlyphCode) when rendering the secondary glyph (OT_GlyphCode2) immediately following the primary glyph. Unlike other ObtainInfo() tags, the scaling engine does not allocate any resources when answering queries about kerning values. Applications do not have to use ReleaseInfo() functions for kerning queries made with either OT_TextKernPair or OT_DesignKernPair. @ENDNODE @NODE V-23-9 "Width Lists" An application can find the widths of a typeface's glyphs using the OT_WidthList tag with one of the ObtainInfo() functions. The engine expects the OT_WidthList value to be an address where it can place the address of a MinList structure. This MinList points to a list of GlyphWidthEntry structures. The GlyphWidthEntry structure (defined in ) is for reading only and looks like this: struct GlyphWidthEntry { struct MinNode gwe_Node; /* on list returned by OT_WidthList */ /* inquiry */ UWORD gwe_Code; /* entry's character code value */ FIXED gwe_Width; /* character advance, as fraction of */ /* em width */ }; The MinList contains an entry for each valid Unicode glyph ranging from the primary glyph, OT_GlyphCode, through the secondary glyph, OT_GlyphCode2. The engine does not create a GlyphWidthEntry structure for codes without glyphs (for example the codes before the space character in the Latin-1 character set). When an application is finished with the width list, it must use one of the ReleaseInfo() functions to relinquish the list. This function uses the same format as the ObtainInfo() functions, except the data value of the OT_WidthList tag is a pointer to the MinList (rather than a pointer to a pointer). The primary and secondary code values do not have to remain constant while an application is using a width list. The engine deallocates the width list resources independently of the primary and secondary code values, so these can change after obtaining a width list. The following code fragment asks the scaling engine, ge, for a list of character widths of the Unicode glyphs ranging from unicode (OT_GlyphCode) to unicode2 (OT_GlyphCode2), inclusive. The fragment steps through the list of widths, printing each one. struct MinList *widthlist; struct GlyphWidthEntry *widthentry; . . . if (SetInfo(ge, OT_GlyphCode, unicode, OT_GlyphCode2, unicode2, TAG_END) == OTERR_Success) { if (ObtainInfo(ge, OT_WidthList, &widthlist, TAG_END) == OTERR_Success) { for (widthentry = (struct GlyphWidthEntry *) widthlist->mlh_Head; widthentry->gwe_Node.mln_Succ; widthentry = (struct GlyphWidthEntry *) widthentry->gwe_Node.mln_Succ) { printf("$%lx: %ld.%ld\n", widthentry->gwe_Code, widthentry->gwe_Width>>16, ((widthentry->gwe_Width&0xffff)*10000)>>16); } ReleaseInfo(ge, OT_WidthList, widthlist, TAG_END); } } . . . Notice that the ObtainInfo() functions (as well as the SetInfo() functions) return an error code (the error codes are defined in ). If that error code is equal to OTERR_Success, the operation was successful. @ENDNODE @NODE V-23-10 "Rotating" Taking advantage of other features of the Bullet library is a matter of setting other engine parameters using one of the SetInfo() functions with some other level 0 tags. One interesting feature of the IntelliFont engine is its ability to rotate glyphs. By setting the OT_RotateSin and OT_RotateCos values, the IntelliFont engine can rotate a glyph's baseline from the glyph origin (the glm_XOrigin and glm_YOrigin from the GlyphMap structure). The OT_RotateSin and OT_RotateCos are, respectively, the sine and cosine of the baseline rotation angle. The engine can rotate to any angle. The sine and cosine must correspond to the same angle and must be in the sine and cosine value range (0 through 1 inclusive). The engine does not do any error checking on the sine and cosine values. As a result, the engine will yield strange results if the sine and cosine are from very different angles or if these values are out of range for sines and cosines (greater than 1). By default, the engine sets these values to 0.0 and 1.0, the sine and cosine of 0 degrees. These values are encoded as fixed point binary fractions (the negative values are two's complement). When setting the baseline rotation, an application must set both the sine and cosine. It must set OT_RotateSin first, then OT_RotateCos. An application can set both values in the same SetInfo() function, but the sine must come first. For example, to set the rotation angle to 150 degrees: The sine of 150 degrees is 0.5 which is 0x00008000 in hex. The cosine of 150 degrees is approximately -0.866 which is approximately 0xffff224c in hex (two's complement), so: . . . if (SetInfo(ge, OT_RotateSin, 0x8000, OT_RotateCos, 0xFFFF224C, TAG_END) == OTERR_Success) /* If SetInfo() returns OTERR_Success, */ /* it worked OK. */ { /* The baseline rotation has been set, now the application can */ /* render it. */ . . . @ENDNODE @NODE V-23-11 "Shearing" Like baseline rotation, glyph shearing (also known as italicizing) is a matter of setting some Level 0 tags. The shearing tags, OT_ShearSin and OT_ShearCos, specify the shearing angle, or the angle at which the typeface is italicized. This angle refers to the angle that results from rotating the vertical axis clockwise. The angle can range from -45 to 45 degrees (inclusive). Like the rotation angle, the shearing angle is represented as a sine and cosine value that must correspond to the same angle and must fall into normal bounds for sine and cosine values. Also like the rotation angle sine and cosine tags, an application must set both the OT_ShearSin and OT_ShearCos tags, in that order. By default, the shearing value is zero degrees meaning there is no shearing (OT_ShearSin = 0x00000000, OT_ShearCos = 0x00010000). @ENDNODE @NODE V-23-12 "Other Level 0 Tags" There are several other Level 0 tags: OT_DotSize ---------- This tag specifies the X and Y size of the target device's dots. The X and Y DPI imply a dot size. For example, at 300 X and 300 Y DPI, the resolution implied dot size is 1/300 inches by 1/300 inches. For some devices (like some dot matrix printers), the size of the output dot does not match its resolution implied size. To a degree, the IntelliFont engine can account for this. The dot size is represented as a percentage of the dot's resolution implied size. The X percentage is in the tag value's upper word, and the Y percentage is in the tag value's lower word. OT_SetFactor ------------ This tag distorts the width of a typeface by changing the width of the em square. The scaling engine changes the em width to this tag's value. The value is a fixed point binary fraction. OT_EmboldenX/OT_EmboldenY ------------------------- These tags specify the algorithmic emboldening factor in the X and Y direction, respectively. The tag values are fixed point two's complement binary numbers. The units are measured in ems. Emboldening values above zero embolden the typeface. Emboldening values below zero lighten the typeface. By default, both values are zero. OT_GlyphWidth ------------- This tag's value specifies a width for the current typeface. It is a fraction of an em represented as a fixed point binary number. If this value is set to something besides 0.0, all glyphs will have this width. To turn off the constant width, set OT_GlyphMap back to 0.0 (its default value). @ENDNODE @NODE V-23-13 "The Otag File Tags" The Outline Tag (otag) file contains a number of tags that describe a font outline to the Diskfont library. The purpose of most of these tags is to allow Diskfont to attribute styles to a typeface when loading a font outline as a standard Amiga system font. Most applications that use the scaling engine will not need to worry about the meaning of the majority of these tags, but they are described below. The following tags are Level 1 tags and must be present in every otag file: OT_FileIdent ------------ Every valid otag file starts with this tag. Its value is the size of the file. It doesn't really have anything to do with the definition of the typeface, but it does serve as a way to check the validity of the otag file. OT_Engine --------- This tag's value points to a string naming the font scaling engine. For example, the OT_Engine tag in fonts:CGTimes.otag is ``bullet''. OT_Family --------- This tag's value points to a string naming the typefaces font family. For example, the OT_Family in fonts:CGTimes.otag is ``bullet''. OT_SymbolSet ------------ This tag's value is a two byte ASCII code for this typeface's symbol set. This tells the system which symbol set to use to map the Amiga character set to the Compugraphic character set. The symbol set for most CG fonts designed for use with the Amiga is ``L1'', which stands for Latin1. The exception is the CG fonts from Gold Disk, Inc. They use the ``GD'' (Gold Disk) symbol set. OT_YSizeFactor -------------- For traditional Amiga fonts, the font size does not include any spacing on top or bottom of the typeface--the Amiga doesn't consider it part of the font. CG fonts include spacing on the top and bottom of their typefaces. This tag's value is a ratio of the point height of a typeface to its ``black'' height (the point height minus the space on the typeface's top and bottom). The high word is the point height factor and the low word is the black height factor. Note that these values form a ratio. Individually, they do not necessarily reflect any useful value. OT_SerifFlag ------------ If this tag's value is TRUE, this typeface has serifs. OT_StemWeight ------------- This tag's value can be anywhere from 0 through 255 and indicates a nominal weight or ``boldness'' to the typeface. The include file defines a set of ratings for this tag's value. See that file for more details. When the Diskfont library opens an outline font, it uses this value to determine if a typeface is bold. OT_SlantStyle ------------- The include file defines a set of three possible values for this tag's value. See that file for more details. When the Diskfont library opens an outline font, it uses this value to determine if a typeface is italicized/obliqued. OT_HorizStyle ------------- This tag's value can be anywhere from 0 through 255 and indicates a nominal width rating to the typeface. The include file defines a set of ratings for this tag's value. See that file for more details. When the Diskfont library opens an outline font, it uses this value to determine if a typeface is extended. OT_AvailSizes ------------- This tag's value points to a sorted list of UWORDs. The first UWORD is the number of entries in the sorted list. The remaining UWORDs are the font sizes that the Diskfont library lists when calling AvailFonts(). OT_SpecCount ------------ This tag's value is a number of spec tags that follow it. A spec tag is private to the scaling engine. The following are Level 2 tags. They may also be in an otag file but are not required: OT_BName -------- This tag points to a string naming the bold variant of this typeface. For example, the fonts:CGTimes.otag file lists ``CGTimesBold'' as its bold variant. OT_IName -------- This tag points to a string naming the italic variant of this typeface. OT_BIName --------- This tag points to a string naming the bold italic variant of this typeface. OT_SpaceWidth ------------- This tag's value is the width of the space character at 250 points (where there are 72.307 points in an inch). The width is in Design Window Units (DWUs). One DWU is equal to 1/2540 inches. To convert to X pixels: OT_SpaceWidth pointsize ------------- * --------- * XDPI = spacewidth in pixels (X dots) 2540 250 OT_IsFixed ---------- If this tag's value is TRUE, every glyph in this typeface has the same character advance (a fixed width). OT_InhibitAlgoStyle ------------------- This tag's value is a bitmask that is compatible with the ta_Style field of the TextAttr structure (defined in ). This tag tells which styles cannot be added to a typeface algorithmically. For example, if the FSF_BOLD bit in OT_InhibitAlgoStyle is set and a user asks for a bold version of the typeface, the diskfont.library (or an application) can add that style algorithmically. At present there are no Level 3 tags. @ENDNODE @NODE V-23-14 "About the Examples" This article contains two code examples. The first, @{"Rotate.c" link Cats_CD:Amiga_Mail/V-23/Rotate.c/MAIN}, rotates a user-specified glyph around a central point. By default, it rotates a 36 point 'A' using the font fonts:CGTimes.font. If Rotate finds an AmigaDOS environment variable called "XYDPI", it will use the X and Y DPI it finds in that variable as the default target device DPI (see the description of the Level 0 OT_DeviceDPI tag). If that variable is not defined, Rotate will use an XDPI of 68 and a YDPI of 27 which, nominally, is the X and Y DPI of a standard Hires display. The second example, @{"View.c" link Cats_CD:Amiga_Mail/V-23/View.c/MAIN}, displays a file using the same defaults as Rotate. View utilizes kerning pairs to display its glyphs. Because View only considers "visible" characters, it ignores characters that have widths but no glyph. The result is, View doesn't print any space characters. If View were smarter, it would ask the scaling engine for a width list so it could properly advance the current pointer when it comes across a space or some other character without a glyph. Notice that View uses a slightly modified version of @{"BulletMain.c" link Cats_CD:Amiga_Mail/V-23/BulletMain.c/MAIN} called @{"BulletMainFile.c" link Cats_CD:Amiga_Mail/V-23/BulletMainFile.c/MAIN}. Only BulletMain.c appears in the example code. The only significant difference between the two is that BulletMainFile.c obtains a file name for View to display. @ENDNODE