Miniwindows in MUSHclient - Drawing text
Written by Nick Gammon - July 2008. Updated September 2010.
On this page:
See also:
- Introduction
- Creating miniwindows
- Drawing shapes
- Drawing images
- Hotspots
- Helpful graphics functions
- Blending images
Examples on this page
Most of the examples on this page are drawn in a 220 x 100 pixel window, with a grid drawn every 20 pixels to make it clearer the effect of the example code. The code for producing the grid is described in Creating miniwindows.
Introduction to text
The MUSHclient miniwindows let you draw text in:
- Any font you choose (which is installed on your system)
- Any size
- Any style (bold, italic, underline, strike-through), or combination of styles
- Any number of fonts can be used in the same window.
- Unicode may be used if you supply UTF-8 strings (with a suitable Unicode-enabled font).
- Any colour
Text basics
To draw text accurately you need to know where the text will appear in your window. In particular, if you are mixing large and small fonts on the same line, you need to understand where the "baseline" of the text is.
Let's look at an example of some rather large text, so we can clearly see the component parts of a line of text:
WindowFont (win, "f", "Trebuchet MS", 28, true, false, false, false) -- define font
width = WindowTextWidth (win, "f", "Lazy dog yawns") -- width of text (270)
height = WindowFontInfo (win, "f", 1) -- height of the font (46)
ascent = WindowFontInfo (win, "f", 2) -- ascent (amount above the baseline) (36)
descent = WindowFontInfo (win, "f", 3) -- descent (amount below the baseline) (10)
leading = WindowFontInfo (win, "f", 4) -- leading (space above the highest letter) (9)
WindowText (win, "f",
"Lazy dog yawns", -- text
5, 20, 0, 0, -- rectangle
ColourNameToRGB ("darkgreen"), -- colour
false) -- not Unicode

