Written by Nick Gammon - July 2008. Updated September 2010.
On this page:
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.
The MUSHclient miniwindows let you draw text in:
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:
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).
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.
If you are using Lua scripting, you can use the utils.getfontfamilies function to find which font names are actually installed.
Unless you have a reason otherwise, use 1 (miniwin.font_charset_default) which is the default value for Lua.
You can add together the following values:
|16||Roman (variable width, serif, eg. Times Roman)|
|32||Swiss (variable width, sans-serif, eg. Helvetica)|
|48||Modern (fixed width, serif or sans-serif - eg. Courier)|
|64||Script (cursive, etc.)|
|80||Decorative (Old English, etc.)|
|4||Use Truetype font|
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).
-- Trebuchet MS, 28 point, bold WindowFont (win, "heading", "Trebuchet MS", 28, true, false, false, false)
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.
-- show all fonts fonts = WindowFontList(win) if fonts then for _, v in ipairs (fonts) do Note (v) end end -- if any
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.
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.
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:
-- 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.
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.
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:
-- 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)
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).
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).
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.
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
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
Gammon Software support
Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )