############################################################################ # # File: xbfont.icn # # Subject: Procedures for X font selection # # Author: Gregg M. Townsend # # Date: May 2, 2001 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # BestFont(W, s, ...) generates X-windows font names matching a # given specification, beginning with the closest match. The # ranking algorithm is similar to that used in Font() but it is # not identical. # ############################################################################ # # BestFont(window, spec, ...) returns the name of whichever available # X-Windows font most closely matches the given specification. Note that # matching is done using a slightly different algorithm from that of the # Icon runtime system; this procedure preceded Icon's font selection # implementation and served as a prototype. # # The font specification is one or more strings containing whitespace- # or comma-separated tokens. Tokens are case-insensitive. There are # three kinds of tokens. # A token having the form of an integer specifies the desired "pixel # size" (height). If no size is included, a target size of 14 is used. # An unrecognized token is taken as a substring of the desired X font # name. Family names, weights, and other such factors are specified this # way. # Certain tokens are recognized and handled specially: # m mono monospaced # p prop proportional # r roman # i italic # o oblique # s sans sans-serif sansserif # These are turned into search strings of a particular form. For example, # "roman" and "r" specify the search string "-r-". # # The "best match" to a given specification is calculated by reviewing # all the available fonts, assigning a score to each, then choosing the # one with the highest value. There are several aspects of scoring. # Size is the most important factor. A tuned font of the correct size # gets the maximum score. Nearby sizes receive partial credit, with # an undersized font preferred over an oversized font. Scalable fonts # are also recognized, but a tuned font of the correct or nearly-correct # size gets a higher score. # Each successful substring match increases the score, whether the # test string comes from an unrecognized token or a special keyword. # Earlier tokens receive slightly more weight than later ones. # All tokens need not match. The string "lucida gill sans 18" # is perfectly reasonable; it specifies a preference for Lucida Sans # over Gill Sans by the position of the tokens, but will match either. # Ties are broken by giving slight preferences for normal weight, # no slant, normal width, and ASCII ("iso8859") encoding. A slight # penalty is assessed for "typewriter" fonts. Oblique fonts receive # partial credit for matching "italic" requests, and vice versa. # The scoring function can be altered by assigning values to certain # global variables. See XBF_defaults() for a commented list of these. # # For a scalable font, the returned value is a string specifying an # instance of the font scaled to the target size. For large sizes, the # scaling time may be noticeable when the font is used. # # BestFont() is actually a generator that produces the entire list # of available fonts in order of preference. RankFonts(w, spec, ...) # is similar to BestFont but produces a sequence of two-element records, # where result.str is the font name and result.val is its score. For # either of these, a list of X font names can be passed instead of a # window. # # There is some startup cost the first time BestFont is called; it # opens a pipe to the "xlsfonts" program and reads the output. Results # are cached, so this overhead is only incurred once. # # Examples: # Font(w, BestFont(w, "times bold italic 20")) # s := BestFont(w, size, family, "italic") # ############################################################################ # # Requires: Version 9 graphics under Unix # ############################################################################ record XBF_rec(str, val) global XBF_wantsize # requested font size global XBF_sizval # array of scores indexed by actual font size # globals used for tuning the scoring function; see XBF_defaults() global XFW_defsize, XFW_size, XFW_maxover, XFW_maxunder, XFW_scaled global XFW_spacing, XFW_slant, XFW_aslant, XFW_sans global XFW_default, XFW_exact, XFW_posn, XFW_tiebreakers # BestFont(window, spec...) - generate ranked sequence of font names procedure BestFont(args[]) #: generate best X fonts suspend (RankFonts ! args) . str end # XRankFont(window, spec...) - generate sequence of (name,score) tuples procedure RankFonts(w, args[]) #: generate scores for X fonts local tokens, cklist, sclist, fspec, ranks, r if type(w) ~== "window" & type(w) ~== "list" then { push(args, w) w := &window } XBF_defaults() # set default values XBF_wantsize := XFW_defsize # set target size to default tokens := XBF_tokenlist(args) # break args into list of tokens cklist := XBF_weights(tokens) # get list of (substring,weight)s XBF_sizval := XBF_sizes(XBF_wantsize) # build array for scoring sizes # make a list of (fontname,score) tuples, and sort it sclist := [] every fspec := XBF_fontlist(w) do put(sclist, XBF_rec(fspec, XBF_eval(fspec, cklist))) ranks := sortf(sclist, 2) # generate results from hightest to lowest rank while r := pull(ranks) do suspend XBF_rec(XBF_spec(r.str, XBF_wantsize), r.val) end # XBF_defaults() - assign default values to any unset tuning parameters procedure XBF_defaults() /XFW_defsize := 14 # default size if unspecified /XFW_size := 1000 # points for matching size exactly /XFW_maxover := 30 # max allowable overage on size (per cent) /XFW_maxunder := 60 # max allowable shortfall on size (per cent) /XFW_scaled := 800 # points for matching size with scaled font /XFW_spacing := 500 # points for matching prop/mono spacing /XFW_slant := 500 # points for matching slant /XFW_aslant := 300 # points for approx slant (oblique : italic) /XFW_sans := 500 # points for matching "sans" spec /XFW_exact := 1100 # points for matching entire font name /XFW_default := 500 # points for matching unrecognized token /XFW_posn := 10 # points for position in request list /XFW_tiebreakers := [ # "tiebreaker" strings always scored XBF_rec("-normal-", 1), # prefer normal width XBF_rec("-medium-", 1), # prefer medium weight XBF_rec("-r-", 2), # upright slant is even more important XBF_rec("-iso8859-", 1), # prefer ASCII, not symbol/kana/etc XBF_rec("typewriter", -4)] # penalize typewriter fonts return end # XBF_tokenlist(args) -- turn list of args into list of tokens procedure XBF_tokenlist(args) local tokens tokens := [] every map(trim(!args)) ? repeat { tab(many(' \t,')) if pos(0) then break put(tokens, tab(upto(' \t,') | 0)) } return tokens end # XBF_weights(tokens) -- turn tokens into list of substrings and weights # # Also saves the size value in the global XBF_wantsize. procedure XBF_weights(tokens) local cklist, tk, pf cklist := [] pf := *tokens * XFW_posn every tk := !tokens do { if not (XBF_wantsize := integer(tk)) then { pf -:= XFW_posn case tk of { "m" | "mono" | "monospaced": every put(cklist, XBF_rec("-m-" | "-c-", XFW_spacing + pf)) "p" | "prop" | "proportional": put(cklist, XBF_rec("-p-", XFW_spacing + pf)) "r" | "roman": put(cklist, XBF_rec("-r-", XFW_slant + pf)) "i" | "italic": { put(cklist, XBF_rec("-i-", XFW_slant + pf)) put(cklist, XBF_rec("-o-", XFW_aslant + pf)) } "o" | "oblique": { put(cklist, XBF_rec("-o-", XFW_slant + pf)) put(cklist, XBF_rec("-i-", XFW_aslant + pf)) } "s" | "sans" | "sans-serif" | "sansserif": put(cklist, XBF_rec("sans", XFW_sans + pf)) default: put(cklist, XBF_rec(tk, XFW_default + pf)) } } } every put(cklist, !XFW_tiebreakers) return cklist end # XBF_sizes(wantsize) -- build array of scores for evaluating font sizes procedure XBF_sizes(wantsize) local l, sz, diff, score, maxunder, maxover l := [XFW_scaled] # initial entry scores scaled fonts # set scores for undersized fonts maxunder := (XFW_maxunder / 100.0) * wantsize every sz := 1 to wantsize-1 do { diff := wantsize - sz score := integer(XFW_size * (1 - diff / maxunder)) score <:= 0 put(l, score) } # set scores for correct and oversized fonts maxover := (XFW_maxover / 100.0) * wantsize repeat { sz +:= 1 diff := sz - wantsize score := integer(XFW_size * (1 - diff / maxover)) if score <= 0 then break # quit when too big to be useful put(l, score) } return l end # XBF_fontlist(w) - generate list of font names for window (or list) w procedure XBF_fontlist(w) static fontlist local pipe if type(w) == "list" then suspend !w else { if /fontlist then { fontlist := [] pipe := open("xlsfonts", "rp") | stop("can't open xlsfonts pipe") while put(fontlist, trim(read(pipe))) close(pipe) } suspend !fontlist } end # XBF_eval(fontname, cklist) -- evaluate the score of an X font name procedure XBF_eval(fontname, cklist) local t, r # find the size and look up its score in the XBF_sizval array fontname ? { every 1 to 7 do tab(upto('-')) & move(1) t := XBF_sizval [1 + integer(tab(upto('-')))] | 0 } # add the corresponding value for every substring that matches every r := !cklist do if find(r.str, fontname) then if r.str == fontname then t +:= XFW_exact # high score for matching entire name else t +:= r.val # else give specified value return t end # XBF_spec(fontname, size) -- return the correct form of an X font name # # This is just the name itself except in the case of scalable fonts. procedure XBF_spec(fontname, size) local s fontname ? { s := tab(find("-0-0-")) | return fontname # return if not scalable move(5) # skip pixel size, point size tab(upto('-')) & move(1) # skip x-resolution tab(upto('-')) & move(1) # skip y-resolution s ||:= "-" s ||:= size # spec pixel size s ||:= "-*-*-*-" # wildcard ptsize & resolutions s ||:= tab(upto('-')) # copy spacing field s ||:= move(1) tab(upto('-')) # skip average width s ||:= "*" s ||:= tab(0) # copy the rest } return s end