We see the following:
- The start point for drawing the text was specified as 5,20 (marked on the example with the red dot).
- We can see that the very top of the text box is at the dot, so the text is drawn below, and to the right of, the specified starting position.
- The red box shows the amount of room that piece of text took up.
- The width can be calculated by calling WindowTextWidth, if you need to know it in advance, and is also returned from the function call to WindowText.
- The height can be obtained by calling WindowFontInfo for that font with a selector of 1.
- The blue line shows the leading amount (the gap between lines) - above that is blank. Actually you can see a small amount of green from the "d" in "dog" poking above the line - zooming in seems to indicate that the anti-aliasing in the font crept above the line slightly. The leading is obtained from calling WindowFontInfo with a selector of 4.
- The magenta line shows the baseline, which is origin of the drawing, plus the ascent value, obtained from calling WindowFontInfo with a selector of 2.
- The ascent + the descent = the height (in this example, 36 + 10 = 46)
- The leading is included in the ascent (of the 36 ascent, 9 is leading, and the remaining 25 is used by the font).
If we wanted to put more text at the right, in a different font, we need to allow for the different likely position of the baseline. In this example, the baseline was 36 pixels down from where we started drawing. If we wanted some text next to it which had a baseline of 26 pixels down, we would need to start the next lot of text 10 pixels lower to compensate (that is, add 10 to the starting y position).
Getting started with text - loading the fonts
Before text can be drawn to the miniwindow, the font must be "loaded". This specifies that this miniwindow is going to use this font. Later on you simply refer to the "font id" you gave when loading the font.
WindowFont function prototype:
long WindowFont(BSTR Name, BSTR FontId, BSTR FontName, double Size, BOOL Bold, BOOL Italic, BOOL Underline, BOOL Strikeout, short Charset, short PitchAndFamily);
This loads the specified font into the miniwindow, and remembers it by the nominated "font id". The font id is later used for drawing in this font. You do not need to specify a colour, that is done when the text is actually drawn.
- Name - the name of an existing miniwindow. Names are case-sensitive.
- FontId - the font id to be associated with this particular font and size. If you are only planning to use one or two fonts you might call them "f1" and "f2". For more complex situations you might call them something like "heading", "body", "boldbody", "footer" etc.
- FontName - the name of the font (for example, "Lucida Console", "Comic Sans MS", "Fixedsys").
If you are using Lua scripting, you can use the utils.getfontfamilies function to find which font names are actually installed.
- Size - the size of the font in points
- Bold - true if you want the bold version of the font
- Italic - true if you want the italic version of the font
- Underline - true if you want the underline version of the font
- Strikeout - true if you want the strikeout version of the font
- Charset - The character set wanted. Some possible values are:
Value Purpose Lua symbol 0 ANSI miniwin.font_charset_ansi 1 Default miniwin.font_charset_default 2 Symbol miniwin.font_charset_symbol Unless you have a reason otherwise, use 1 (miniwin.font_charset_default) which is the default value for Lua.
- PitchAndFamily - the requested pitch and family
You can add together the following values:
Family
Value Purpose Lua symbol 0 Don't care miniwin.font_family_any 16 Roman (variable width, serif, eg. Times Roman) miniwin.font_family_roman 32 Swiss (variable width, sans-serif, eg. Helvetica) miniwin.font_family_swiss 48 Modern (fixed width, serif or sans-serif - eg. Courier) miniwin.font_family_modern 64 Script (cursive, etc.) miniwin.font_family_script 80 Decorative (Old English, etc.) miniwin.font_family_decorative Pitch
Value Purpose Lua symbol 0 Default miniwin.font_pitch_default 1 Fixed pitch miniwin.font_pitch_fixed 2 Variable pitch miniwin.font_pitch_variable 8 Monospaced font miniwin.font_pitch_monospaced Truetype
Value Purpose Lua symbol 4 Use Truetype font miniwin.font_truetype A reasonable entry would be 0 - for don't care. Or, if you wanted a variable width, serif, truetype font, you could specify 20 (which is 16 + 4), or (miniwin.font_family_roman + miniwin.font_truetype).
Example of loading a font
-- Trebuchet MS, 28 point, bold
WindowFont (win, "heading", "Trebuchet MS", 28, true, false, false, false)
List all fonts
WindowFontList function prototype:
VARIANT WindowFontList(BSTR Name);
This returns a list of all fonts loaded into this miniwindow. You could use this to find which fonts have been loaded, and then use WindowFontInfo to find information about each one.
Example:
-- show all fonts
fonts = WindowFontList(win)
if fonts then
for _, v in ipairs (fonts) do
Note (v)
end
end -- if any
Get information about a font
WindowFontInfo function prototype:
VARIANT WindowFontInfo(BSTR Name, BSTR FontId, long InfoType);
This returns information about a loaded font. You need to specify the name of the miniwindow, and the font id you used when loading the font.
Probably the most important item is number 1 - the height of the font. If you are drawing multiple lines of a particular font, you should add the font height to the "y" parameter of the rectangle, to move the pixel position down the window an appropriate amount.
- Name - the name of an existing miniwindow. Names are case-sensitive.
- FontId - the font id you used when you loaded the font.
- InfoType - the information you want:
- 1: Height - Specifies the height (ascent + descent) of characters.
- 2: Ascent - Specifies the ascent (units above the base line) of characters
- 3: Descent - Specifies the descent (units below the base line) of characters.
- 4: Internal Leading - Specifies the amount of leading (space) inside the bounds set by the height. Accent marks and other diacritical characters may occur in this area. The designer may set this to zero.
- 5: External Leading - Specifies the amount of extra leading (space) that the application adds between rows. Since this area is outside the font, it contains no marks and is not altered by text output calls. The designer may set this member to zero.
- 6: Average Character Width - Specifies the average width of characters in the font (generally defined as the width of the letter x). This value does not include the overhang required for bold or italic characters.
- 7: Maximum Character Width - Specifies the width of the widest character in the font.
- 8: Weight - Specifies the weight of the font.
- 9: Overhang - Specifies the extra width per string that may be added to some synthesized fonts. When synthesizing some attributes, such as bold or italic, graphics device interface (GDI) or a device may have to add width to a string on both a per-character and per-string basis. For example, GDI makes a string bold by expanding the spacing of each character and overstriking by an offset value; it italicizes a font by shearing the string. In either case, there is an overhang past the basic string. For bold strings, the overhang is the distance by which the overstrike is offset. For italic strings, the overhang is the amount the top of the font is sheared past the bottom of the font.
- 10: Digitized Aspect X - Specifies the horizontal aspect of the device for which the font was designed.
- 11: Digitized Aspect Y - Specifies the vertical aspect of the device for which the font was designed. The ratio of the DigitizedAspectX and DigitizedAspectY is the aspect ratio of the device for which the font was designed.
- 12: First Character - Specifies the value of the first character defined in the font.
- 13: Last Character - Specifies the value of the last character defined in the font
- 14: Default Character - Specifies the value of the character to be substituted for characters not in the font.
- 15: Break Character - Specifies the value of the character that will be used to define word breaks for text justification.
- 16: Italic - Specifies an italic font if it is nonzero.
- 17: Underlined - Specifies an underlined font if it is nonzero.
- 18: Struck Out - Specifies a strikeout font if it is nonzero.
- 19: Pitch And Family - Specifies information about the pitch, the technology, and the family of a physical font.
- 20: Character Set - Specifies the character set of the font.
- 21: Name - Specifies the name of the font
Writing text
WindowText function prototype:
long WindowText(BSTR Name, BSTR FontId, BSTR Text, long Left, long Top, long Right, long Bottom, long Colour, BOOL Unicode);
This draws some text in miniwindow.
- Name - the name of an existing miniwindow. Names are case-sensitive.
- FontId - a font id that you have loaded.
- Text - the text to be drawn
- Left, Top, Right, Bottom - describes the rectangle to be drawn.
- Colour - what colour to draw the text in (RGB). You can use ColourNameToRGB to convert a colour name (like "red" or "green") into a colour number.
- Unicode - if true, the text is Unicode text in UTF-8 format
If successful, returns the number of pixels the text took horizontally. Thus, by adding that to the "Left" parameter, you can draw some more text to the right of what was just drawn.
If unsuccessful, returns a negative number as follows:
- -1 : That window name does not exist
- -2 : That font was not loaded
- -3 : For Unicode, invalid UTF-8 sequence
Examples of writing text
-- load fonts (do this once only)
WindowFont (win, "f", "Trebuchet MS", 14, true, false, false, false)
WindowFont (win, "f2", "Trebuchet MS", 30, false, true, false, false)
-- Draw in 14 point Trebuchet MS bold
WindowText (win, "f", "Quest Log",
5, 10, 0, 0, -- rectangle
ColourNameToRGB ("blue"),
false) -- not Unicode
-- Draw in 30 point Trebuchet MS italic
WindowText (win, "f2", "MUSHclient",
5, 40, 0, 0, -- rectangle
ColourNameToRGB ("saddlebrown"),
false) -- not Unicode

