summaryrefslogtreecommitdiffstats
path: root/emacs.d/nxhtml/util/css-palette.el
blob: 44287beb89b8172e0caaa9ea0c881c5106a28615 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
;;; css-palette.el

(defconst css-palette:version "0.02")
;; Copyright (C) 2008 Niels Giesen

;; Author: Niels Giesen <nielsforkgiesen@gmailspooncom, but please
;; replace the kitchen utensils with a dot before hitting "Send">
;; Keywords: processes, css, multimedia, extensions, tools
;; Homepage: http://niels.kicks-ass.org/

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; css-palette defines commands to have "palettes" inside a block
;; comment to circumvent the absence of (color or other) variable
;; definitions in the CSS specification. It can import and export GIMP
;; color palettes. See the documentation of `css-palette-mode'
;; for details of usage.

;;; Installation:

;; Something like:

;; put it in your load-path.

;; (autoload 'css-palette-mode "css-palette" "" t)
;; (add-hook 'css-mode-hook
;; 	  (lambda ()
;; 	    (css-palette-mode t)))

;; Notes:

;; css-palette depends on css-color.el to do font-locking.

;; ccs-palette is orthogonal to css-mode, so it could probably be used
;; inside other language modes, provided they support multiline block
;; comments.

;;; Change log:

;; 2009-01-11 Lennart Borgman
;;   - Minor code clean up.

;;; Code:
(require 'css-color)
(eval-when-compile (require 'cl))		;i'm a bad bad boy...

(defconst css-palette-hex-chars "0123456789abcdefABCDEF"
  "Composing chars in hexadecimal notation, save for the hash (#) sign.")

(defvar css-palette-mode-map
  (let ((m (make-sparse-keymap)))
    (define-key m "\C-c\C-c" 'css-palette-update-all)
    (define-key m "\C-c\C-i" 'css-palette-insert-reference)
    (define-key m "\C-c\C-p" 'css-palette-import-from-GIMP)
    (define-key m "\C-c\C-f" 'css-palette-insert-files)
    m)
  "Mode map for `css-palette-mode'")

;;;###autoload
(define-minor-mode css-palette-mode
  "Minor mode for palettes in CSS.

The mode `css-palette-mode' acts on the first COLORS declaration in your
  file of the form:

COLORS:
\(
c0 \"#6f5d25\"	;tainted sand
c1 \"#000000\"	;Black
c2 \"#cca42b\"	;goldenslumber
c3 \"#6889cb\"	;far off sky
c4 \"#fff\"	;strange aeons
)

Such declarations should appear inside a block comment, in order
  to be parsed properly by the LISP reader.

Type \\[css-palette-update-all], and any occurence of

  color: #f55; /*[c3]*/

will be updated with

  color: #6899cb; /*[c3]*/

The following commands are available to insert key-value pairs
  and palette declarations:
  \\{css-palette-mode-map}

You can extend or redefine the types of palettes by defining a
  new palette specification of the form (PATTERN REGEXP
  REF-FOLLOWS-VALUE), named according to the naming scheme
  css-palette:my-type, where

PATTERN is a pattern containing two (%s) format directives which
  will be filled in with the variable and its value,

REGEXP is a regular expression to match a value - variable
  pattern,

and REF-FOLLOWS-VALUE defined whether or not the reference comes
  after the value. This allows for more flexibility.

Note that, although the w3c spec at URL
  `http://www.w3.org/TR/CSS2/syndata.html#comments' says that
  comments \" may occur anywhere between tokens, and their
  contents have no influence on the rendering\", Internet
  Explorer does not think so. Better keep all your comments after
  a \"statement\", as per the default. This means `css-palette'
  is ill-suited for use within shorthands.

See variable `css-palette:colors' for an example of a palette
  type.

The extension mechanism means that palette types can be used to
  contain arbitrary key-value mappings.

Besides the colors palette, css-palette defines the palette
  definition variables `css-palette:colors-outside' and
  `css-palette:files', for colors with the reference outside and
  for file url()'s respectively.

You can fine-control which palette types css-palette should look
  at via the variable `css-palette-types'.

"
  nil
  "-palette"
  css-palette-mode-map
  (css-color-mode +1))

;;;###autoload
(defgroup css-palette nil
  "Customization group for css-palette library.

See function `css-palette-mode' for documentation"
  :group 'css-color)

(defcustom css-palette:colors
  `("%s; /*[%s]*/ "
    ,(concat "\\("
	     css-color-color-re
;; 	      (mapconcat
;; 	       'identity
;; 	       (list css-color-hex-re
;; 		     css-color-hsl-re
;; 		     css-color-rgb-re) "\\|")
	      "\\)"
	      "[[:space:]]*;[[:space:]]*\/\\*\\[\\([^[:space:]]+\\)\\]\\*\/")
    t)
  "Color palette specification.

See function `css-palette-mode' for documentation"
  :group 'css-palette
  :type '(list
	  (string :tag "Pattern")
	  (regexp :tag "Regexp")
	  (boolean :tag "Reversed")))

(defcustom css-palette:files
  '("url(%s); /*[%s]*/ "
    "url(\\([^)]+\\))[[:space:]]*;[[:space:]]*\/\\*\\[\\([^[:space:]]+\\)\\]\\*\/"
    t)
  "File palette specification.

See function `css-palette-mode' for documentation"
  :group 'css-palette
  :type '(list
	  (string :tag "Pattern")
	  (regexp :tag "Regexp")
	  (boolean :tag "Reversed")))

