Module: motif-duim Synopsis: Motif font mapping implementation Author: Scott McKay, Stuart Croy Based on work by John Aspinall and Richard Billington Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Motif font management define sealed class () sealed slot %font-name :: , required-init-keyword: name:; sealed slot %font-id :: = 0; sealed slot %font-struct = #f; end class ; define sealed domain make (singleton()); define sealed domain initialize (); define abstract class () end class ; define abstract class () sealed constant slot %font-name, required-init-keyword: name:; end class ; define sealed class () end class ; define method condition-to-string (condition :: ) => (string :: ) format-to-string("Font name %s is from a private registry", condition.%font-name) end method condition-to-string; define sealed class () sealed constant slot %start, required-init-keyword: start:; sealed constant slot %end, required-init-keyword: end:; sealed constant slot %token, required-init-keyword: token:; end class ; define method condition-to-string (condition :: ) => (string :: ) format-to-string("Font name %s should have had an integer in %d..%d while looking for %s)", condition.%font-name, condition.%start, condition.%end, condition.%token) end method condition-to-string; /// Decoding X font names define sealed method disassemble-x-font-name (font-name :: ) => => (registry, foundry, family, weight, slant, set-width, add-style, pixel-size, point-size, horiz-dpi, vert-dpi, spacing, avg-width, char-set) let prev-pos :: = 1; let dash-pos :: = 0; local method one-token (token :: ) => (token :: false-or()) ignore(token); dash-pos := position(font-name, '-', start: prev-pos); let result = if (prev-pos = dash-pos) #f else copy-sequence(font-name, start: prev-pos, end: dash-pos) end; prev-pos := dash-pos + 1; result end method, method one-integer (token :: ) => (integer :: false-or()) dash-pos := position(font-name, '-', start: prev-pos); let string = copy-sequence(font-name, start: prev-pos, end: dash-pos); let result = string-to-integer(string); if (result) prev-pos := dash-pos + 1; result else error(make(, name: font-name, start: prev-pos, end: dash-pos, token: token)) end end method, method last-token (token :: ) => (token :: ) ignore(token); copy-sequence(font-name, start: prev-pos) end method; let registry = begin let char0 = font-name[0]; case char0 = '-' => prev-pos := 1; #t; char0 = '+' => one-token("registry"); otherwise => error(make(, name: font-name)) end end; let foundry = one-token("foundry"); let family = one-token("family"); let weight = one-token("weight"); let slant = one-token("slant"); let set-width = one-token("set-width"); let add-style = one-token("add-style"); let pixel-size = one-integer("pixel-size"); let point-size = one-integer("point-size"); let horiz-dpi = one-integer("horizontal-dpi"); let vert-dpi = one-integer("vertical-dpi"); let spacing = one-token("spacing"); let avg-width = one-integer("average-width"); let char-set = last-token("char-set"); values(registry, foundry, family, weight, slant, set-width, add-style, pixel-size, point-size, horiz-dpi, vert-dpi, spacing, avg-width, char-set) end method disassemble-x-font-name; define sealed method disassemble-x-font-name (registry, foundry, family, weight, slant, set-width, add-style, pixel-size, point-size, horiz-dpi, vert-dpi, spacing, avg-width, char-set) => (font-name :: ) //---*** WHAT DOES ~@[~A~] DO??? format-to-string("-%s-%s-%s-%s-%s-~@[~A~]-%d-%d-%d-%d-%s-%d-%s", foundry, family, weight, slant, set-width, add-style, pixel-size, point-size, horiz-dpi, vert-dpi, spacing, average-width, char-set) end method disassemble-x-font-name; /// Font mapping define constant $motif-font-families :: = #(#(#"fix", "courier"), #(#"sans-serif", "helvetica"), #(#"serif", "times", "charter", "new century schoolbook"), #(#"symbol", "symbol")); //--- We should compute the numbers based on either device characteristics //--- or some user option define constant $motif-logical-sizes :: = #[#[#"normal", 10], // put most common one first for efficiency #[#"small", 8], #[#"large", 12], #[#"very-small", 6], #[#"very-large", 14], #[#"tiny", 5], #[#"huge", 18]]; define method install-default-text-style-mappings (_port :: ) => () for (entry in $motif-font-families) let duim-family = head(entry); let x-families = tail(entry); block (break) when (size(x-families) > 1) for (x-family in x-families) when (can-install-entire-family?(_port, duim-family, x-family)) break(install-font-family(_port, duim-family, x-family)) end end end; for (x-family in x-families) install-font-family(_port, duim-family, x-family) end end end end method install-default-text-style-mappings; define method can-install-entire-family? (_port :: , duim-family, x-family :: ) => (can-install? :: ) let x-display = %port.%display; let font-matches = x/XListFonts(x-display, format-to-string("-*-%s-*", x-family)); let nsizes = length($motif-logical-sizes); let fixed-match = make(, dimensions: vector(4, nsizes)); let scaled-match = make(, size: 4); let horiz-dpi = #f; let vert-dpi = #f; for (font-name in font-matches) let (registry, foundry, family, weight, slant, set-width, add-style, pixel-size, point-size, h-dpi, v-dpi, spacing, avg-width, char-set) = disassemble-x-font-name(font-name); ignore(registry, foundry, family, set-width, add-style, spacing, char-set); when (~horiz-dpi & h-dpi > 0 & ~vert-dpi & v-dpi > 0) horiz-dpi := h-dpi; vert-dpi := v-dpi end; let face-code :: = logior(if (string-equal(weight, "bold")) 1 else 0 end, if (string-equal(slant, "i") | string-equal(slant, "o")) 2 else 0 end); let scalable? = (pixel-size = 0 & avg-width = 0); if (scalable?) scaled-match[face-code] := font-name else let index = position($motif-logical-sizes, round/(point-size, 10), test: method (entry, s) second(entry) = s end); when (index) fixed-match[face-code, index] := font-name end end end; block (return) when (horiz-dpi & vert-dpi) for (face-code :: from 0 below 4) unless (scaled-match[face-code] | for (size :: from 0 below n-sizes) unless (fixed-match[face-code, size]) return(#f) end end) return(#f) end end; #t end end end method can-install-entire-family?; define method install-font-family (_port :: , duim-family, x-family :: ) => () let x-display = %port.%display; let font-matches = x/XListFonts(x-display, format-to-string("-*-%s-*", x-family)); let scaled-match = #(); let horiz-dpi = #f; let vert-dpi = #f; for (font-name in font-matches) let (registry, foundry, family, weight, slant, set-width, add-style, pixel-size, point-size, h-dpi, v-dpi, spacing, avg-width, char-set) = disassemble-x-font-name(font-name); ignore(registry, foundry, family, set-width, add-style, spacing, char-set); when (~horiz-dpi & h-dpi > 0 & ~vert-dpi & v-dpi > 0) horiz-dpi := h-dpi; vert-dpi := v-dpi end; let weight = if (string-equal(weight, "bold")) #"bold" else #"normal" end; let slant = if (string-equal(slant, "i") | string-equal(slant, "o")) #"italic" else #"roman" end; let scalable? = (pixel-size = 0 & avg-width = 0); if (scalable?) push!(scaled-match, vector(weight, slant, font-name)) else let index = position($motif-logical-sizes, round/(point-size, 10), test: method (entry, s) second(entry) = s end); let size = index & first($motif-logical-sizes[index]); when (size) let text-style = make-text-style(duim-family, weight, slant, size); unless (text-style-mapping-exists?(_port, text-style, exact-size?: #t)) text-style-mapping(_port, text-style) := font-name end end end end; // Now add any scaleable font mappings for (entry in scaled-match) let weight = entry[0]; let slant = entry[1]; let font-name = entry[2]; for (entry in $motif-logical-sizes) let size = entry[0]; let points = entry[1]; let text-style = make-text-style(duim-family, weight, slant, size); unless (text-style-mapping-exists?(_port, text-style, exact-size?: #t)) text-style-mapping(_port, text-style) := scaleable-font-name-at-size(font-name, points, horiz-dpi, vert-dpi) end end end end method install-font-family; define method scaleable-font-name-at-size (font-name :: , point-size :: , horiz-dpi :: , vertical-dpi :: ) => (font-name :: ) let (registry, foundry, family, weight, slant, set-width, add-style, pixel-size, pt-size, h-dpi, v-dpi, spacing, avg-width, char-set) = disassemble-x-font-name(font-name); ignore(registry, pt-size, h-dpi, v-dpi); assemble-font-name(foundry, family, weight, slant, set-width, add-style, pixel-size, (* point-size 10), horiz-dpi, vert-dpi, spacing, average-width, char-set) end method scaleable-font-name-at-size; define sealed method do-text-style-mapping (_port :: , text-style :: , character-set) => (font :: ) ignore(character-set); let text-style = standardize-text-style(_port, text-style, character-set: character-set); let table :: = port-font-mapping-table(_port); let font = gethash(table, text-style); font | begin //---*** DO THE REST OF THIS let font = make(, ...); gethash(table, text-style) := font; font; end end method do-text-style-mapping; //--- This approach seems unnecessarily clumsy; we might as well just have //--- 'do-text-style-mapping' do the table lookup directly itself. We shouldn't //--- need to cons up a whole new text-style object just to map the size. define sealed method standardize-text-style (_port :: , text-style :: , #rest keys, #key character-set) => (text-style :: ) apply(standardize-text-style-size, _port, text-style, $motif-logical-sizes, keys) end method standardize-text-style; /// Font metrics define sealed inline method font-width (text-style :: , _port :: , #rest keys, #key character-set) => (width :: ) let (font, width, height, ascent, descent) = apply(font-metrics, text-style, _port, keys); ignore(font, height, ascent, descent); width end method font-width; define sealed inline method font-height (text-style :: , _port :: , #rest keys, #key character-set) => (height :: ) let (font, width, height, ascent, descent) = apply(font-metrics, text-style, _port, keys); ignore(font, width, ascent, descent); height end method font-height; define sealed inline method font-ascent (text-style :: , _port :: , #rest keys, #key character-set) => (ascent :: ) let (font, width, height, ascent, descent) = apply(font-metrics, text-style, _port, keys); ignore(font, width, height, descent); ascent end method font-ascent; define sealed inline method font-descent (text-style :: , _port :: , #rest keys, #key character-set) => (descent :: ) let (font, width, height, ascent, descent) = apply(font-metrics, text-style, _port, keys); ignore(font, width, height, ascent); descent end method font-descent; define sealed inline method fixed-width-font? (text-style :: , _port :: , #key character-set) => (fixed? :: ) let font = font-metrics(text-style, _port); let struct = font.%font-struct; empty?(struct.x/per-char-value) end method fixed-width-font?; define sealed method font-metrics (text-style :: , _port :: , #rest keys, #key character-set) => (font, width :: , height :: , ascent :: , descent :: ) let font :: = apply(text-style-mapping, _port, text-style, keys); motif-font-metrics(font, _port) end method font-metrics; define sealed method motif-font-metrics (font :: , _port :: ) => (font :: , width :: , height :: , ascent :: , descent :: ) unless (font.%font-struct) let x-display = port.%display; font.%font-id := x/XLoadFont(x-display, font.%font-name); font.%font-struct := x/XQueryFont(x-display, font.%font-id) end; let struct = font.%font-struct; values(font, struct.x/max-bounds-value.x/width-value, struct.x/ascent-value + struct.x/descent-value, struct.x/ascent-value, struct.x/descent-value) end method motif-font-metrics; /// Text measurement define sealed method text-size (_port :: , char :: , #key text-style :: = $default-text-style, start: _start, end: _end, do-newlines? = #f, do-tabs? = #f) => (largest-x :: , largest-y :: , cursor-x :: , cursor-y :: , baseline :: ) ignore(_start, _end, do-newlines?, do-tabs?); let string = make(, size: 1, fill: char); text-size(_port, string, text-style: text-style) end method text-size; //---*** What do we do about Unicode strings? define sealed method text-size (_port :: , string :: , #key text-style :: = $default-text-style, start: _start, end: _end, do-newlines? = #f, do-tabs? = #f) => (largest-x :: , largest-y :: , cursor-x :: , cursor-y :: , baseline :: ) let length :: = size(string); let _start :: = _start | 0; let _end :: = _end | length; let (font :: , width, height, ascent, descent) = font-metrics(text-style, _port); ignore(width, height); local method measure-string (font :: , string :: , _start :: , _end :: ) => (x1 :: , y1 :: , x2 :: , y2 :: ) let substring = if (_start = 0 & _end = size(string)) string else copy-sequence(string, start: _start, end: _end) end; with-c-string (c-string = substring) with-stack-structure (overall :: x/) let (direction, max-ascent, max-descent) = x/XTextExtents(font.%font-struct, substring, overall); ignore(direction, max-ascent, max-descent); //---*** IS THIS REALLY RIGHT? values(0, overall.x/width-value, 0, overall.x/ascent-value + overall.x/descent-value) end end end method; case do-tabs? & do-newlines? => next-method(); // the slow case... do-tabs? => let tab-width :: = width * 8; let last-x :: = 0; let last-y :: = 0; let s :: = _start; block (return) while (#t) let e = position(string, '\t', start: s, end: _end); let (x1, y1, x2, y2) = measure-string(font, string, s, e); ignore(x1); if (e = _end) last-x := last-x + x2 else last-x := floor/(last-x + x2 + tab-width, tab-width) * tab-width; end; max!(last-y, y2 - y1); s := min(e + 1, _end); when (e = _end) return(last-x, last-y, last-x, last-y, ascent) end end end; do-newlines? => let largest-x :: = 0; let largest-y :: = 0; let last-x :: = 0; let last-y :: = 0; let s :: = _start; block (return) while (#t) let e = position(string, '\n', start: s, end: _end); let (x1, y1, x2, y2) = measure-string(font, string, s, e); ignore(x1); max!(largest-x, x2); last-x := x2; inc!(largest-y, y2 - y1); last-y := y2; s := min(e + 1, _end); when (e = _end) return(largest-x, largest-y, last-x, last-y, ascent) end end end; otherwise => let (x1, y1, x2, y2) = measure-string(font, string, _start, _end); ignore(x1); values(x2, y2 - y1, x2, y2 - y1, ascent); end end method text-size;