In this example I have used 0,0 as the right,bottom parameters for the rectangle. As explained on the Drawing shapes page, that actually means the bottom-right corner. Thus the text has the rest of the window to be drawn in. This is normally safe to do, unless you want to "clip" the text, so it does not spill over onto some other graphic object to the right of it, or below it.
Calculate text width
WindowTextWidth function prototype:
long WindowTextWidth(BSTR Name, BSTR FontId, BSTR Text, BOOL Unicode);
This calculates how many pixels a particular piece of text will take up, which can help in calculating how big a window to create, or where to put other things. Note that WindowText returns the number of pixels when it draws text, so you don't need to call WindowTextWidth simply to work out where to draw the very next item.
- Name - the name of an existing miniwindow. Names are case-sensitive.
- FontId - a font id that you have loaded.
- Text - the text to be calculated
- Unicode - if true, the text is Unicode text in UTF-8 format
If successful, returns the number of pixels the text would take horizontally, when drawn in this font.
If unsuccessful, returns a negative number as follows:
- -1 : That window name does not exist
- -2 : That font was not loaded
- -3 : For Unicode, invalid UTF-8 sequence
Examples of calculating width
-- load font (do this once only)
WindowFont (win, "f", "Trebuchet MS", 14, true, false, false, false)
width = WindowTextWidth (win, "f", "Lazy dog yawns") -- width of text (140)
Hints for working with text
Hint 1
When writing a plugin, create your window initially in OnPluginInstall, as a 1 x 1 pixel window, simply to load in the font (ready for future use), and find out the font height.
function OnPluginInstall ()
win = "A" .. GetPluginID ()
font_id = "fn"
bold_font_id = "fb"
font_name = "Sylfaen" -- the actual font
font_size = 8
-- make window so I can grab the font info
WindowCreate (win,
0, 0, 0, 0, -- empty window
miniwin.pos_top_left, -- position (irrelevant)
0, -- flags (none)
0) -- background colour (black - irrelevant)
-- add font in normal and bold styles
WindowFont (win, font_id, font_name, font_size,
false, false, false, false, -- normal
miniwin.font_charset_ansi, miniwin.font_family_any)
WindowFont (win, bold_font_id, font_name, font_size,
true, false, false, false, -- bold
miniwin.font_charset_ansi, miniwin.font_family_any)
-- find height of font for future calculations
font_height = WindowFontInfo (win, font_id, 1) -- height
end -- OnPluginInstall
The above example creates the window (which is initially invisible), adds two fonts ("Sylfaen" normal, and bold), and finds the height of the normal font (we assume the bold one will be the same height).
Later on, if we need to display (say) 10 lines of text, then we can re-create the window with vertical size of 10 x font_height (plus a few extra pixels for a border, if wanted).
Hint 2
In a plugin, as you capture information (for example, quest instructions), do not display it as each line arrives, as that will cause flicker. Instead, batch it up in a table, and calculate the maximum size window you will need when it has all arrived, like this:
table.insert (quest_info, text) -- store text for later
max_width = math.max (max_width, WindowTextWidth (win, font_id, text)) -- find max width
This assumes you set max_width to zero at the start of the operation (eg. when the first trigger fires at the start of the quest message).
Hint 3
When you have collected all the text, re-create the window, using the number of lines gathered to specify the vertical size, and the maximum width for the horizontal size, plus a bit extra for margins:
-- recreate the window the correct size
WindowCreate (win,
0, 0, -- left, top (auto-positions)
max_width + 10, -- width
((#quest_info + 2) * font_height) + 5, -- height
miniwin.pos_center_right, -- position
0, -- flags
0x696969) -- yellow background
In the above example I am planning to output a heading line ("Current quest") and a "time to go" line, so I have taken the number of captured text lines (#quest_info) and added 2, then multiplied by the font height. Finally I added 5 to give a small vertical margin.
Then for the width, I took the maximum width as calculated as each line arrived, added 10 for a margin, and used that.
Hint 4
Now we can make a helper function Display_Line to handle displaying each line easily. This function merely takes line number, subtracts 1 to make it zero-relative, and multiplies by the line height. That gives the pixel position vertically. It then displays the text in the requested font id and colour.
function Display_Line (line, text, id, colour)
local left = 5
local top = (line - 1) * font_height
WindowText (win, id, text, left, top, 0, 0, colour)
end -- Display_Line
Hint 5
Displaying style runs is almost as easy. Style runs are available to triggers (in Lua) or can be obtained by calling GetStyleInfo in other languages. Each style run already has the text to be displayed, and the text colour. A simple loop will display each style, one after the other. The loop below makes use of the fact that each call to WindowText gives you the number of pixels you need to move to the right.
function Display_Styled_Line (line, styles, id)
local left = 5
local top = (line - 1) * font_height
for _, v in ipairs (styles) do
left = left + WindowText (win, id, v.text, left, top, 0, 0, v.textcolour)
end -- for each style run
end -- Display_Styled_Line
Other pages about miniwindows
- Introduction
- Creating miniwindows
- Drawing shapes
- Drawing images
- Hotspots
- Helpful graphics functions
- Blending images