(defcustom css-palette-types
  '(colors)
  "List of palette types to check for in buffer.

See function `css-palette-mode' for documentation"
  :group 'css-palette
  :type '(repeat (symbol :tag "Palette type")))
(make-variable-buffer-local 'css-palette-types)

;; (defun css-palette-mode-turn-on ()
;;   "Turn on `css-palette-mode'."
;;   (css-palette-mode 1))

;; ;;;###autoload
;; (defcustom css-palette-mode-activate-p nil
;; "Start `css-palette-mode' when `css-mode' is activated."
;;   :group 'css-palette
;;   :set (lambda (sym val)
;;          (set-default sym val)
;;          (if val
;;              (add-hook 'css-mode-hook 'css-palette-mode-turn-on)
;;            (remove-hook 'css-mode-hook 'css-palette-mode-turn-on)))
;;   :type 'boolean)

(defun css-palette-turn-on-in-buffer ()
  "Turn on `css-palette-mode' in `css-mode'."
  (when (derived-mode-p 'css-mode)
    (message "turn-on-in-b:before (css-palette-mode 1) cb=%s" (current-buffer))
    (css-palette-mode 1)
    (message "turn-on-in-b:after (css-palette-mode 1)")
    ))

;;;###autoload
(define-globalized-minor-mode css-palette-global-mode css-palette-mode
  css-palette-turn-on-in-buffer
  :group 'css-color)

(defun css-palette-get (key spec)
  (plist-get
   (css-palette-spec-to-plist
    (symbol-value
     (intern-soft
      (format "css-palette:%s" spec)))) key))

(defun css-palette-spec-to-plist (palette)
  (destructuring-bind (pattern regexp ref-follows-value) palette
    (list :regexp regexp
	  :pattern pattern
	  :ref-follows-value ref-follows-value)))

(defun css-palette-choose-type ()
  (intern-soft
   (if (null (cdr css-palette-types))
       (car css-palette-types)
     (completing-read "Type: "
		      (mapcar 'symbol-name css-palette-types)))))

(defun css-palette-get-declaration (type)
  "Return `css-palette' declaration of TYPE in current buffer.

If none is found, throw an error."
  (let ((type (symbol-name type)))
    (save-excursion
      (goto-char (point-min))
      (or (re-search-forward (format "%s:"
				     (upcase type)) nil t)
	  (error "No %s declaration found in buffer; check value of variable
	  `css-palette-types'" type))
      (let ((palette (read (current-buffer))))
	;; Check (could be better..)
	(if (not (and
		  (listp palette)
		  (= 0 (% (length palette) 2))))
	    (error "Invalid %s " type))
	palette))))

(defun css-palette-update (type)
"Update buffer references for palette of TYPE."
  (interactive (list
		(css-palette-choose-type)))
  (let ((palette (css-palette-get-declaration type))
	(regexp (css-palette-get :regexp type))
	(ref-follows-value (css-palette-get :ref-follows-value type)))
    (flet ((getval (key palette)
		      (let ((value (plist-get palette (intern-soft key))))
			(if (null value)
			    (error
			     "%S not specified in %S palette "
			     key
			     type
			     ;;  (signal 'css-palette-not-found-error nil)
			     )
			  value))))
      (save-excursion
	(goto-char (point-min))
	(while (re-search-forward
		regexp
		(point-max) t)
	  (replace-match
	   (getval (match-string-no-properties (if ref-follows-value 2 1)) palette)
	   nil nil nil (if ref-follows-value 1 2))))))
   (css-color-mode 1))

(defun css-palette-update-all ()
  "Update all references for palettes in `css-palette-types'"
  (interactive)
  (catch 'err
    (mapc (lambda (type)
	    (condition-case err
		(css-palette-update type)
	      (if (y-or-n-p (format "%s, skip? " err))
		  nil)))
	  css-palette-types)))

;; Reference Insertion
(defun css-palette-insert-reference (type)
  "Insert `css-palette' reference of TYPE at point."
  (interactive
    (list (css-palette-choose-type)))
  (let* ((palette (css-palette-get-declaration type))
	 (ref-follows-value (css-palette-get :ref-follows-value type))
	 (pattern (css-palette-get :pattern type))
	 (var
	  (completing-read (format "%s variable: "
				   (capitalize
				    (substring (symbol-name type)
					       0 -1)))
			   (loop for i on
				 palette
				 by 'cddr
				 collect
				 (css-palette-colorify
				  (symbol-name (car i))
				  (cadr i)))))
	 (val  (plist-get palette (read var))))
    (insert (apply 'format
		   pattern
		   (if ref-follows-value
		       (list val var)
		     (list var val))))
    (css-color-mode +1)))

(defun css-palette-hex-color-p (str)
  (string-match "#\\([a-fA-F[:digit:]]\\{6\\}\\|[a-fA-F[:digit:]]\\{3\\}\\)" str))

(defun css-palette-colorify (string color)
  (let ((color (if (css-palette-hex-color-p color)
		   color
		 "#000")))
  (propertize string
	      'font-lock-face
	      (list :background color
		    :foreground (css-color-foreground-color color)
		    string)
	      'fontified t)))

;; Imports
(defun css-palette-from-existing-colors ()
  (interactive)
  (let ((palette)
	(count -1))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward "#[[:digit:]a-fA-F]\\{6\\}\\>" nil t)
	(if (not (member (match-string-no-properties 0) palette))
	    (setq palette (append (list
				   (match-string-no-properties 0)
				   (intern(format "c%d" (incf count))))
				  palette)))
	(save-match-data (re-search-forward ";" nil t))
	(insert (format "/*[%S]*/" (cadr (member (match-string-no-properties 0) palette))))))
    (insert (format "COLORS:\n%S" (nreverse palette)))
    (forward-sexp -1)
    (forward-char 1)
    (while
	(not (looking-at ")"))
      (forward-sexp 2)
      (newline)
      (indent-for-tab-command))))

(defun css-palette-newest-GIMP-dir ()
  "Return newest (version-wise) ~/.gimp-n.n/palettes directory on disk.

Return `nil' if none such directory is found."
  (catch 'none
    (concat
     (or
      (car
       (last
	(directory-files "~/" t "^.gimp-[[:digit:].]\\{3,\\}")))
      (throw 'none ()))
   "/palettes/")))

(defun css-palette-import-from-GIMP ()
  "Import GIMP palette file as a `css-palette' palette.

GIMP palettes can be made with the GIMP or on-line tools such as
found at URL `http://colourlovers.com'."
  (interactive)
  (let ((file (read-file-name "File: " (css-palette-newest-GIMP-dir)))
	(this-buffer (current-buffer))
	(count -1))
    (insert "\nCOLORS:\n(\n")
    (with-temp-buffer
      (insert-file-contents file)
      (goto-char (point-min))
      (while (re-search-forward
	      (concat
	       "^"
	       "[[:space:]]*\\([[:digit:]]+\\)"  ;red
	       "[[:space:]]+\\([[:digit:]]+\\)"  ;green
	       "[[:space:]]+\\([[:digit:]]+\\)"	 ;blue
	       "[[:space:]]+\\(.*\\)$") ;name (=> used as comment)
	      nil t)
	(destructuring-bind (rb re gb ge bb be nb ne &rest ignore)
	    (cddr (match-data t))
	  (let ((color
		 (apply 'format "c%d \"#%02x%02x%02x\" ;%s\n"
			(incf count)
			(append
			 (mapcar 'string-to-number
				 (list
				  (buffer-substring-no-properties rb re)
				  (buffer-substring-no-properties gb ge)
				  (buffer-substring-no-properties bb be)))
			 (list (buffer-substring-no-properties nb ne))))))
	    (with-current-buffer this-buffer
	      (insert color))))))
    (insert ")")
    (message "C-c C-c to update colors")))

(defun css-palette-insert-files (dir)
  "Insert a `css-palette' declaration for all files in DIR.

Filenames are relative.
Main use-case: an image directory."
  (interactive "DDirectory: ")
  (save-excursion
    (let ((image-count -1))
      (insert "\nFILES:\n(\n")
      (mapc
       (lambda (f)
	 (insert
	  (format "file-%d %S\n"
		  (incf image-count)
		  (file-relative-name
		   f
		   (file-name-directory (buffer-file-name))))))
       (directory-files dir t "...+"))
      (insert ")\n\n"))))

;; Exports
(defun css-palette-export-to-GIMP (type name columns)
  "Export the COLORS declaration to a GIMP (.gpl) palette.

See also `gpl-mode' at URL
`http://niels.kicks-ass.org/public/elisp/gpl.el'."
  (interactive
   (list
    (css-palette-choose-type)
    (read-string "Name: ")
    (read-number "Number of columns: " 2)))
  (let ((palette (css-palette-get-declaration type)))
    (find-file
     (concat (css-palette-newest-GIMP-dir)
	     name
	     ".gpl"))
    (insert
     (format "GIMP Palette
Name: %s
Columns: %d
#
" name columns))
    (loop for i on palette
	  by 'cddr
	  do
	  (multiple-value-bind (r g b)(css-color-hex-to-rgb
				       (css-color-hexify-anystring (cadr i)))
	    (insert (format "%3d %3d %3d\t%s\n"
			    r g b
			    (car i))))))
  (if (featurep 'gpl)
      (gpl-mode)))

(provide 'css-palette)
;; css-palette.el ends here