Raymond Toy pushed to branch issue-111-fixes-for-motifd-clm at cmucl / cmucl

Commits:

5 changed files:

Changes:

  • src/interface/initial.lisp
    ... ... @@ -22,6 +22,8 @@
    22 22
       (:use "TOOLKIT" "LISP" "EXTENSIONS" "KERNEL")
    
    23 23
       (:shadow "CLASS-DIRECT-SUPERCLASSES")
    
    24 24
       (:export "*INTERFACE-STYLE*" "+HEADER-TAG+" "+ITALIC-TAG+"
    
    25
    +           "*DEFAULT-FONT-NAME*" "*HEADER-FONT-NAME*" "*ITALIC-FONT-NAME*"
    
    26
    +           "*AMBIGUOUS-FONT-DISPOSITION*"
    
    25 27
     	   "USE-GRAPHICS-INTERFACE" "VERIFY-SYSTEM-SERVER-EXISTS"
    
    26 28
     	   "CREATE-INTERFACE-SHELL" "POPUP-INTERFACE-PANE"
    
    27 29
     	   "CREATE-INTERFACE-PANE-SHELL" "FIND-INTERFACE-PANE"
    

  • src/interface/interface.lisp
    ... ... @@ -64,6 +64,13 @@
    64 64
     (defconstant +header-tag+ "header")
    
    65 65
     (defconstant +italic-tag+ "italic")
    
    66 66
     
    
    67
    +;; Default fonts. Users are allowed to customize these. Note that
    
    68
    +;; changing them only takes effect the next time these interface
    
    69
    +;; programs start a fresh motifd process.
    
    70
    +(defvar *default-font-name* "Helvetica-12:Regular")
    
    71
    +(defvar *header-font-name* "Helvetica-12:Bold")
    
    72
    +(defvar *italic-font-name* "Helvetica-12:Italic")
    
    73
    +
    
    67 74
     
    
    68 75
     
    
    69 76
     ;;;; Functions for dealing with interface widgets
    
    ... ... @@ -74,7 +81,10 @@
    74 81
           (let ((con (xt::open-motif-connection
    
    75 82
     		  *default-server-host* *default-display*
    
    76 83
     		  "lisp" "Lisp"
    
    77
    -		  nil ;; fallback resources go here.
    
    84
    +		  (generate-heuristicated-font-resources
    
    85
    +		   (list "" +header-tag+ +italic-tag+)
    
    86
    +		   (list *default-font-name* *header-font-name*
    
    87
    +			 *italic-font-name*))
    
    78 88
     		  (and *system-motif-server*
    
    79 89
     		       (ext:process-pid *system-motif-server*)))))
    
    80 90
     	(with-motif-connection (con)
    

  • src/motif/lisp/fonts.lisp
    1
    +;;;; -*- Mode: Lisp ; Package: Toolkit -*-
    
    2
    +
    
    3
    +(ext:file-comment "$Header: src/motif/lisp/fonts.lisp $")
    
    4
    +
    
    5
    +;;; fonts.lisp -- some machinery for unifying the naming of
    
    6
    +;;; traditional Core X11 Fonts with Xft2 fonts. Conceptually almost
    
    7
    +;;; all of this this file is not specific to CLM (it's mostly parsing
    
    8
    +;;; and some invented heuristics/conventions that could be useful in
    
    9
    +;;; X11 context), but it currently only models the minimum properties
    
    10
    +;;; of font names necessary to generate the resource specifications
    
    11
    +;;; OpenMotif uses to configure fonts. However, the interfaces in this
    
    12
    +;;; file were designed to permit retrofitting in a richer model
    
    13
    +;;; non-disruptively.
    
    14
    +
    
    15
    +(in-package "TOOLKIT")
    
    16
    +
    
    17
    +;; For reasons that'll be explained as we go, we need to parse (or at
    
    18
    +;; least validate) font name strings. Here's the base class for
    
    19
    +;; parsing errors.
    
    20
    +(define-condition font-name-parse-error (parse-error)
    
    21
    +  ((kind :initarg :kind :reader font-name-parse-error-kind)
    
    22
    +   (string :initarg :string :reader font-name-parse-error-string)
    
    23
    +   (index :initarg :index :reader font-name-parse-error-index)
    
    24
    +   (description :initarg :description
    
    25
    +		:reader font-name-parse-error-description))
    
    26
    +  (:default-initargs :kind nil :description nil)
    
    27
    +  (:documentation "Class of error signaled when a string can't be parsed as a font name.")
    
    28
    +  (:report
    
    29
    +   (lambda (error stream)
    
    30
    +     (format stream
    
    31
    +	     "Parsing ~S as a font-name~@[ according to ~A syntax~] ended at ~D~@[ ~A~]."
    
    32
    +	     (font-name-parse-error-string error)
    
    33
    +	     (font-name-parse-error-kind error)
    
    34
    +	     (font-name-parse-error-index error)
    
    35
    +	     (font-name-parse-error-description error)))))
    
    36
    +
    
    37
    +;; As mentioned, this file currently only offers a trivial model of
    
    38
    +;; font specifications. The representation of parsed font names is
    
    39
    +;; *not* part of the interface, and subject to change. To insulate
    
    40
    +;; prospective clients from that detail, here are some types.
    
    41
    +(deftype core-font-name ()
    
    42
    +  "Instances of this type are for use with the Core X11 Font system."
    
    43
    +  '(satisfies core-font-name-p))
    
    44
    +(deftype xlfd-name ()
    
    45
    +  "Subtype of CORE-FONT-NAME for XLFD names."
    
    46
    +  '(satisfies xlfd-name-p))
    
    47
    +(deftype fontconfig-name ()
    
    48
    +  "Instances of this type are for use with the Xft2 font system."
    
    49
    +  '(satisfies fontconfig-name-p))
    
    50
    +
    
    51
    +;; Core X11 Font names are just strings, ultimately transmitted to the
    
    52
    +;; X server for resolution. In general core fonts' names are strings
    
    53
    +;; that are opaque to clients. We'll wrap them in an object for
    
    54
    +;; discrimination, and let DEFSTRUCT define the predicate we use for
    
    55
    +;; the DEFTYPE above.
    
    56
    +(defstruct (core-font-name (:type vector) :named (:copier nil))
    
    57
    +  (string "" :type string :read-only t))
    
    58
    +
    
    59
    +;; Core X11 Font names can be in XLFD format, but they might
    
    60
    +;; not be (e.g., aliases are unlikely to be in XLFD format). Here's
    
    61
    +;; the XLFD spec:
    
    62
    +
    
    63
    +;; https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/XLFD/xlfd.html
    
    64
    +
    
    65
    +;; For now, we don't really need a detailed parse of an XLFD, but
    
    66
    +;; we'll pretend as if we've got one. In fact our parser will merely
    
    67
    +;; validate the string and then cons up an object for which we've got
    
    68
    +;; a predicate.
    
    69
    +(defstruct (xlfd-name (:type vector) :named (:copier nil)
    
    70
    +		      (:include core-font-name)))
    
    71
    +
    
    72
    +(define-condition xlfd-name-parse-error
    
    73
    +    (font-name-parse-error)
    
    74
    +  ()
    
    75
    +  (:default-initargs :kind "XLFD"))
    
    76
    +
    
    77
    +;; A proper XLFD has 14 hyphens, so 15 fields (inclusive of the
    
    78
    +;; registry, which must be the empty string).
    
    79
    +(defconstant +xlfd-field-count+ 15)
    
    80
    +
    
    81
    +;; Even though we don't really need a structured XLFD parse, our
    
    82
    +;; heuristics require code for validating well-formedness of an XLFD
    
    83
    +;; (14 hyphens, optionally excluding wildcards). JUNK-ALLOWED follows
    
    84
    +;; the ANSI CL convention. WILDCARD-ALLOWED is just a convenience. It
    
    85
    +;; seems that Xorg and Xquartz treat subsets of well-formed XLFDs as
    
    86
    +;; usable font names, so this also supports a keyword to make it okay
    
    87
    +;; to have fewer than 15 fields).
    
    88
    +(defun parse-xlfd-name (string &key (start 0) end
    
    89
    +					  junk-allowed subsequence-allowed
    
    90
    +					  wildcard-allowed)
    
    91
    +  "Parse STRING bounded by START and END as an X Logical Font
    
    92
    +Description. If parsing succeeds, return an object for which
    
    93
    +XLFD-NAME-P returns true and the index at which parsing
    
    94
    +ended. Exceptional conditions: if STRING has a registry but doesn't
    
    95
    +have enough fields (13), then signal an error if SUBSEQUENCE-ALLOWED
    
    96
    +is false (the default); if string contains a delimiter after the 13th
    
    97
    +field, signal an error if JUNK-ALLOWED is false (the default). If
    
    98
    +SUBSEQUENCE-ALLOWED is true or JUNK-ALLOWED is true, then return NIL
    
    99
    +and the index at which parsing stopped. If WILDCARD-ALLOWED is
    
    100
    +false (the default), wildcard characters will cause parsing to end at
    
    101
    +the first wildcard character (and so the consequences will depend on
    
    102
    +JUNK-ALLOWED); otherwise, wildcard characters will be treated as field
    
    103
    +contents."
    
    104
    +  (setq end (or end (length string)))
    
    105
    +  (let ((index start) (field-count 0))
    
    106
    +    (labels
    
    107
    +	(;; This is the only way out of PARSE-XLFD-NAME.  It
    
    108
    +	 ;; implements all the SUBSEQUENCE-ALLOWED and JUNK-ALLOWED
    
    109
    +	 ;; logic. Callers can supply arguments to enrich the error
    
    110
    +	 ;; report, though it's not the caller's job to decide whether
    
    111
    +	 ;; we've succeeded or not.
    
    112
    +	 (finish-parsing (&rest error-description)
    
    113
    +	   (if (and (or (= field-count +xlfd-field-count+)
    
    114
    +			(and (plusp field-count) subsequence-allowed))
    
    115
    +		    (or (= index end) junk-allowed))
    
    116
    +	       (return-from parse-xlfd-name
    
    117
    +		 (values
    
    118
    +		  (make-xlfd-name
    
    119
    +		   :string (subseq string start index))
    
    120
    +		  index))
    
    121
    +	       (error 'xlfd-name-parse-error
    
    122
    +		      :string (subseq string start end)
    
    123
    +		      :index (- index start)
    
    124
    +		      :description
    
    125
    +		      (apply #'format nil
    
    126
    +			     (if error-description
    
    127
    +				 error-description
    
    128
    +				 (if (< field-count
    
    129
    +					+xlfd-field-count+)
    
    130
    +				     (list "with too few fields (~D)"
    
    131
    +					   field-count)
    
    132
    +				     '("with trailing junk")))))))
    
    133
    +	 (next-token ()
    
    134
    +	   (loop
    
    135
    +	      (when (= index end)
    
    136
    +		(return))
    
    137
    +	      (when (> (- index start) 255)
    
    138
    +		(finish-parsing "due to length limits"))
    
    139
    +	      (let ((char (char string index)))
    
    140
    +		(cond
    
    141
    +		  ;; Fields must be ISO-8859-1 strings.
    
    142
    +		  ((> (char-code char) 255)
    
    143
    +		   (finish-parsing "due to non-ISO-8859-1 character, ~@C" char))
    
    144
    +		  ;; Explicitly disallowed in field values.
    
    145
    +		  ((char= #\" char)
    
    146
    +		   (finish-parsing "due to a double-quote"))
    
    147
    +		  ;; Conditionally allowed.
    
    148
    +		  ((and (find (char string index) '(#\? #\*))
    
    149
    +			(not wildcard-allowed))
    
    150
    +		   (finish-parsing "due to wildcard character, ~@C" char))
    
    151
    +		  ;; Field delimiter character, unescapable.
    
    152
    +		  ((char= #\- char)
    
    153
    +		   (return))
    
    154
    +		  (t (incf index)))))
    
    155
    +	   (progn (incf field-count)
    
    156
    +		  (values index
    
    157
    +			  ;; Leave INDEX at END when we're at end of string
    
    158
    +			  (when (< index end)
    
    159
    +			    (prog1 (char string index)
    
    160
    +			      (incf index)))))))
    
    161
    +      (unless (< index end)
    
    162
    +	(finish-parsing "because the bounded string was empty"))
    
    163
    +      (let ((registry-end (next-token)))
    
    164
    +	(when (> registry-end start)
    
    165
    +	  (finish-parsing "due to unsupported font name registry ~S"
    
    166
    +			  (subseq string start registry-end))))
    
    167
    +      (loop
    
    168
    +	 (let ((delimiter (nth-value 1 (next-token))))
    
    169
    +	   (when (= field-count +xlfd-field-count+)
    
    170
    +	     (when delimiter
    
    171
    +	       (decf index))
    
    172
    +	     (finish-parsing))
    
    173
    +	   (when (null delimiter)
    
    174
    +	     (finish-parsing)))))))
    
    175
    +
    
    176
    +;; Several test cases for PARSE-XLFD-NAME.
    
    177
    +#+(or)
    
    178
    +(macrolet
    
    179
    +  ((test-okay (results string &rest args)
    
    180
    +	      `(assert (equalp (ignore-errors
    
    181
    +				(multiple-value-list
    
    182
    +				 (parse-xlfd-name ,string ,@args)))
    
    183
    +			       ',(if results
    
    184
    +				     results
    
    185
    +				     (list (vector 'core-font-name string 'xlfd-name)
    
    186
    +					   (length string))))))
    
    187
    +   (test-fail (msg string &rest args &aux (result (gensym)) (error (gensym)))
    
    188
    +	      `(multiple-value-bind (,result ,error)
    
    189
    +		   (ignore-errors
    
    190
    +		    (multiple-value-list
    
    191
    +		     (parse-xlfd-name ,string ,@args)))
    
    192
    +		 (assert (null ,result))
    
    193
    +		 (assert (typep ,error 'xlfd-name-parse-error))
    
    194
    +		 ,(when msg
    
    195
    +		    `(assert (search ',msg (princ-to-string ,error)))))))
    
    196
    +  (test-okay nil "--------------")
    
    197
    +  ;; By default, an XLFD with fewer than 14 hyphens is an error.
    
    198
    +  (test-fail nil "--------")
    
    199
    +  ;; But :SUBSEQUENCE-ALLOWED T will make it allowed.
    
    200
    +  (test-okay nil "--------" :subsequence-allowed t)
    
    201
    +  ;; By default, a string that has more than 14 fields is an error
    
    202
    +  (test-fail nil "--------------nope-")
    
    203
    +  ;; But :JUNK-ALLOWED T will make it allowed.
    
    204
    +  (test-okay (#(core-font-name "--------------nope" xlfd-name)
    
    205
    +	       18)
    
    206
    +	     "--------------nope" :junk-allowed t)
    
    207
    +  ;; By default, wildcards are disallowed.
    
    208
    +  (test-fail nil "-*-------------")
    
    209
    +  (test-okay nil "-*-------------" :wildcard-allowed t)
    
    210
    +  (test-fail nil "--------------*")
    
    211
    +  (test-okay nil "--------------*" :wildcard-allowed t))
    
    212
    +
    
    213
    +;; Xft2 doesn't strictly have its own font names; it uses fontconfig
    
    214
    +;; for naming. Fontconfig has a syntax for specifying fonts; here's
    
    215
    +;; the spec for that:
    
    216
    +
    
    217
    +;; https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
    
    218
    +
    
    219
    +;; For Motif-y reasons explained below, we must parse a few properties
    
    220
    +;; out of fontconfig names. We'll ignore properties we don't care
    
    221
    +;; about. We'll use the same basic idea as above: a lightweight
    
    222
    +;; representation of the stuff we need, a PARSE-ERROR subclass, and a
    
    223
    +;; parsing function.
    
    224
    +(defstruct (fontconfig-name (:type vector) :named (:copier nil))
    
    225
    +  (foundry nil :type (or null string) :read-only t)
    
    226
    +  (family "" :type string :read-only t)
    
    227
    +  ;; TODO: SIZE is really a number, but the fontconfig spec doesn't
    
    228
    +  ;; document the number format, so for the moment it's a string.
    
    229
    +  ;; Probably this ought to get fixed before contemplating exporting
    
    230
    +  ;; the accessor name.
    
    231
    +  (size nil :type (or null string) :read-only t)
    
    232
    +  (weight nil :type (or null string) :read-only t)
    
    233
    +  (slant nil :type (or null string) :read-only t)
    
    234
    +  ;; This isn't a proper part of a model of a fontconfig name, just an
    
    235
    +  ;; internal trick for the heuristics that follow.
    
    236
    +  (has-properties-p nil :type boolean :read-only t))
    
    237
    +
    
    238
    +(define-condition fontconfig-name-parse-error
    
    239
    +    (font-name-parse-error)
    
    240
    +  ()
    
    241
    +  (:default-initargs :kind "fontconfig"))
    
    242
    +
    
    243
    +;; This routine attempts to implement a fairly strict idea of
    
    244
    +;; well-formedness for fontconfig specs. Any functional disagreement
    
    245
    +;; with fontconfig over the domain of well-formed fontconfig names is
    
    246
    +;; a bug. (fontconfig's matching of strings that aren't well-formed
    
    247
    +;; fontconfig names is none of our business.)
    
    248
    +(defun parse-fontconfig-name (string &key (start 0) end junk-allowed)
    
    249
    +  (setq end (or end (length string)))
    
    250
    +  (let (foundry family size weight slant has-properties-p
    
    251
    +		(index start) part-end)
    
    252
    +    (labels
    
    253
    +	(;; This is the only way out of
    
    254
    +	 ;; PARSE-FONTCONFIG-NAME.
    
    255
    +	 (finish-parsing (&rest error-description)
    
    256
    +	   (if (or (= part-end end) junk-allowed)
    
    257
    +	       (return-from parse-fontconfig-name
    
    258
    +		 (values
    
    259
    +		  (when family
    
    260
    +		    (make-fontconfig-name
    
    261
    +		     :family family :foundry foundry :size size
    
    262
    +		     :weight weight :slant slant
    
    263
    +		     :has-properties-p
    
    264
    +		     (or has-properties-p slant weight foundry)))
    
    265
    +		  ;; Parsing always ends at the index of the end of
    
    266
    +		  ;; the part of the name that parsed, even if there's
    
    267
    +		  ;; junk after.
    
    268
    +		  part-end))
    
    269
    +	       (error 'fontconfig-name-parse-error
    
    270
    +		      :string (subseq string start end)
    
    271
    +		      :index (- index start)
    
    272
    +		      :description (when error-description
    
    273
    +				     (apply #'format nil error-description)))))
    
    274
    +	 ;; Parse the next token starting at INDEX, delimited by any
    
    275
    +	 ;; character in DELIMITERS. Note that the family and any
    
    276
    +	 ;; property value use backslash to escape the delimiter, but
    
    277
    +	 ;; the size and property name are not documented as allowing
    
    278
    +	 ;; an escape character. Returns a non-empty token, the
    
    279
    +	 ;; delimiter that ended the token, and the delimiter's index.
    
    280
    +	 (next-token (delimiters &optional (escapep t))
    
    281
    +	   (do ((chars)
    
    282
    +		(char (and (< index end) (char string index))
    
    283
    +		      (and (< index end) (char string index))))
    
    284
    +	       ((or (null char) (find char delimiters))
    
    285
    +		(multiple-value-prog1
    
    286
    +		    (values (when chars
    
    287
    +			      (coerce (nreverse chars) 'string))
    
    288
    +			    char
    
    289
    +			    index)
    
    290
    +		  (incf index)))
    
    291
    +	     (when (and escapep (char= #\\ char))
    
    292
    +	       (when (= index end)
    
    293
    +		 (finish-parsing "after the escape character"))
    
    294
    +	       (incf index)
    
    295
    +	       (setq char (char string index)))
    
    296
    +	     (push char chars)
    
    297
    +	     (incf index)))
    
    298
    +	 ;; The fontconfig spec doesn't say whether names & their
    
    299
    +	 ;; components are matched case-sensitively or
    
    300
    +	 ;; case-insensitively. It seems as if it's insensitive, but
    
    301
    +	 ;; let's factor it here just in case.
    
    302
    +	 (string-equiv (s1 s2)
    
    303
    +	   (string-equal s1 s2)))
    
    304
    +      (let (delimiter token-end)
    
    305
    +	(multiple-value-setq (family delimiter token-end)
    
    306
    +	  (next-token '(#\- #\:)))
    
    307
    +	(when (null family)
    
    308
    +	  (finish-parsing "without any family"))
    
    309
    +	;; fontconfig names allow for a comma-separated list of
    
    310
    +	;; families. TODO: check if Motif can handle such lists.
    
    311
    +	;; Pending that, make it an error to find a comma in the name.
    
    312
    +	;; This is a defect in this parser.
    
    313
    +	(when (find #\, family)
    
    314
    +	  (finish-parsing "with an unsupported syntax (list of families))"))
    
    315
    +	;; If we're here, the family is acceptable, so we've reached
    
    316
    +	;; the end of this part. Save it for FINISH-PARSING.
    
    317
    +	(setq part-end token-end)
    
    318
    +	(when (eql #\- delimiter)
    
    319
    +	  (multiple-value-setq	(size delimiter token-end)
    
    320
    +	    (next-token '(#\:) nil))
    
    321
    +	  ;; TODO, maybe: validate that SIZE parses as a number. (But
    
    322
    +	  ;; first figure out what the number syntax is; the
    
    323
    +	  ;; fontconfig spec doesn't say.)
    
    324
    +	  (unless size
    
    325
    +	    (finish-parsing "with a hyphen")))
    
    326
    +	;; TODO: check if Motif supports lists of sizes.
    
    327
    +	;; This is a defect in this parser.
    
    328
    +	(when (find #\, size)
    
    329
    +	  (finish-parsing "with an unsupported syntax (list of sizes))"))
    
    330
    +	(setq part-end token-end)
    
    331
    +	(when (eql #\: delimiter)      ;There are properties to parse.
    
    332
    +	  (let (name value tmp-end)
    
    333
    +	    (loop
    
    334
    +	       (setq part-end token-end)
    
    335
    +	       ;; We must not set TOKEN-END until we know we've parsed
    
    336
    +	       ;; a whole property. So we'll use TMP-END.
    
    337
    +	       (multiple-value-setq (name delimiter tmp-end)
    
    338
    +		 (next-token '(#\= #\:) nil))
    
    339
    +	       (if (null name)
    
    340
    +		   (ecase delimiter
    
    341
    +		     (#\=
    
    342
    +		      (finish-parsing "with an empty property name"))
    
    343
    +		     (#\:
    
    344
    +		      (finish-parsing "with an empty property"))
    
    345
    +		     ((nil)
    
    346
    +		      (finish-parsing "with a colon")))
    
    347
    +		   (ecase delimiter
    
    348
    +		     (#\=
    
    349
    +		      (multiple-value-setq (value delimiter tmp-end)
    
    350
    +			(next-token '(#\:)))
    
    351
    +		      (when (null value)
    
    352
    +			(finish-parsing "with an empty property value"))
    
    353
    +		      (setq token-end tmp-end
    
    354
    +			    has-properties-p t)
    
    355
    +		      ;; These are the only properties we care about.
    
    356
    +		      (cond ((string-equiv name "weight")
    
    357
    +			     (setq weight value))
    
    358
    +			    ((string-equiv name "slant")
    
    359
    +			     (setq slant value))
    
    360
    +			    ((string-equiv name "foundry")
    
    361
    +			     (setq foundry value))))
    
    362
    +		     ((#\: nil)
    
    363
    +		      ;; In this case, the property might be a
    
    364
    +		      ;; "symbolic constant" The fontconfig spec says
    
    365
    +		      ;; "there are symbolic constants that
    
    366
    +		      ;; simultaneously indicate both a name and a
    
    367
    +		      ;; value", but it's not clear what those
    
    368
    +		      ;; constants are. We'll assume that any
    
    369
    +		      ;; construct is both syntactically valid here.
    
    370
    +		      (setq token-end tmp-end
    
    371
    +			    has-properties-p t)
    
    372
    +		      ;; We need to recognize whatever symbolic
    
    373
    +		      ;; constants are defined for the weight and
    
    374
    +		      ;; slant properties. These are taken from the
    
    375
    +		      ;; description of the <const> element of the
    
    376
    +		      ;; configuration file format, in case that's
    
    377
    +		      ;; what's intended in the fontconfig spec.
    
    378
    +		      (cond ((member name
    
    379
    +				     '("thin"
    
    380
    +				       "extralight"
    
    381
    +				       "ultralight"
    
    382
    +				       "light"
    
    383
    +				       "demilight"
    
    384
    +				       "semilight"
    
    385
    +				       "book"
    
    386
    +				       "regular"
    
    387
    +				       "normal"
    
    388
    +				       "medium"
    
    389
    +				       "demibold"
    
    390
    +				       "semibold"
    
    391
    +				       "bold"
    
    392
    +				       "extrabold"
    
    393
    +				       "black"
    
    394
    +				       "heavy")
    
    395
    +				     :test #'string-equiv)
    
    396
    +			     (setq weight name))
    
    397
    +			    ((member name
    
    398
    +				     '("roman"
    
    399
    +				       "italic"
    
    400
    +				       "oblique"
    
    401
    +				       "ultracondensed"
    
    402
    +				       "extracondensed"
    
    403
    +				       "condensed"
    
    404
    +				       "semicondensed"
    
    405
    +				       "normal"
    
    406
    +				       "semiexpanded"
    
    407
    +				       "expanded"
    
    408
    +				       "extraexpanded"
    
    409
    +				       "ultraexpanded")
    
    410
    +				     :test #'string-equiv)
    
    411
    +			     (setq slant name))))))))))
    
    412
    +      (finish-parsing))))
    
    413
    +
    
    414
    +;; Some test cases for PARSE-FONTCONFIG-NAME.
    
    415
    +#+(or)
    
    416
    +(macrolet
    
    417
    +    ((test-okay (results string &rest args)
    
    418
    +       `(assert (equalp (ignore-errors
    
    419
    +			 (multiple-value-list
    
    420
    +			  (parse-fontconfig-name ,string ,@args)))
    
    421
    +			',(if (listp results)
    
    422
    +			      results
    
    423
    +			      (list results (length string))))))
    
    424
    +     (test-fail (msg string &rest args &aux (result (gensym)) (error (gensym)))
    
    425
    +       `(multiple-value-bind (,result ,error)
    
    426
    +	    (ignore-errors
    
    427
    +	     (multiple-value-list
    
    428
    +	      (parse-fontconfig-name ,string ,@args)))
    
    429
    +	  (assert (null ,result))
    
    430
    +	  (assert (typep ,error 'fontconfig-name-parse-error))
    
    431
    +	  ,(when msg
    
    432
    +	     `(assert (search ',msg (princ-to-string ,error)))))))
    
    433
    +  ;; Just a name
    
    434
    +  (test-okay #(fontconfig-name nil "Foo" nil nil nil nil) "Foo")
    
    435
    +  ;; Name and size
    
    436
    +  (test-okay #(fontconfig-name nil "Foo" "12" nil nil nil) "Foo-12")
    
    437
    +  ;; This fully specifies everything we care about.
    
    438
    +  (test-okay #(fontconfig-name "Bar" "Foo" "12" "bold" "italic" t)
    
    439
    +	     "Foo-12:foundry=Bar:slant=italic:weight=bold")
    
    440
    +  ;; Same as previous, but with extra junk (which should be ignored).
    
    441
    +  (test-okay #(fontconfig-name "Bar" "Foo" "12" "bold" "italic" t)
    
    442
    +	     "Foo-12:abc=def:foundry=Bar:xyz=123:slant=italic:weight=bold")
    
    443
    +  ;; Test recognition of symbolic constants for weight and slant.
    
    444
    +  (test-okay #(fontconfig-name nil "Foo" "12" "bold" "italic" t)
    
    445
    +	     "Foo-12:italic:bold")
    
    446
    +  ;; Test recognition that a font has properties (even if we don't
    
    447
    +  ;; know what they are).
    
    448
    +  (test-okay #(fontconfig-name nil "Foo" "12" nil nil t)
    
    449
    +	     "Foo-12:bar=baz")
    
    450
    +  ;; Test various invalid (I think) things.
    
    451
    +  (test-fail "with a hyphen" "Foo-")
    
    452
    +  (test-fail "with a colon" "Foo:")
    
    453
    +  (test-fail "with a colon" "Foo-12:")
    
    454
    +  (test-fail "empty property" "Foo::")
    
    455
    +  (test-fail "empty property" "Foo-12::")
    
    456
    +  (test-fail "empty property name" "Foo:=bar")
    
    457
    +  (test-fail "empty property value" "Foo:bar="))
    
    458
    +
    
    459
    +;; Now that we have font name parsers, let's build a convention for
    
    460
    +;; figuring out when to apply them. Every octet string up to length
    
    461
    +;; 255 is a syntactically valid Core X11 Font name; and fontconfig
    
    462
    +;; appears not to care whether its input strings are well-formed
    
    463
    +;; fontconfig names. So in principle all strings (modulo length and
    
    464
    +;; encoding) might be "usable" as a font names in either system.
    
    465
    +;;
    
    466
    +;; However, in practice, most Core X11 Fonts have XLFD names, and
    
    467
    +;; fontconfig's behavior is more predictable when its inputs are
    
    468
    +;; well-formed and detailed fontconfig names. Therefore, it seems
    
    469
    +;; reasonable to build up some heuristics:
    
    470
    +;;
    
    471
    +;; 1. a string that starts with a hyphen is an XLFD (fontconfig name
    
    472
    +;; can't start with hyphens).
    
    473
    +;;
    
    474
    +;; 2. a string that's a well-formed fontconfig name containing a colon
    
    475
    +;; is a fontconfig name (colons don't seem much used in Core X11 Font
    
    476
    +;; names).
    
    477
    +;;
    
    478
    +;; Here are two helper routines that implement those heuristics.  Note
    
    479
    +;; that these two don't partition all strings, e.g., "Times" or
    
    480
    +;; "Helvetica-12" won't satisfy either predicate. We'll address those
    
    481
    +;; "ambiguous" cases below.
    
    482
    +(defun xlfdp (thing)
    
    483
    +  "Returns true if THING represents an X Logical Font Description, either
    
    484
    +as an XLFD string or the parse of one."
    
    485
    +  (etypecase thing
    
    486
    +    (xlfd-name
    
    487
    +     ;; Note that objects that satisfy this predicate might have been
    
    488
    +     ;; created by PARSE-XLFD-NAME calls with non-default
    
    489
    +     ;; flags, and so may not be well-formed XLFDs on their own.  If
    
    490
    +     ;; the user had the context to do that, then we're not going to
    
    491
    +     ;; overrule the decision.
    
    492
    +     thing)
    
    493
    +    (string (nth-value
    
    494
    +	     0
    
    495
    +	     (ignore-errors
    
    496
    +	      (parse-xlfd-name
    
    497
    +	       thing
    
    498
    +	       ;; These initargs are arbitrary, but appear to agree
    
    499
    +	       ;; with what my X server seems to consider acceptable
    
    500
    +	       ;; arguments to XOpenFont.
    
    501
    +	       :junk-allowed nil :subsequence-allowed t
    
    502
    +	       :wildcard-allowed t))))))
    
    503
    +
    
    504
    +(defun fontconfigp (thing)
    
    505
    +  "Returns true in case THING is probably a fontconfig name:
    
    506
    +either a parsed fontconfig name, or a string that parses to a
    
    507
    +fontconfig name having explicit properties."
    
    508
    +  (etypecase thing
    
    509
    +    (fontconfig-name
    
    510
    +     thing)
    
    511
    +    (string
    
    512
    +     (let ((thing (ignore-errors
    
    513
    +		   (parse-fontconfig-name thing))))
    
    514
    +       (when (and thing (fontconfig-name-has-properties-p thing))
    
    515
    +	 thing)))))
    
    516
    +
    
    517
    +;; So now we've got heuristic detection of XLFD and fontconfig
    
    518
    +;; names. Disambiguating other strings in isolation is inherently
    
    519
    +;; arbitrary. However, when we've got an opportunity to look at a set
    
    520
    +;; of strings, we can disambiguate using context: let's assume that if
    
    521
    +;; any string is an XLFD, then all ambiguous strings are meant as Core
    
    522
    +;; X11 Font names; that if any string is a well-formed fontconfig name
    
    523
    +;; with properties, then all ambiguous strings are also for Xft2; that
    
    524
    +;; if all strings are ambiguous, we'll fallthru to consulting a
    
    525
    +;; variable.
    
    526
    +(defvar *ambiguous-font-disposition* :xft2)
    
    527
    +(declaim (type (member :xft2 :core) *ambiguous-font-disposition*))
    
    528
    +
    
    529
    +;; Finally, it seems that using Core X11 Fonts with Xft2 fonts within
    
    530
    +;; a single RenderTable that doesn't work in OpenMotif circa 2021. (I
    
    531
    +;; couldn't figure it out, anyhow.) And maybe nobody would want to do
    
    532
    +;; so anyway. So for now we'll rule out mix-and-match scenarios.
    
    533
    +(defun heuristicate-font-name-types (names)
    
    534
    +  (assert (every #'stringp names))
    
    535
    +  (flet ((parse-as-core-fonts ()
    
    536
    +	   (mapcar #'(lambda (spec)
    
    537
    +		       (or (xlfdp spec) (make-core-font-name :string spec)))
    
    538
    +		   names))
    
    539
    +	 (parse-as-xft2-fonts ()
    
    540
    +	   (mapcar #'parse-fontconfig-name names)))
    
    541
    +    (cond ((some #'xlfdp names)
    
    542
    +	   (when (some #'fontconfigp names)
    
    543
    +	     (error "Can't mix fontconfig and Core X11 font names."))
    
    544
    +	   (parse-as-core-fonts))
    
    545
    +	  ((some #'fontconfigp names)
    
    546
    +	   (when (some #'xlfdp names)
    
    547
    +	     (error "Can't mix fontconfig and Core X11 font names."))
    
    548
    +	   (parse-as-xft2-fonts))
    
    549
    +	  (t (ecase *ambiguous-font-disposition*
    
    550
    +	       (:core (parse-as-core-fonts))
    
    551
    +	       (:xft2 (parse-as-xft2-fonts)))))))
    
    552
    +
    
    553
    +;; Here's the OpenMotif-specific bit. Now that we can heuristically
    
    554
    +;; classify a list of fonts, we can pair up tags with heuristicated
    
    555
    +;; font names in order to generate OpenMotif resource strings suitable
    
    556
    +;; for either fallback resources or writing into X resource files.
    
    557
    +(defun generate-heuristicated-font-resources
    
    558
    +    (tags fonts &key application-name application-class)
    
    559
    +  "Generate a list of OpenMotif RenderTable & Rendition resources
    
    560
    +associating FONTS with TAGS. If APPLICATION-NAME or APPLICATION-CLASS
    
    561
    +is supplied, the resource keys will be prefixed by that string;
    
    562
    +otherwise, the resource key will start with the loose binding
    
    563
    +operator, asterisk."
    
    564
    +  (declare (type list tags fonts)
    
    565
    +	   (type (or null string) application-name application-class))
    
    566
    +  (let ((ntags (length tags))
    
    567
    +	(nfonts (length fonts)))
    
    568
    +    (assert (= ntags nfonts) (tags fonts)
    
    569
    +	    "Too ~:[many~;few~] tags (~A) for fonts (~A)."
    
    570
    +	    (> ntags nfonts) tags fonts))
    
    571
    +  (let ((name/class (or application-name application-class)))
    
    572
    +    (nconc
    
    573
    +     (mapcan
    
    574
    +      (lambda (tag spec)
    
    575
    +	(let* ((rendition (if (or (string= "" tag) (null tag))
    
    576
    +			      ;; Accept NIL or "" as a the default
    
    577
    +			      ;; tag. Default tags' resources are
    
    578
    +			      ;; resources of the RenderTable itself.
    
    579
    +			      "renderTable"
    
    580
    +			      ;; Non-default tags get used as resource
    
    581
    +			      ;; names.
    
    582
    +			      tag)))
    
    583
    +	  ((lambda (resources)
    
    584
    +	     (loop for (resname resval) on resources by #'cddr
    
    585
    +		   collect (format nil "~@[~A~]*~A.~A: ~A"
    
    586
    +				   name/class rendition resname resval)))
    
    587
    +	   ;; Core fonts are specified by 2 resource name/value pairs.
    
    588
    +	   ;; Xft2 fonts are specified by 4 such pairs.
    
    589
    +	   (if (core-font-name-p spec)
    
    590
    +	       (list "fontName" (core-font-name-string spec)
    
    591
    +		     "fontType" "FONT_IS_FONT")
    
    592
    +	       (list* "fontName" (fontconfig-name-family spec)
    
    593
    +		      "fontType" "FONT_IS_XFT"
    
    594
    +		      (nconc
    
    595
    +		       (when (fontconfig-name-foundry spec)
    
    596
    +			 (list "foundryName" (fontconfig-name-foundry spec)))
    
    597
    +		       (when (fontconfig-name-size spec)
    
    598
    +			 (list "fontSize" (fontconfig-name-size spec)))
    
    599
    +		       (when (or (fontconfig-name-weight spec)
    
    600
    +				 (fontconfig-name-slant spec))
    
    601
    +			 (list "fontStyle"
    
    602
    +			       (format nil "~:[~@[~A~]~;~:*~A~@[ ~A~]~]"
    
    603
    +				       (fontconfig-name-weight spec)
    
    604
    +				       (fontconfig-name-slant spec))))))))))
    
    605
    +      tags (heuristicate-font-name-types fonts))
    
    606
    +     (list (format nil "~@[~A~]*renderTable: ~{~A~^ ~}"
    
    607
    +		   name/class
    
    608
    +		   (remove "" tags))))))
    
    609
    +
    
    610
    +;; Test cases for GENERATE-HEURISTICATED-FONT-RESOURCES.
    
    611
    +#+(or)
    
    612
    +(assert
    
    613
    + (equal
    
    614
    +  ;; These are the Core X11 Fonts that the CLM Debugger/Inspector have
    
    615
    +  ;; used.
    
    616
    +  (let ((fonts '("-adobe-helvetica-medium-r-normal--*-120-75-*"
    
    617
    +		 "-adobe-helvetica-bold-r-normal--*-120-75-*"
    
    618
    +		 "-adobe-helvetica-medium-o-normal--*-120-75-*"))
    
    619
    +	(tags '("" "header" "italic")))
    
    620
    +    (generate-heuristicated-font-resources tags fonts))
    
    621
    +  '("*renderTable.fontName: -adobe-helvetica-medium-r-normal--*-120-75-*"
    
    622
    +    "*renderTable.fontType: FONT_IS_FONT"
    
    623
    +    "*header.fontName: -adobe-helvetica-bold-r-normal--*-120-75-*"
    
    624
    +    "*header.fontType: FONT_IS_FONT"
    
    625
    +    "*italic.fontName: -adobe-helvetica-medium-o-normal--*-120-75-*"
    
    626
    +    "*italic.fontType: FONT_IS_FONT" "*renderTable:  header italic")))
    
    627
    +
    
    628
    +#+(or)
    
    629
    +(assert
    
    630
    + (equal
    
    631
    +  ;; Here are some fontconfig names.
    
    632
    +  (let ((fonts '("Sans-12:regular"
    
    633
    +		 "Sans-12:bold"
    
    634
    +		 "Sans-12:italic"
    
    635
    +		 "Sans-12:bold:italic"))
    
    636
    +	(tags '("" "header" "italic" "foo")))
    
    637
    +    (generate-heuristicated-font-resources tags fonts))
    
    638
    +  '("*renderTable.fontName: Sans" "*renderTable.fontType: FONT_IS_XFT"
    
    639
    +    "*renderTable.fontSize: 12" "*renderTable.fontStyle: regular"
    
    640
    +    "*header.fontName: Sans" "*header.fontType: FONT_IS_XFT"
    
    641
    +    "*header.fontSize: 12" "*header.fontStyle: bold"
    
    642
    +    "*italic.fontName: Sans" "*italic.fontType: FONT_IS_XFT"
    
    643
    +    "*italic.fontSize: 12" "*italic.fontStyle: italic"
    
    644
    +    "*foo.fontName: Sans" "*foo.fontType: FONT_IS_XFT"
    
    645
    +    "*foo.fontSize: 12" "*foo.fontStyle: bold italic"
    
    646
    +    "*renderTable:  header italic foo")))

  • src/motif/lisp/initial.lisp
    ... ... @@ -220,7 +220,8 @@
    220 220
                "IS-APPLICATION-SHELL" "IS-COMPOSITE" "IS-CONSTRAINT" "IS-OBJECT"
    
    221 221
                "IS-OVERRIDE-SHELL" "IS-RECT-OBJ" "IS-SHELL" "IS-TOP-LEVEL-SHELL"
    
    222 222
                "IS-TRANSIENT-SHELL" "IS-VENDOR-SHELL" "IS-W-M-SHELL"
    
    223
    -           "XT-WIDGET-PARENT"))
    
    223
    +           "XT-WIDGET-PARENT" "*AMBIGUOUS-FONT-DISPOSITION*"
    
    224
    +           "GENERATE-HEURISTICATED-FONT-RESOURCES"))
    
    224 225
     
    
    225 226
     
    
    226 227
     
    

  • src/tools/clmcom.lisp
    ... ... @@ -48,6 +48,7 @@
    48 48
         "target:motif/lisp/callbacks"
    
    49 49
         "target:motif/lisp/widgets"
    
    50 50
     ;    "target:motif/lisp/timer-support"
    
    51
    +    "target:motif/lisp/fonts"
    
    51 52
         "target:motif/lisp/main"))
    
    52 53
     
    
    53 54
     (defparameter interface-files