Fix relative links to parent directories in shr
[bpt/emacs.git] / lisp / htmlfontify.el
CommitLineData
e1dbe924 1;;; htmlfontify.el --- htmlize a buffer/source tree with optional hyperlinks
acca02b0 2
ba318903 3;; Copyright (C) 2002-2003, 2009-2014 Free Software Foundation, Inc.
acca02b0
SM
4
5;; Emacs Lisp Archive Entry
13e9d2a7 6;; Package: htmlfontify
acca02b0
SM
7;; Filename: htmlfontify.el
8;; Version: 0.21
9;; Keywords: html, hypermedia, markup, etags
10;; Author: Vivek Dasmohapatra <vivek@etla.org>
11;; Maintainer: Vivek Dasmohapatra <vivek@etla.org>
12;; Created: 2002-01-05
e1dbe924 13;; Description: htmlize a buffer/source tree with optional hyperlinks
acca02b0
SM
14;; URL: http://rtfm.etla.org/emacs/htmlfontify/
15;; Compatibility: Emacs23, Emacs22
16;; Incompatibility: Emacs19, Emacs20, Emacs21
17;; Last Updated: Thu 2009-11-19 01:31:21 +0000
18
19;; This file is part of GNU Emacs.
20
21;; GNU Emacs is free software: you can redistribute it and/or modify
22;; it under the terms of the GNU General Public License as published by
23;; the Free Software Foundation, either version 3 of the License, or
24;; (at your option) any later version.
25
26;; GNU Emacs is distributed in the hope that it will be useful,
27;; but WITHOUT ANY WARRANTY; without even the implied warranty of
28;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29;; GNU General Public License for more details.
30
31;; You should have received a copy of the GNU General Public License
32;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
33
34;;; Commentary:
35;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
36;; I have made some changes to make it work for Emacs 22. A lot of
37;; small bug fixes related to the format of text and overlay
38;; properties (which might have changed since the beginning of 2003
39;; when this file was originally written).
40;;
41;; The function `hfy-face-at' currently carries much of the burden of
42;; my lacking understanding of the formats mentioned above and should
43;; need some knowledgeable help.
44;;
45;; Another thing that maybe could be fixed is that overlay background
46;; colors which are now only seen where there is text (in the XHTML
47;; output). A bit of CSS tweaking is necessary there.
48;;
49;; The face 'default has a value :background "SystemWindow" for the
50;; background color. There is no explicit notion that this should be
51;; considered transparent, but I have assumed that it could be handled
52;; like if it was here. (I am unsure that background and foreground
53;; priorities are handled ok, but it looks ok in my tests now.)
54;;
55;; 2007-12-27 Lennart Borgman
56;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
57
58;; Here's some elisp code to html-pretty-print an Emacs buffer, preserving
59;; the Emacs syntax/whatever highlighting. It also knows how to drive etags
60;; (exuberant-ctags or Emacs etags) and hyperlink the code according
61;; to its (etags') output.
62
63;; NOTE: Currently the hyperlinking code only knows how to drive GNU find
64;; and the exuberant and GNU variants of etags : I do not know of any other
65;; etags variants, but mechanisms have been provided to allow htmlfontify
66;; to be taught how to drive them. As long as your version of find has
67;; the -path test and is reasonably sane, you should be fine.
68
69;; A sample of the htmlfontified / hyperlinked output of this module can be
70;; found at http://rtfm.etla.org/sql/dbishell/src/ - it's not perfect, but
71;; it's a hell of a lot faster and more thorough than I could hope to be
72;; doing this by hand.
73
74;; some user / horrified onlooker comments:
75;; What? No! There's something deeply wrong here... (R. Shufflebotham)
76;; You're a freak. (D. Silverstone)
77;; Aren't we giving you enough to do? (J. Busuttil)
78;; You're almost as messed up as Lexx is! (N. Graves-Morris)
79
80;;; History:
81;; Changes: moved to changelog (CHANGES) file.
82
83;;; Code:
84(eval-when-compile (require 'cl))
85(require 'faces)
86;; (`facep' `face-attr-construct' `x-color-values' `color-values' `face-name')
87(require 'custom)
88;; (`defgroup' `defcustom')
89(require 'font-lock)
90;; (`font-lock-fontify-region')
91(require 'cus-edit)
92
acca02b0
SM
93(defconst htmlfontify-version 0.21)
94
95(defconst hfy-meta-tags
96 (format "<meta name=\"generator\" content=\"emacs %s; htmlfontify %0.2f\" />"
97 emacs-version htmlfontify-version)
98 "The generator meta tag for this version of htmlfontify.")
99
100(defconst htmlfontify-manual "Htmlfontify Manual"
30afcdff
JB
101 "Copy and convert buffers and files to HTML, adding hyperlinks between files
102\(driven by etags) if requested.
acca02b0
SM
103\nInteractive functions:
104 `htmlfontify-buffer'
105 `htmlfontify-run-etags'
106 `htmlfontify-copy-and-link-dir'
107 `htmlfontify-load-rgb-file'
108 `htmlfontify-unload-rgb-file'\n
109In order to:\n
153c5428
SM
110fontify a file you have open: \\[htmlfontify-buffer]
111prepare the etags map for a directory: \\[htmlfontify-run-etags]
112copy a directory, fontifying as you go: \\[htmlfontify-copy-and-link-dir]\n
acca02b0 113The following might be useful when running non-windowed or in batch mode:
30afcdff 114\(note that they shouldn't be necessary - we have a built in map)\n
153c5428
SM
115load an X11 style rgb.txt file: \\[htmlfontify-load-rgb-file]
116unload the current rgb.txt file: \\[htmlfontify-unload-rgb-file]\n
acca02b0 117And here's a programmatic example:\n
30afcdff
JB
118\(defun rtfm-build-page-header (file style)
119 (format \"#define TEMPLATE red+black.html
acca02b0
SM
120#define DEBUG 1
121#include <build/menu-dirlist|>\\n
122html-css-url := /css/red+black.css
30afcdff 123title := rtfm.etla.org ( %s / src/%s )
acca02b0
SM
124bodytag :=
125head <=STYLESHEET;\\n
126%s
127STYLESHEET
128main-title := rtfm / %s / src/%s\\n
30afcdff
JB
129main-content <=MAIN_CONTENT;\\n\" rtfm-section file style rtfm-section file))
130
131\(defun rtfm-build-page-footer (file) \"\\nMAIN_CONTENT\\n\")
132
133\(defun rtfm-build-source-docs (section srcdir destdir)
134 (interactive
135 \"s section[eg- emacs / p4-blame]:\\nD source-dir: \\nD output-dir: \")
136 (require 'htmlfontify)
137 (hfy-load-tags-cache srcdir)
138 (let ((hfy-page-header 'rtfm-build-page-header)
139 (hfy-page-footer 'rtfm-build-page-footer)
140 (rtfm-section section)
141 (hfy-index-file \"index\"))
142 (htmlfontify-run-etags srcdir)
143 (htmlfontify-copy-and-link-dir srcdir destdir \".src\" \".html\")))")
acca02b0
SM
144
145(defgroup htmlfontify nil
b660eb70 146 "Convert buffers and files to HTML."
acca02b0 147 :group 'applications
b660eb70 148 :link '(variable-link htmlfontify-manual)
ed85dee6
RS
149 :link '(custom-manual "(htmlfontify) Top")
150 :link '(info-link "(htmlfontify) Customization")
acca02b0
SM
151 :prefix "hfy-")
152
153(defcustom hfy-page-header 'hfy-default-header
5c32d3f2 154 "Function called to build the header of the HTML source.
153c5428 155This is called with two arguments (the filename relative to the top
30afcdff 156level source directory being etag'd and fontified), and a string containing
153c5428 157the <style>...</style> text to embed in the document.
5c32d3f2 158It should return a string that will be used as the header for the
153c5428 159htmlfontified version of the source file.\n
30afcdff 160See also `hfy-page-footer'."
acca02b0 161 :group 'htmlfontify
72fe6b25
SM
162 ;; FIXME: Why place such a :tag everywhere? Isn't it imposing your
163 ;; own Custom preference on your users? --Stef
acca02b0
SM
164 :tag "page-header"
165 :type '(function))
166
167(defcustom hfy-split-index nil
153c5428
SM
168 "Whether or not to split the index `hfy-index-file' alphabetically.
169If non-nil, the index is split on the first letter of each tag.
5c32d3f2
JB
170Useful when the index would otherwise be large and take
171a long time to render or be difficult to navigate."
acca02b0
SM
172 :group 'htmlfontify
173 :tag "split-index"
174 :type '(boolean))
175
176(defcustom hfy-page-footer 'hfy-default-footer
153c5428
SM
177 "As `hfy-page-header', but generates the output footer.
178It takes only one argument, the filename."
acca02b0
SM
179 :group 'htmlfontify
180 :tag "page-footer"
181 :type '(function))
182
5c32d3f2 183(defcustom hfy-extn ".html"
72fe6b25 184 "File extension used for output files."
acca02b0
SM
185 :group 'htmlfontify
186 :tag "extension"
187 :type '(string))
188
189(defcustom hfy-src-doc-link-style "text-decoration: underline;"
30afcdff 190 "String to add to the '<style> a' variant of an htmlfontify CSS class."
acca02b0
SM
191 :group 'htmlfontify
192 :tag "src-doc-link-style"
193 :type '(string))
194
195(defcustom hfy-src-doc-link-unstyle " text-decoration: none;"
30afcdff 196 "Regex to remove from the <style> a variant of an htmlfontify CSS class."
acca02b0
SM
197 :group 'htmlfontify
198 :tag "src-doc-link-unstyle"
199 :type '(string))
200
201(defcustom hfy-link-extn nil
30afcdff
JB
202 "File extension used for href links.
203Useful where the htmlfontify output files are going to be processed
204again, with a resulting change in file extension. If nil, then any
205code using this should fall back to `hfy-extn'."
acca02b0
SM
206 :group 'htmlfontify
207 :tag "link-extension"
208 :type '(choice string (const nil)))
209
210(defcustom hfy-link-style-fun 'hfy-link-style-string
153c5428
SM
211 "Function to customize the appearance of hyperlinks.
212Set this to a function, which will be called with one argument
30afcdff 213\(a \"{ foo: bar; ...}\" CSS style-string) - it should return a copy of
acca02b0
SM
214its argument, altered so as to make any changes you want made for text which
215is a hyperlink, in addition to being in the class to which that style would
216normally be applied."
217 :group 'htmlfontify
218 :tag "link-style-function"
219 :type '(function))
220
30afcdff
JB
221(defcustom hfy-index-file "hfy-index"
222 "Name (sans extension) of the tag definition index file produced during
acca02b0
SM
223fontification-and-hyperlinking."
224 :group 'htmlfontify
225 :tag "index-file"
226 :type '(string))
227
30afcdff
JB
228(defcustom hfy-instance-file "hfy-instance"
229 "Name (sans extension) of the tag usage index file produced during
acca02b0
SM
230fontification-and-hyperlinking."
231 :group 'htmlfontify
232 :tag "instance-file"
233 :type '(string))
234
153c5428 235(defcustom hfy-html-quote-regex "\\([<\"&>]\\)"
30afcdff
JB
236 "Regex to match (with a single back-reference per match) strings in HTML
237which should be quoted with `hfy-html-quote' (and `hfy-html-quote-map')
acca02b0
SM
238to make them safe."
239 :group 'htmlfontify
240 :tag "html-quote-regex"
241 :type '(regexp))
242
72fe6b25
SM
243(define-obsolete-variable-alias 'hfy-init-kludge-hooks 'hfy-init-kludge-hook
244 "23.2")
245(defcustom hfy-init-kludge-hook '(hfy-kludge-cperl-mode)
30afcdff
JB
246 "List of functions to call when starting `htmlfontify-buffer' to do any
247kludging necessary to get highlighting modes to behave as you want, even
acca02b0
SM
248when not running under a window system."
249 :group 'htmlfontify
250 :tag "init-kludge-hooks"
251 :type '(hook))
252
d1069532
SM
253(define-obsolete-variable-alias 'hfy-post-html-hooks 'hfy-post-html-hook "24.3")
254(defcustom hfy-post-html-hook nil
30afcdff 255 "List of functions to call after creating and filling the HTML buffer.
5c32d3f2 256These functions will be called with the HTML buffer as the current buffer."
acca02b0
SM
257 :group 'htmlfontify
258 :tag "post-html-hooks"
259 :options '(set-auto-mode)
260 :type '(hook))
261
262(defcustom hfy-default-face-def nil
30afcdff
JB
263 "Fallback `defface' specification for the face 'default, used when
264`hfy-display-class' has been set (the normal htmlfontify way of extracting
265potentially non-current face information doesn't necessarily work for
266'default).\n
267Example: I customize this to:\n
268\((t :background \"black\" :foreground \"white\" :family \"misc-fixed\"))"
acca02b0
SM
269 :group 'htmlfontify
270 :tag "default-face-definition"
271 :type '(alist))
272
273(defcustom hfy-etag-regex (concat ".*"
274 "\x7f" "\\([^\x01\n]+\\)"
275 "\x01" "\\([0-9]+\\)"
276 "," "\\([0-9]+\\)$"
277 "\\|" ".*\x7f[0-9]+,[0-9]+$")
72fe6b25 278 "Regex used to parse an etags entry: must have 3 subexps, corresponding,
acca02b0
SM
279in order, to:\n
280 1 - The tag
281 2 - The line
30afcdff 282 3 - The char (point) at which the tag occurs."
acca02b0
SM
283 :group 'htmlfontify
284 :tag "etag-regex"
285 :type '(regexp))
286
287(defcustom hfy-html-quote-map '(("\"" "&quot;")
288 ("<" "&lt;" )
289 ("&" "&amp;" )
290 (">" "&gt;" ))
30afcdff 291 "Alist of char -> entity mappings used to make the text HTML-safe."
acca02b0
SM
292 :group 'htmlfontify
293 :tag "html-quote-map"
294 :type '(alist :key-type (string)))
037e7c3f 295(defconst hfy-e2x-etags-cmd "for src in `find . -type f`;
acca02b0
SM
296do
297 ETAGS=%s;
298 case ${src} in
299 *.ad[absm]|*.[CFHMSacfhlmpsty]|*.def|*.in[cs]|*.s[as]|*.src|*.cc|\\
300 *.hh|*.[chy]++|*.[ch]pp|*.[chy]xx|*.pdb|*.[ch]s|*.[Cc][Oo][Bb]|\\
301 *.[eh]rl|*.f90|*.for|*.java|*.[cem]l|*.clisp|*.lisp|*.[Ll][Ss][Pp]|\\
302 [Mm]akefile*|*.pas|*.[Pp][LlMm]|*.psw|*.lm|*.pc|*.prolog|*.oak|\\
303 *.p[sy]|*.sch|*.scheme|*.[Ss][Cc][Mm]|*.[Ss][Mm]|*.bib|*.cl[os]|\\
304 *.ltx|*.sty|*.TeX|*.tex|*.texi|*.texinfo|*.txi|*.x[bp]m|*.yy|\\
305 *.[Ss][Qq][Ll])
306 ${ETAGS} -o- ${src};
307 ;;
308 *)
309 FTYPE=`file ${src}`;
310 case ${FTYPE} in
311 *script*text*)
312 ${ETAGS} -o- ${src};
313 ;;
314 *text*)
315 SHEBANG=`head -n1 ${src} | grep '#!' -c`;
316 if [ ${SHEBANG} -eq 1 ];
317 then
318 ${ETAGS} -o- ${src};
319 fi;
320 ;;
321 esac;
322 ;;
323 esac;
324done;")
325
037e7c3f
SM
326(defconst hfy-etags-cmd-alist-default
327 `(("emacs etags" . ,hfy-e2x-etags-cmd)
328 ("exuberant ctags" . "%s -R -f -" )))
acca02b0 329
037e7c3f
SM
330(defcustom hfy-etags-cmd-alist
331 hfy-etags-cmd-alist-default
332 "Alist of possible shell commands that will generate etags output that
30afcdff 333`htmlfontify' can use. '%s' will be replaced by `hfy-etags-bin'."
037e7c3f
SM
334 :group 'htmlfontify
335 :tag "etags-cmd-alist"
336 :type '(alist :key-type (string) :value-type (string)))
acca02b0
SM
337
338(defcustom hfy-etags-bin "etags"
30afcdff 339 "Location of etags binary (we begin by assuming it's in your path).\n
acca02b0
SM
340Note that if etags is not in your path, you will need to alter the shell
341commands in `hfy-etags-cmd-alist'."
342 :group 'htmlfontify
343 :tag "etags-bin"
344 :type '(file))
345
346(defcustom hfy-shell-file-name "/bin/sh"
5c32d3f2 347 "Shell (Bourne or compatible) to invoke for complex shell operations."
acca02b0
SM
348 :group 'htmlfontify
349 :tag "shell-file-name"
350 :type '(file))
351
30afcdff 352(defcustom hfy-ignored-properties '(read-only
2ea1c4aa
SM
353 intangible
354 modification-hooks
355 insert-in-front-hooks
356 insert-behind-hooks
357 point-entered
358 point-left)
30afcdff 359 "Properties to omit when copying a fontified buffer for HTML transformation."
2ea1c4aa
SM
360 :group 'htmlfontify
361 :tag "ignored-properties"
362 :type '(repeat symbol))
363
acca02b0 364(defun hfy-which-etags ()
c5e87d10 365 "Return a string indicating which flavor of etags we are using."
acca02b0
SM
366 (let ((v (shell-command-to-string (concat hfy-etags-bin " --version"))))
367 (cond ((string-match "exube" v) "exuberant ctags")
368 ((string-match "GNU E" v) "emacs etags" )) ))
369
370(defcustom hfy-etags-cmd
037e7c3f
SM
371 ;; We used to wrap this in a `eval-and-compile', but:
372 ;; - it had no effect because this expression was not seen by the
373 ;; byte-compiler (defcustom used to quote this argument).
374 ;; - it signals an error (`hfy-which-etags' is not defined at compile-time).
375 ;; - we want this auto-detection to reflect the system on which Emacs is run
376 ;; rather than the one on which it's compiled.
377 (cdr (assoc (hfy-which-etags) hfy-etags-cmd-alist))
72fe6b25 378 "The etags equivalent command to run in a source directory to generate a tags
acca02b0
SM
379file for the whole source tree from there on down. The command should emit
380the etags output on stdout.\n
44e97401 381Two canned commands are provided - they drive Emacs's etags and
30afcdff 382exuberant-ctags' etags respectively."
acca02b0
SM
383 :group 'htmlfontify
384 :tag "etags-command"
037e7c3f
SM
385 :type (let ((clist (list '(string))))
386 (dolist (C hfy-etags-cmd-alist)
387 (push (list 'const :tag (car C) (cdr C)) clist))
388 (cons 'choice clist)))
acca02b0
SM
389
390(defcustom hfy-istext-command "file %s | sed -e 's@^[^:]*:[ \t]*@@'"
72fe6b25 391 "Command to run with the name of a file, to see whether it is a text file
30afcdff
JB
392or not. The command should emit a string containing the word 'text' if
393the file is a text file, and a string not containing 'text' otherwise."
acca02b0
SM
394 :group 'htmlfontify
395 :tag "istext-command"
396 :type '(string))
397
398(defcustom hfy-find-cmd
399 "find . -type f \\! -name \\*~ \\! -name \\*.flc \\! -path \\*/CVS/\\*"
72fe6b25 400 "Find command used to harvest a list of files to attempt to fontify."
acca02b0
SM
401 :group 'htmlfontify
402 :tag "find-command"
403 :type '(string))
404
405(defcustom hfy-display-class nil
72fe6b25 406 "Display class to use to determine which display class to use when
30afcdff 407calculating a face's attributes. This is useful when, for example, you
acca02b0
SM
408are running Emacs on a tty or in batch mode, and want htmlfontify to have
409access to the face spec you would use if you were connected to an X display.\n
410Some valid class specification elements are:\n
30afcdff
JB
411 '(class color)
412 '(class grayscale)
413 '(background dark)
414 '(background light)
415 '(type x-toolkit)
416 '(type tty)
417 '(type motif)
418 '(type lucid)
acca02b0
SM
419Multiple values for a tag may be combined, to indicate that any one or more
420of these values in the specification key constitutes a match, eg:\n
30afcdff
JB
421'((class color grayscale) (type tty)) would match any of:\n
422 '((class color))
423 '((class grayscale))
424 '((class color grayscale))
425 '((class color foo))
426 '((type tty))
427 '((type tty) (class color))\n
acca02b0
SM
428and so on."
429 :type '(alist :key-type (symbol) :value-type (symbol))
430 :group 'htmlfontify
431 :tag "display-class"
432 :options '((type (choice (const :tag "X11" x-toolkit)
433 (const :tag "Terminal" tty )
434 (const :tag "Lucid Toolkit" lucid )
435 (const :tag "Motif Toolkit" motif )))
436
f6b1b0a8
PE
437 (class (choice (const :tag "Color" color )
438 (const :tag "Grayscale" grayscale)))
acca02b0
SM
439
440 (background (choice (const :tag "Dark" dark )
441 (const :tag "Bright" light ))) ))
442
443(defcustom hfy-optimisations (list 'keep-overlays)
30afcdff 444 "Optimizations to turn on: So far, the following have been implemented:\n
acca02b0
SM
445 merge-adjacent-tags: If two (or more) span tags are adjacent, identical and
446 separated by nothing more than whitespace, they will
447 be merged into one span.
448 zap-comment-links : Suppress hyperlinking of tags found in comments.
449 zap-string-links : Suppress hyperlinking of tags found in strings.
450 div-wrapper : Add <div class=\"default\"> </div> tags around the
451 output.
30afcdff
JB
452 keep-overlays : More of a bell (or possibly whistle) than an
453 optimization - If on, preserve overlay highlighting
454 (cf ediff or goo-font-lock) as well as basic faces.\n
f02ff80d
J
455 body-text-only : Emit only body-text. In concrete terms,
456 1. Suppress calls to `hfy-page-header'and
457 `hfy-page-footer'
458 2. Pretend that `div-wrapper' option above is
459 turned off
460 3. Don't enclose output in <pre> </pre> tags
acca02b0
SM
461 And the following are planned but not yet available:\n
462 kill-context-leak : Suppress hyperlinking between files highlighted by
463 different modes.\n
30afcdff 464Note: like compiler optimizations, these optimize the _output_ of the code,
acca02b0 465not the processing of the source itself, and are therefore likely to slow
30afcdff 466htmlfontify down, at least a little. Except for skip-refontification,
acca02b0
SM
467which can never slow you down, but may result in incomplete fontification."
468 :type '(set (const :tag "merge-adjacent-tags" merge-adjacent-tags )
469 (const :tag "zap-comment-links" zap-comment-links )
470 (const :tag "zap-string-links" zap-string-links )
471 (const :tag "skip-refontification" skip-refontification)
472 (const :tag "kill-context-leak" kill-context-leak )
473 (const :tag "div-wrapper" div-wrapper )
f02ff80d
J
474 (const :tag "keep-overlays" keep-overlays )
475 (const :tag "body-text-only" body-text-only ))
acca02b0 476 :group 'htmlfontify
30afcdff 477 :tag "optimizations")
acca02b0 478
30afcdff 479(defvar hfy-tags-cache nil
acca02b0 480 "Alist of the form:\n
30afcdff
JB
481\((\"/src/dir/0\" . tag-hash0) (\"/src/dir/1\" tag-hash1) ...)\n
482Each tag hash entry then contains entries of the form:\n
acca02b0 483\"tag_string\" => ((\"file/name.ext\" line char) ... )\n
30afcdff
JB
484ie an alist mapping (relative) file paths to line and character offsets.\n
485See also `hfy-load-tags-cache'.")
acca02b0 486
30afcdff
JB
487(defvar hfy-tags-sortl nil
488 "Alist of the form ((\"/src/dir\" . (tag0 tag1 tag2)) ... )\n
489where the tags are stored in descending order of length.\n
490See also `hfy-load-tags-cache'.")
acca02b0 491
30afcdff
JB
492(defvar hfy-tags-rmap nil
493 "Alist of the form ((\"/src/dir\" . tag-rmap-hash))\n
494where tag-rmap-hash has entries of the form:
acca02b0
SM
495\"tag_string\" => ( \"file/name.ext\" line char )
496Unlike `hfy-tags-cache' these are the locations of occurrences of
497tagged items, not the locations of their definitions.")
498
499(defvar hfy-style-assoc 'please-ignore-this-line
500 "An assoc representing/describing an Emacs face.
30afcdff
JB
501Properties may be repeated, in which case later properties should be
502treated as if they were inherited from a 'parent' font.
acca02b0
SM
503\(For some properties, only the first encountered value is of any importance,
504for others the values might be cumulative, and for others they might be
30afcdff 505cumulative in a complex way.)\n
acca02b0 506Some examples:\n
30afcdff
JB
507\(hfy-face-to-style 'default) =>
508 ((\"background\" . \"rgb(0, 0, 0)\")
509 (\"color\" . \"rgb(255, 255, 255)\")
510 (\"font-style\" . \"normal\")
511 (\"font-weight\" . \"500\")
512 (\"font-stretch\" . \"normal\")
513 (\"font-family\" . \"misc-fixed\")
514 (\"font-size\" . \"13pt\")
515 (\"text-decoration\" . \"none\"))\n
516\(hfy-face-to-style 'Info-title-3-face) =>
517 ((\"font-weight\" . \"700\")
518 (\"font-family\" . \"helv\")
519 (\"font-size\" . \"120%\")
520 (\"text-decoration\" . \"none\"))\n")
acca02b0
SM
521
522(defvar hfy-sheet-assoc 'please-ignore-this-line
30afcdff
JB
523 "An assoc with elements of the form (face-name style-name . style-string):\n
524'((default \"default\" . \"{background: black; color: white}\")
525 (font-lock-string-face \"string\" . \"{color: rgb(64,224,208)}\"))" )
acca02b0
SM
526
527(defvar hfy-facemap-assoc 'please-ignore-this-line
30afcdff 528 "An assoc of (point . FACE-SYMBOL) or (point . DEFFACE-LIST)
acca02b0 529and (point . 'end) elements, in descending order of point value
30afcdff
JB
530\(ie from the file's end to its beginning).\n
531The map is in reverse order because inserting a <style> tag (or any other
532string) at `point' invalidates the map for all entries with a greater value of
533point. By traversing the map from greatest to least point, we still invalidate
534the map as we go, but only those points we have already dealt with (and
535therefore no longer care about) will be invalid at any time.\n
536'((64820 . end)
537 (64744 . font-lock-comment-face)
538 (64736 . end)
539 (64722 . font-lock-string-face)
540 (64630 . end)
541 (64623 . font-lock-string-face)
542 (64449 . end)
543 (64446 . font-lock-keyword-face)
544 (64406 . end)
545 (64395 . font-lock-constant-face)
546 (64393 . end)
547 (64386 . font-lock-keyword-face)
548 (64379 . end)
acca02b0 549 ;; big similar section elided. You get the idea.
30afcdff
JB
550 (4285 . font-lock-constant-face)
551 (4285 . end)
552 (4221 . font-lock-comment-face)
553 (4221 . end)
554 (4197 . font-lock-constant-face)
555 (4197 . end)
556 (1 . font-lock-comment-face))")
acca02b0
SM
557
558(defvar hfy-tmpfont-stack nil
559 "An alist of derived fonts resulting from overlays.")
560
561(defconst hfy-hex-regex "[0-9A-Fa-f]")
562
563(defconst hfy-triplet-regex
564 (concat
565 "\\(" hfy-hex-regex hfy-hex-regex "\\)"
566 "\\(" hfy-hex-regex hfy-hex-regex "\\)"
567 "\\(" hfy-hex-regex hfy-hex-regex "\\)"))
568
569(defun hfy-interq (set-a set-b)
30afcdff 570 "Return the intersection (using `eq') of two lists SET-A and SET-B."
acca02b0
SM
571 (let ((sa set-a) (interq nil) (elt nil))
572 (while sa
573 (setq elt (car sa)
574 sa (cdr sa))
153c5428
SM
575 (if (memq elt set-b) (setq interq (cons elt interq))))
576 interq))
acca02b0
SM
577
578(defun hfy-colour-vals (colour)
30afcdff
JB
579 "Where COLOUR is a color name or #XXXXXX style triplet, return a
580list of three (16 bit) rgb values for said color.\n
acca02b0
SM
581If a window system is unavailable, calls `hfy-fallback-colour-values'."
582 (if (string-match hfy-triplet-regex colour)
583 (mapcar
72fe6b25
SM
584 (lambda (x) (* (string-to-number (match-string x colour) 16) 257))
585 '(1 2 3))
acca02b0
SM
586 ;;(message ">> %s" colour)
587 (if window-system
588 (if (fboundp 'color-values)
589 (color-values colour)
590 ;;(message "[%S]" window-system)
591 (x-color-values colour))
fa463103 592 ;; blarg - tty colors are no good - go fetch some X colors:
acca02b0
SM
593 (hfy-fallback-colour-values colour))))
594
595(defvar hfy-cperl-mode-kludged-p nil)
596
597(defun hfy-kludge-cperl-mode ()
4c36be58 598 "CPerl mode does its damnedest not to do some of its fontification when not
acca02b0
SM
599in a windowing system - try to trick it..."
600 (if (not hfy-cperl-mode-kludged-p)
601 (progn (if (not window-system)
602 (let ((window-system 'htmlfontify))
603 (eval-and-compile (require 'cperl-mode))
604 (setq cperl-syntaxify-by-font-lock t)))
605 (setq hfy-cperl-mode-kludged-p t))) )
606
5c32d3f2
JB
607(defun hfy-opt (symbol)
608 "Is option SYMBOL set."
153c5428 609 (memq symbol hfy-optimisations))
acca02b0
SM
610
611(defun hfy-default-header (file style)
612 "Default value for `hfy-page-header'.
613FILE is the name of the file.
614STYLE is the inline CSS stylesheet (or tag referring to an external sheet)."
615;; (format "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
616;; <html>\n <head>\n <title>%s</title>\n %s\n </head>\n <body>\n" file style))
617 (format "<?xml version=\"1.0\" encoding=\"utf-8\"?>
618<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"
619\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">
620<html xmlns=\"http://www.w3.org/1999/xhtml\">
621 <head>
622 <title>%s</title>
623%s
624 <script type=\"text/javascript\"><!--
625 // this function is needed to work around
626 // a bug in IE related to element attributes
627 function hasClass(obj)
628 {
629 var result = false;
630 if (obj.getAttributeNode(\"class\") != null)
631 {
632 result = obj.getAttributeNode(\"class\").value;
633 }
634 return result;
635 }
636
637 function stripe(id)
638 {
639 // the flag we'll use to keep track of
640 // whether the current row is odd or even
641 var even = false;
642
30afcdff 643 // if arguments are provided to specify the colors
acca02b0
SM
644 // of the even & odd rows, then use the them;
645 // otherwise use the following defaults:
646 var evenColor = arguments[1] ? arguments[1] : \"#fff\";
647 var oddColor = arguments[2] ? arguments[2] : \"#ddd\";
648
649 // obtain a reference to the desired table
650 // if no such table exists, abort
651 var table = document.getElementById(id);
652 if (! table) { return; }
653
654 // by definition, tables can have more than one tbody
655 // element, so we'll have to get the list of child
656 // &lt;tbody&gt;s
657 var tbodies = table.getElementsByTagName(\"tbody\");
658
659 // and iterate through them...
660 for (var h = 0; h < tbodies.length; h++)
661 {
662 // find all the &lt;tr&gt; elements...
663 var trs = tbodies[h].getElementsByTagName(\"tr\");
664
665 // ... and iterate through them
666 for (var i = 0; i < trs.length; i++)
667 {
668 // avoid rows that have a class attribute
669 // or backgroundColor style
670 if (! hasClass(trs[i]) &&
671 ! trs[i].style.backgroundColor)
672 {
673 // get all the cells in this row...
674 var tds = trs[i].getElementsByTagName(\"td\");
675
676 // and iterate through them...
677 for (var j = 0; j < tds.length; j++)
678 {
679 var mytd = tds[j];
680
681 // avoid cells that have a class attribute
682 // or backgroundColor style
683 if (! hasClass(mytd) &&
684 ! mytd.style.backgroundColor)
685 {
686 mytd.style.backgroundColor =
687 even ? evenColor : oddColor;
688 }
689 }
690 }
691 // flip from odd to even, or vice-versa
692 even = ! even;
693 }
694 }
695 }
85e0a536
SM
696
697 function toggle_invis( name )
698 {
699 var filter =
700 { acceptNode:
701 function( node )
702 { var classname = node.id;
703 if( classname )
704 { var classbase = classname.substr( 0, name.length );
705 if( classbase == name ) { return NodeFilter.FILTER_ACCEPT; } }
706 return NodeFilter.FILTER_SKIP; } };
707 var walker = document.createTreeWalker( document.body ,
708 NodeFilter.SHOW_ELEMENT ,
709 filter ,
710 false );
711 while( walker.nextNode() )
712 {
713 var e = walker.currentNode;
714 if( e.style.display == \"none\" ) { e.style.display = \"inline\"; }
715 else { e.style.display = \"none\"; }
716 }
717 }
acca02b0
SM
718--> </script>
719 </head>
720 <body onload=\"stripe('index'); return true;\">\n"
153dd4d0 721 (mapconcat 'hfy-html-quote (mapcar 'char-to-string file) "") style))
acca02b0 722
06b60517 723(defun hfy-default-footer (_file)
acca02b0
SM
724 "Default value for `hfy-page-footer'.
725FILE is the name of the file being rendered, in case it is needed."
726 "\n </body>\n</html>\n")
727
728(defun hfy-link-style-string (style-string)
30afcdff 729 "Replace the end of a CSS style declaration STYLE-STRING with the contents
acca02b0
SM
730of the variable `hfy-src-doc-link-style', removing text matching the regex
731`hfy-src-doc-link-unstyle' first, if necessary."
732 ;;(message "hfy-colour-vals");;DBUG
733 (if (string-match hfy-src-doc-link-unstyle style-string)
734 (setq style-string (replace-match "" 'fixed-case 'literal style-string)))
735 (if (and (not (string-match hfy-src-doc-link-style style-string))
736 (string-match "} *$" style-string))
737 (concat (replace-match hfy-src-doc-link-style
738 'fixed-case
739 'literal
153c5428
SM
740 style-string) " }")
741 style-string))
acca02b0
SM
742
743;; utility functions - cast emacs style specification values into their
744;; css2 equivalents:
745(defun hfy-triplet (colour)
30afcdff 746 "Takes a COLOUR name (string) and return a CSS rgb(R, G, B) triplet string.
acca02b0 747Uses the definition of \"white\" to map the numbers to the 0-255 range, so
30afcdff
JB
748if you've redefined white, (esp. if you've redefined it to have a triplet
749member lower than that of the color you are processing) strange things
750may happen."
acca02b0 751 ;;(message "hfy-colour-vals");;DBUG
eab35f39
GM
752 ;; TODO? Can we do somehow do better than this?
753 (cond
754 ((equal colour "unspecified-fg") (setq colour "black"))
755 ((equal colour "unspecified-bg") (setq colour "white")))
acca02b0
SM
756 (let ((white (mapcar (lambda (I) (float (1+ I))) (hfy-colour-vals "white")))
757 (rgb16 (mapcar (lambda (I) (float (1+ I))) (hfy-colour-vals colour))))
758 (if rgb16
759 ;;(apply 'format "rgb(%d, %d, %d)"
760 ;; Use #rrggbb instead, it is smaller
761 (apply 'format "#%02x%02x%02x"
762 (mapcar (lambda (X)
763 (* (/ (nth X rgb16)
72fe6b25
SM
764 (nth X white)) 255))
765 '(0 1 2))))))
acca02b0
SM
766
767(defun hfy-family (family) (list (cons "font-family" family)))
768(defun hfy-bgcol (colour) (list (cons "background" (hfy-triplet colour))))
769(defun hfy-colour (colour) (list (cons "color" (hfy-triplet colour))))
770(defun hfy-width (width) (list (cons "font-stretch" (symbol-name width))))
771
772(defcustom hfy-font-zoom 1.05
773 "Font scaling from Emacs to HTML."
774 :type 'float
775 :group 'htmlfontify)
776
30afcdff 777(defun hfy-size (height)
acca02b0
SM
778 "Derive a CSS font-size specifier from an Emacs font :height attribute HEIGHT.
779Does not cope with the case where height is a function to be applied to
780the height of the underlying font."
eab35f39
GM
781 ;; In ttys, the default face has :height == 1.
782 (and (not (display-graphic-p)) (equal 1 height) (setq height 100))
acca02b0
SM
783 (list
784 (cond
785 ;;(t (cons "font-size" ": 1em"))
786 ((floatp height)
787 (cons "font-size" (format "%d%%" (* (* hfy-font-zoom height) 100))))
788 ((integerp height)
789 (cons "font-size" (format "%dpt" (/ (* hfy-font-zoom height) 10 )))) )) )
790
30afcdff
JB
791(defun hfy-slant (slant)
792 "Derive a font-style CSS specifier from the Emacs :slant attribute SLANT:
acca02b0
SM
793CSS does not define the reverse-* styles, so just maps those to the
794regular specifiers."
72fe6b25
SM
795 (list (cons "font-style"
796 (or (cdr (assq slant '((italic . "italic")
797 (reverse-italic . "italic" )
798 (oblique . "oblique")
799 (reverse-oblique . "oblique"))))
800 "normal"))))
acca02b0
SM
801
802(defun hfy-weight (weight)
30afcdff 803 "Derive a font-weight CSS specifier from an Emacs weight spec symbol WEIGHT."
72fe6b25
SM
804 (list (cons "font-weight" (cdr (assq weight '((ultra-bold . "900")
805 (extra-bold . "800")
806 (bold . "700")
807 (semi-bold . "600")
808 (normal . "500")
809 (semi-light . "400")
810 (light . "300")
811 (extra-light . "200")
812 (ultra-light . "100")))))))
30afcdff 813
acca02b0
SM
814(defun hfy-box-to-border-assoc (spec)
815 (if spec
816 (let ((tag (car spec))
817 (val (cadr spec)))
72fe6b25
SM
818 (cons (case tag
819 (:color (cons "colour" val))
820 (:width (cons "width" val))
821 (:style (cons "style" val)))
822 (hfy-box-to-border-assoc (cddr spec))))))
acca02b0
SM
823
824(defun hfy-box-to-style (spec)
825 (let* ((css (hfy-box-to-border-assoc spec))
826 (col (cdr (assoc "colour" css)))
827 (s (cdr (assoc "style" css))))
828 (list
829 (if col (cons "border-color" (cdr (assoc "colour" css))))
830 (cons "border-width" (format "%dpx" (or (cdr (assoc "width" css)) 1)))
72fe6b25
SM
831 (cons "border-style" (case s
832 (released-button "outset")
833 (pressed-button "inset" )
834 (t "solid" ))))))
acca02b0
SM
835
836(defun hfy-box (box)
837 "Derive CSS border-* attributes from the Emacs :box attribute BOX."
838 (if box
839 (cond
840 ((integerp box) (list (cons "border-width" (format "%dpx" box))))
841 ((stringp box) (list (cons "border" (format "solid %s 1px" box))))
842 ((listp box) (hfy-box-to-style box) ))) )
843
06b60517 844(defun hfy-decor (tag _val)
acca02b0
SM
845 "Derive CSS text-decoration specifiers from various Emacs font attributes.
846TAG is an Emacs font attribute key (eg :underline).
847VAL is ignored."
848 (list
e3353a78 849 ;; FIXME: Why not '("text-decoration" . "underline")? --Stef
72fe6b25
SM
850 (case tag
851 (:underline (cons "text-decoration" "underline" ))
852 (:overline (cons "text-decoration" "overline" ))
853 (:strike-through (cons "text-decoration" "line-through")))))
acca02b0 854
06b60517 855(defun hfy-invisible (&optional _val)
acca02b0
SM
856 "This text should be invisible.
857Do something in CSS to make that happen.
858VAL is ignored here."
859 '(("display" . "none")))
860
861(defun hfy-combined-face-spec (face)
862 "Return a `defface' style alist of possible specifications for FACE.
30afcdff 863Entries resulting from customization (`custom-set-faces') will take
acca02b0 864precedence."
153c5428
SM
865 (append
866 (if (and hfy-display-class hfy-default-face-def (eq face 'default))
867 hfy-default-face-def)
868 (get face 'saved-face)
869 (get face 'face-defface-spec)))
acca02b0
SM
870
871(defun hfy-face-attr-for-class (face &optional class)
872 "Return the face attributes for FACE.
30afcdff 873If CLASS is set, it must be a `defface' alist key [see below],
acca02b0 874in which case the first face specification returned by `hfy-combined-face-spec'
30afcdff 875which *doesn't* clash with CLASS is returned.\n
acca02b0 876\(A specification with a class of t is considered to match any class you
44e97401 877specify - this matches Emacs's behavior when deciding on which face attributes
30afcdff 878to use, to the best of my understanding).\n
5a5fa834 879If CLASS is nil, then you just get whatever `face-attr-construct' returns,
acca02b0 880ie the current specification in effect for FACE.\n
30afcdff
JB
881*NOTE*: This function forces any face that is not 'default and which has
882no :inherit property to inherit from 'default (this is because 'default
44e97401 883is magical in that Emacs's fonts behave as if they inherit implicitly from
30afcdff
JB
884'default, but no such behavior exists in HTML/CSS).\n
885See also `hfy-display-class' for details of valid values for CLASS."
72fe6b25
SM
886 (let ((face-spec
887 (if class
888 (let ((face-props (hfy-combined-face-spec face))
889 (face-specn nil)
890 (face-class nil)
891 (face-attrs nil)
892 (face-score -1)
893 (face-match nil))
894 (while face-props
895 (setq face-specn (car face-props)
896 face-class (car face-specn)
897 face-attrs (cdr face-specn)
898 face-props (cdr face-props))
899 ;; if the current element CEL of CLASS is t we match
900 ;; if the current face-class is t, we match
901 ;; if the cdr of CEL has a non-nil
902 ;; intersection with the cdr of the first member of
903 ;; the current face-class with the same car as CEL, we match
904 ;; if we actually clash, then we can't match
905 (let ((cbuf class)
906 (cel nil)
907 (key nil)
908 (val nil)
909 (x nil)
910 (next nil)
911 (score 0))
912 (while (and cbuf (not next))
913 (setq cel (car cbuf)
914 cbuf (cdr cbuf)
915 key (car cel)
916 val (cdr cel)
917 val (if (listp val) val (list val)))
918 (cond
919 ((or (eq cel t)
920 (memq face-class '(t default))) ;Default match.
921 (setq score 0) (ignore "t match"))
922 ((not (cdr (assq key face-class))) ;Neither good nor bad.
923 nil (ignore "non match, non collision"))
924 ((setq x (hfy-interq val (cdr (assq key face-class))))
925 (setq score (+ score (length x)))
926 (ignore "intersection"))
927 (t ;; nope.
928 (setq next t score -10) (ignore "collision")) ))
929 (if (> score face-score)
930 (progn
931 (setq face-match face-attrs
932 face-score score )
933 (ignore "%d << %S/%S" score face-class class))
934 (ignore "--- %d ---- (insufficient)" score)) ))
935 ;; matched ? last attrs : nil
936 (if face-match
937 (if (listp (car face-match)) (car face-match) face-match)
938 nil))
939 ;; Unfortunately the default face returns a
940 ;; :background. Fortunately we can remove it, but how do we do
941 ;; that in a non-system specific way?
942 (let ((spec (face-attr-construct face))
943 (new-spec nil))
944 (if (not (memq :background spec))
945 spec
946 (while spec
947 (let ((a (nth 0 spec))
948 (b (nth 1 spec)))
949 (unless (and (eq a :background)
950 (stringp b)
951 (string= b "SystemWindow"))
952 (setq new-spec (cons a (cons b new-spec)))))
953 (setq spec (cddr spec)))
954 new-spec)))))
acca02b0
SM
955 (if (or (memq :inherit face-spec) (eq 'default face))
956 face-spec
b7d4de51 957 (append face-spec (list :inherit 'default)))))
acca02b0
SM
958
959;; construct an assoc of (css-tag-name . css-tag-value) pairs
960;; from a face or assoc of face attributes:
961
962;; Some tests etc:
963;; (mumamo-message-with-face "testing face" 'highlight)
964;; (mumamo-message-with-face "testing face" '(:foreground "red" :background "yellow"))
965;; (hfy-face-to-style-i '(:inherit default foreground-color "red"))
966;; default face=(:stipple nil :background "SystemWindow" :foreground
967;; "SystemWindowText" :inverse-video nil :box nil :strike-through
968;; nil :overline nil :underline nil :slant normal :weight normal
969;; :height 98 :width normal :family "outline-courier new")
970(defun hfy-face-to-style-i (fn)
971 "The guts of `hfy-face-to-style': FN should be a `defface' font spec,
30afcdff
JB
972as returned by `face-attr-construct' or `hfy-face-attr-for-class'.
973Note that this function does not get font-sizes right if they are based
974on inherited modifiers (via the :inherit) attribute, and any other
acca02b0
SM
975modifiers that are cumulative if they appear multiple times need to be
976merged by the user - `hfy-flatten-style' should do this."
977 ;;(message "hfy-face-to-style-i");;DBUG
978
979 ;; fn's value could be something like
980 ;; (:inherit
981 ;; ((foreground-color . "blue"))
982 ;; (foreground-color . "blue")
983 ;; nil)
984
985 (when fn
986 (let ((key (car fn))
987 (val (cadr fn))
988 (next (cddr fn))
989 (that nil)
990 (this nil)
991 (parent nil))
992 (if (eq key :inherit)
993 (let ((vs (if (listp val) val (list val))))
994 ;; (let ((x '(a b))) (setq x (append '(c d) x)))
995 ;; (let ((x '(a b))) (setq x (append '(c d) x)))
996 (dolist (v vs)
997 (setq parent
998 (append
999 parent
1000 (hfy-face-to-style-i
1001 (hfy-face-attr-for-class v hfy-display-class)) ))))
1002 (setq this
72fe6b25
SM
1003 (if val (case key
1004 (:family (hfy-family val))
1005 (:width (hfy-width val))
1006 (:weight (hfy-weight val))
1007 (:slant (hfy-slant val))
1008 (:foreground (hfy-colour val))
1009 (:background (hfy-bgcol val))
1010 (:box (hfy-box val))
1011 (:height (hfy-size val))
1012 (:underline (hfy-decor key val))
1013 (:overline (hfy-decor key val))
1014 (:strike-through (hfy-decor key val))
1015 (:invisible (hfy-invisible val))
1016 (:bold (hfy-weight 'bold))
1017 (:italic (hfy-slant 'italic))))))
acca02b0
SM
1018 (setq that (hfy-face-to-style-i next))
1019 ;;(lwarn t :warning "%S => %S" fn (nconc this that parent))
1020 (nconc this that parent))) )
1021
1022(defun hfy-size-to-int (spec)
30afcdff
JB
1023 "Convert SPEC, a CSS font-size specifier, to an Emacs :height attribute value.
1024Used while merging multiple font-size attributes."
acca02b0
SM
1025 ;;(message "hfy-size-to-int");;DBUG
1026 (list
1027 (if (string-match "\\([0-9]+\\)\\(%\\|pt\\)" spec)
1028 (cond ((string= "%" (match-string 2 spec))
1029 (/ (string-to-number (match-string 1 spec)) 100.0))
1030 ((string= "pt" (match-string 2 spec))
1031 (* (string-to-number (match-string 1 spec)) 10)))
1032 (string-to-number spec))) )
1033
1034;; size is different, in that in order to get it right at all,
1035;; we have to trawl the inheritance path, accumulating modifiers,
1036;; _until_ we get to an absolute (pt) specifier, then combine the lot
1037(defun hfy-flatten-style (style)
1038 "Take STYLE (see `hfy-face-to-style-i', `hfy-face-to-style') and merge
1039any multiple attributes appropriately. Currently only font-size is merged
1040down to a single occurrence - others may need special handling, but I
30afcdff 1041haven't encountered them yet. Returns a `hfy-style-assoc'."
acca02b0
SM
1042 ;;(message "(hfy-flatten-style %S)" style) ;;DBUG
1043 (let ((n 0)
1044 (m (list 1))
1045 (x nil)
1046 (r nil))
72fe6b25
SM
1047 (dolist (css style)
1048 (if (string= (car css) "font-size")
1049 (progn
1050 (when (not x) (setq m (nconc m (hfy-size-to-int (cdr css)))))
1051 (when (string-match "pt" (cdr css)) (setq x t)))
1052 (setq r (nconc r (list css)))))
acca02b0
SM
1053 ;;(message "r: %S" r)
1054 (setq n (apply '* m))
1055 (nconc r (hfy-size (if x (round n) (* n 1.0)))) ))
1056
ecb0ab90
CY
1057(defun hfy-face-resolve-face (fn)
1058 (cond
1059 ((facep fn)
1060 (hfy-face-attr-for-class fn hfy-display-class))
1061 ((and (symbolp fn)
f02ff80d 1062 (facep (symbol-value fn)))
ecb0ab90
CY
1063 (hfy-face-attr-for-class (symbol-value fn) hfy-display-class))
1064 (t nil)))
1065
1066
acca02b0
SM
1067(defun hfy-face-to-style (fn)
1068 "Take FN, a font or `defface' style font specification,
30afcdff 1069\(as returned by `face-attr-construct' or `hfy-face-attr-for-class')
acca02b0 1070and return a `hfy-style-assoc'.\n
30afcdff 1071See also `hfy-face-to-style-i', `hfy-flatten-style'."
acca02b0 1072 ;;(message "hfy-face-to-style");;DBUG
153c5428
SM
1073 (let* ((face-def (hfy-face-resolve-face fn))
1074 (final-style
1075 (hfy-flatten-style (hfy-face-to-style-i face-def))))
acca02b0
SM
1076 ;;(message "%S" final-style)
1077 (if (not (assoc "text-decoration" final-style))
1078 (progn (setq final-style
1079 ;; Fix-me: there is no need for this since
1080 ;; text-decoration is not inherited.
1081 ;; but it's not wrong and if this ever changes it will
1082 ;; be needed, so I think it's better to leave it in? -- v
5c32d3f2 1083 (nconc final-style '(("text-decoration" . "none"))))))
acca02b0
SM
1084 final-style))
1085
1086;; strip redundant bits from a name. Technically, this could result in
1087;; a collision, but it is pretty unlikely - will fix later...
1088;; also handle ephemeral fonts created by overlays, which don't actually
1089;; have names:
1090(defun hfy-face-or-def-to-name (fn)
30afcdff 1091 "Render a font symbol or `defface' font spec FN into a name (string)."
acca02b0
SM
1092 ;;(message "generating name for %s" fn)
1093 (if (not (listp fn))
1094 (format "%s" fn)
1095 (let* ((key (format "%s" fn))
1096 (entry (assoc key hfy-tmpfont-stack))
1097 (base (cadr (memq :inherit fn)))
1098 (tag (cdr entry)))
1099 ;;(message "checking for key «%s» in font stack [%d]"
1100 ;; key (if entry 1 0))
1101 (if entry nil ;; noop
1102 (setq tag (format "%04d" (length hfy-tmpfont-stack))
1103 entry (cons key tag)
1104 hfy-tmpfont-stack (cons entry hfy-tmpfont-stack)))
1105 ;;(message " -> name: %s-%s" (or base 'default) tag)
1106 (format "%s-%s" (or base 'default) tag)) ))
1107
1108(defun hfy-css-name (fn)
1109 "Strip the boring bits from a font-name FN and return a CSS style name."
1110 ;;(message "hfy-css-name");;DBUG
1111 (let ((face-name (hfy-face-or-def-to-name fn)))
1112 (if (or (string-match "font-lock-\\(.*\\)" face-name)
1113 (string-match "cperl-\\(.*\\)" face-name)
1114 (string-match "^[Ii]nfo-\\(.*\\)" face-name))
1115 (progn
1116 (setq face-name (match-string 1 face-name))
153c5428
SM
1117 (if (string-match "\\(.*\\)-face\\'" face-name)
1118 (setq face-name (match-string 1 face-name)))
1119 face-name)
acca02b0
SM
1120 face-name)) )
1121
1122;; construct an assoc of (stripped-name . "{ css-stuff-here }") pairs
1123;; from a face:
f02ff80d
J
1124(defun hfy-face-to-css-default (fn)
1125 "Default handler for mapping faces to styles.
1126See also `hfy-face-to-css'."
acca02b0 1127 ;;(message "hfy-face-to-css");;DBUG
153c5428
SM
1128 (let* ((css-list (hfy-face-to-style fn))
1129 (seen nil)
1130 (css-text
72fe6b25
SM
1131 (mapcar
1132 (lambda (E)
1133 (if (car E)
1134 (unless (member (car E) seen)
1135 (push (car E) seen)
1136 (format " %s: %s; " (car E) (cdr E)))))
153c5428 1137 css-list)))
acca02b0
SM
1138 (cons (hfy-css-name fn) (format "{%s}" (apply 'concat css-text)))) )
1139
f02ff80d
J
1140(defvar hfy-face-to-css 'hfy-face-to-css-default
1141 "Handler for mapping faces to styles.
1142The signature of the handler is of the form \(lambda (FN) ...\).
1143FN is a font or `defface' specification (cf
1144`face-attr-construct'). The handler should return a cons cell of
1145the form (STYLE-NAME . STYLE-SPEC).
1146
1147The default handler is `hfy-face-to-css-default'.
1148
1149See also `hfy-face-to-style'.")
1150
153c5428
SM
1151(defalias 'hfy-prop-invisible-p
1152 (if (fboundp 'invisible-p) #'invisible-p
1153 (lambda (prop)
1154 "Is text property PROP an active invisibility property?"
1155 (or (and (eq buffer-invisibility-spec t) prop)
1156 (or (memq prop buffer-invisibility-spec)
1157 (assq prop buffer-invisibility-spec))))))
acca02b0
SM
1158
1159(defun hfy-find-invisible-ranges ()
1160 "Return a list of (start-point . end-point) cons cells of invisible regions."
153c5428
SM
1161 (save-excursion
1162 (let (invisible p i s) ;; return-value pos invisible end start
acca02b0
SM
1163 (setq p (goto-char (point-min)))
1164 (when (invisible-p p) (setq s p i t))
1165 (while (< p (point-max))
1166 (if i ;; currently invisible
1167 (when (not (invisible-p p)) ;; but became visible
153c5428
SM
1168 (setq i nil
1169 invisible (cons (cons s p) invisible)))
acca02b0
SM
1170 ;; currently visible:
1171 (when (invisible-p p) ;; but have become invisible
1172 (setq s p i t)))
1173 (setq p (next-char-property-change p)))
1174 ;; still invisible at buffer end?
1175 (when i
06b60517 1176 (setq invisible (cons (cons s (point-max)) invisible)))
153c5428 1177 invisible)))
acca02b0
SM
1178
1179(defun hfy-invisible-name (point map)
1180 "Generate a CSS style name for an invisible section of the buffer.
1181POINT is the point inside the invisible region.
1182MAP is the invisibility map as returned by `hfy-find-invisible-ranges'."
1183 ;;(message "(hfy-invisible-name %S %S)" point map)
1184 (let (name)
72fe6b25
SM
1185 (dolist (range map)
1186 (when (and (>= point (car range))
1187 (< point (cdr range)))
1188 (setq name (format "invisible-%S-%S" (car range) (cdr range)))))
acca02b0
SM
1189 name))
1190
1191;; Fix-me: This function needs some cleanup by someone who understand
1192;; all the formats that face properties can have.
1193;;
1194;; overlay handling should be fine. haven't tested multiple stacked overlapping
1195;; overlays recently, but the common case of a text property face + an overlay
1196;; face produces the correct merged css style (or as close to it as css can get)
1197;; -- v
1198(defun hfy-face-at (p)
1199 "Find face in effect at point P.
30afcdff
JB
1200If overlays are to be considered (see `hfy-optimisations') then this may
1201return a `defface' style list of face properties instead of a face symbol."
acca02b0
SM
1202 ;;(message "hfy-face-at");;DBUG
1203 ;; Fix-me: clean up, remove face-name etc
1204 ;; not sure why we'd want to remove face-name? -- v
72fe6b25
SM
1205 (let ((overlay-data nil)
1206 (base-face nil)
153c5428 1207 (face-name (get-text-property p 'face))
72fe6b25
SM
1208 ;; (face-name (hfy-get-face-at p))
1209 (prop-seen nil)
1210 (extra-props nil)
1211 (text-props (text-properties-at p)))
1212 ;;(message "face-name: %S" face-name)
1213 (when (and face-name (listp face-name) (facep (car face-name)))
1214 ;;(message "face-name is a list %S" face-name)
1215 ;;(setq text-props (cons 'face face-name))
1216 (dolist (f face-name)
1217 (setq extra-props (if (listp f)
1218 ;; for things like (variable-pitch
1219 ;; (:foreground "red"))
1220 (cons f extra-props)
1221 (cons :inherit (cons f extra-props)))))
1222 (setq base-face (car face-name)
1223 face-name nil))
1224 ;; text-properties-at => (face (:foreground "red" ...))
1225 ;; or => (face (compilation-info underline)) list of faces
1226 ;; overlay-properties
1227 ;; format= (evaporate t face ((foreground-color . "red")))
1228
1229 ;; SO: if we have turned overlays off,
1230 ;; or if there's no overlay data
1231 ;; just bail out and return whatever face data we've accumulated so far
1232 (if (or (not (hfy-opt 'keep-overlays))
1233 (not (setq overlay-data (hfy-overlay-props-at p))))
1234 (progn
1235 ;;(message "· %d: %s; %S; %s"
1236 ;; p face-name extra-props text-props)
1237 (or face-name base-face)) ;; no overlays or extra properties
1238 ;; collect any face data and any overlay data for processing:
1239 (when text-props
1240 (push text-props overlay-data))
1241 (setq overlay-data (nreverse overlay-data))
1242 ;;(message "- %d: %s; %S; %s; %s"
1243 ;; p face-name extra-props text-props overlay-data)
1244 ;; remember the basic face name so we don't keep repeating its specs:
1245 (when face-name (setq base-face face-name))
1246 (dolist (P overlay-data)
1247 (let ((iprops (cadr (memq 'invisible P)))) ;FIXME: plist-get?
1248 ;;(message "(hfy-prop-invisible-p %S)" iprops)
1249 (when (and iprops (hfy-prop-invisible-p iprops))
1250 (setq extra-props
1251 (cons :invisible (cons t extra-props))) ))
1252 (let ((fprops (cadr (or (memq 'face P)
1253 (memq 'font-lock-face P)))))
1254 ;;(message "overlay face: %s" fprops)
1255 (if (not (listp fprops))
1256 (let ((this-face (if (stringp fprops) (intern fprops) fprops)))
1257 (when (not (eq this-face base-face))
1258 (setq extra-props
1259 (cons :inherit
1260 (cons this-face extra-props))) ))
1261 (while fprops
1262 (if (facep (car fprops))
1263 (let ((face (car fprops)))
1264 (when (stringp face) (setq face (intern fprops)))
1265 (setq extra-props
1266 (cons :inherit
1267 (cons face
1268 extra-props)))
1269 (setq fprops (cdr fprops)))
1270 (let (p v)
1271 ;; Sigh.
1272 (if (listp (car fprops))
1273 (if (nlistp (cdr (car fprops)))
1274 (progn
1275 ;; ((prop . val))
1276 (setq p (caar fprops))
1277 (setq v (cdar fprops))
1278 (setq fprops (cdr fprops)))
1279 ;; ((prop val))
1280 (setq p (caar fprops))
1281 (setq v (cadar fprops))
1282 (setq fprops (cdr fprops)))
1283 (if (listp (cdr fprops))
1284 (progn
1285 ;; (:prop val :prop val ...)
1286 (setq p (car fprops))
1287 (setq v (cadr fprops))
1288 (setq fprops (cddr fprops)))
1289 (if (and (listp fprops)
1290 (not (listp (cdr fprops))))
1291 ;;(and (consp x) (cdr (last x)))
1292 (progn
1293 ;; (prop . val)
1294 (setq p (car fprops))
1295 (setq v (cdr fprops))
1296 (setq fprops nil))
1297 (error "Eh... another format! fprops=%s" fprops) )))
1298 (setq p (case p
1299 ;; These are all the properties handled
1300 ;; in `hfy-face-to-style-i'.
1301 ;;
1302 ;; Are these translations right?
1303 ;; yes, they are -- v
1304 (family :family )
1305 (width :width )
1306 (height :height )
1307 (weight :weight )
1308 (slant :slant )
1309 (underline :underline )
1310 (overline :overline )
1311 (strike-through :strike-through)
1312 (box :box )
1313 (foreground-color :foreground)
1314 (background-color :background)
1315 (bold :bold )
1316 (italic :italic )
1317 (t p)))
1318 (if (memq p prop-seen) nil ;; noop
1319 (setq prop-seen (cons p prop-seen)
1320 extra-props (cons p (cons v extra-props))))))))))
1321 ;;(message "+ %d: %s; %S" p face-name extra-props)
1322 (if extra-props
153c5428
SM
1323 (nconc extra-props (if (listp face-name)
1324 face-name
1325 (face-attr-construct face-name)))
72fe6b25 1326 face-name)) ))
acca02b0
SM
1327
1328(defun hfy-overlay-props-at (p)
1329 "Grab overlay properties at point P.
1330The plists are returned in descending priority order."
72fe6b25
SM
1331 (sort (mapcar #'overlay-properties (overlays-at p))
1332 (lambda (A B) (> (or (cadr (memq 'priority A)) 0) ;FIXME: plist-get?
1333 (or (cadr (memq 'priority B)) 0)))))
acca02b0
SM
1334
1335;; construct an assoc of (face-name . (css-name . "{ css-style }")) elements:
1336(defun hfy-compile-stylesheet ()
f02ff80d
J
1337 "Trawl the current buffer, construct and return a `hfy-sheet-assoc'.
1338If `hfy-user-sheet-assoc' is currently bound then use it to
1339collect new styles discovered during this run. Otherwise create
1340a new assoc."
acca02b0
SM
1341 ;;(message "hfy-compile-stylesheet");;DBUG
1342 (let ((pt (point-min))
1343 ;; Make the font stack stay:
1344 ;;(hfy-tmpfont-stack nil)
1345 (fn nil)
f02ff80d 1346 (style (and (boundp 'hfy-user-sheet-assoc) hfy-user-sheet-assoc)))
acca02b0
SM
1347 (save-excursion
1348 (goto-char pt)
1349 (while (< pt (point-max))
1350 (if (and (setq fn (hfy-face-at pt)) (not (assoc fn style)))
f02ff80d
J
1351 (push (cons fn (funcall hfy-face-to-css fn)) style))
1352 (setq pt (next-char-property-change pt))))
1353 (unless (assoc 'default style)
1354 (push (cons 'default (funcall hfy-face-to-css 'default)) style))
1355 (when (boundp 'hfy-user-sheet-assoc)
1356 (setq hfy-user-sheet-assoc style))
1357 style))
acca02b0
SM
1358
1359(defun hfy-fontified-p ()
30afcdff 1360 "`font-lock' doesn't like to say it's been fontified when in batch
acca02b0
SM
1361mode, but we want to know if we should fontify or raw copy, so in batch
1362mode we check for non-default face properties. Otherwise we test
1363variable `font-lock-mode' and variable `font-lock-fontified' for truth."
1364 ;;(message "font-lock-fontified: %S" font-lock-fontified)
1365 ;;(message "noninteractive : %S" noninteractive)
1366 ;;(message "font-lock-mode : %S" font-lock-mode)
1367 (and font-lock-fontified
1368 (if noninteractive
1369 (let ((pt (point-min))
1370 (face-name nil))
1371 (save-excursion
1372 (goto-char pt)
1373 (while (and (< pt (point-max)) (not face-name))
1374 (setq face-name (hfy-face-at pt))
153c5428
SM
1375 (setq pt (next-char-property-change pt))))
1376 face-name)
acca02b0
SM
1377 font-lock-mode)))
1378
1379;; remember, the map is in reverse point order:
1380;; I wrote this while suffering the effects of a cold, and maybe a
1381;; mild fever - I think it's correct, but it might be a little warped
1382;; as my minfd keeps ... where was I? Oh yes, the bunnies...
1383(defun hfy-merge-adjacent-spans (face-map)
1384 "Where FACE-MAP is a `hfy-facemap-assoc' for the current buffer,
1385this function merges adjacent style blocks which are of the same value
1386and are separated by nothing more interesting than whitespace.\n
1387 <span class=\"foo\">narf</span> <span class=\"foo\">brain</span>\n
30afcdff 1388\(as interpreted from FACE-MAP) would become:\n
acca02b0
SM
1389 <span class=\"foo\">narf brain</span>\n
1390Returns a modified copy of FACE-MAP."
1391 (let ((tmp-map face-map)
1392 (map-buf nil)
1393 (first-start nil)
1394 (first-stop nil)
1395 (last-start nil)
1396 (last-stop nil)
1397 (span-stop nil)
1398 (span-start nil)
1399 (reduced-map nil))
72fe6b25
SM
1400 ;;(push (car tmp-map) reduced-map)
1401 ;;(push (cadr tmp-map) reduced-map)
acca02b0
SM
1402 (while tmp-map
1403 (setq first-start (cadddr tmp-map)
1404 first-stop (caddr tmp-map)
1405 last-start (cadr tmp-map)
1406 last-stop (car tmp-map)
1407 map-buf tmp-map
1408 span-start last-start
1409 span-stop last-stop )
1410 (while (and (equal (cdr first-start)
1411 (cdr last-start))
1412 (save-excursion
1413 (goto-char (car first-stop))
1414 (not (re-search-forward "[^ \t\n\r]" (car last-start) t))))
1415 (setq map-buf (cddr map-buf)
1416 span-start first-start
1417 first-start (cadddr map-buf)
1418 first-stop (caddr map-buf)
1419 last-start (cadr map-buf)
1420 last-stop (car map-buf)))
72fe6b25
SM
1421 (push span-stop reduced-map)
1422 (push span-start reduced-map)
acca02b0
SM
1423 (setq tmp-map (memq last-start tmp-map))
1424 (setq tmp-map (cdr tmp-map)))
1425 (setq reduced-map (nreverse reduced-map))))
1426
1427;; remember to generate 'synthetic' </span> entries -
1428;; emacs copes by just having a stack of styles in effect
1429;; and only using the top one: html has a more simplistic approach -
1430;; we have to explicitly end a style, there's no way of temporarily
1431;; overriding it w. another one... (afaik)
1432(defun hfy-compile-face-map ()
1433;; no need for special <a> version.
1434;; IME hyperlinks don't get underlined, esp when you htmlfontify a whole
1435;; source tree, so the <a> version is needed -- v
1436;; Fix-me: save table for multi-buffer
1437 "Compile and return a `hfy-facemap-assoc' for the current buffer."
1438 ;;(message "hfy-compile-face-map");;DBUG
153c5428
SM
1439 (let* ((pt (point-min))
1440 (pt-narrow (save-restriction (widen) (point-min)))
1441 (offset (- pt pt-narrow))
1442 (fn nil)
1443 (map nil)
1444 (prev-tag nil)) ;; t if the last tag-point was a span-start
1445 ;; nil if it was a span-stop
acca02b0
SM
1446 (save-excursion
1447 (goto-char pt)
1448 (while (< pt (point-max))
1449 (if (setq fn (hfy-face-at pt))
72fe6b25
SM
1450 (progn (if prev-tag (push (cons pt-narrow 'end) map))
1451 (push (cons pt-narrow fn) map)
acca02b0 1452 (setq prev-tag t))
72fe6b25 1453 (if prev-tag (push (cons pt-narrow 'end) map))
acca02b0
SM
1454 (setq prev-tag nil))
1455 (setq pt (next-char-property-change pt))
153c5428 1456 (setq pt-narrow (+ offset pt)))
acca02b0 1457 (if (and map (not (eq 'end (cdar map))))
f02ff80d 1458 (push (cons (1+ (- (point-max) (point-min))) 'end) map)))
acca02b0
SM
1459 (if (hfy-opt 'merge-adjacent-tags) (hfy-merge-adjacent-spans map) map)))
1460
1461(defun hfy-buffer ()
30afcdff
JB
1462 "Generate a buffer to hold the HTML output.
1463The filename of this buffer is derived from the source (current) buffer's
acca02b0
SM
1464variable `buffer-file-name', if it is set, plus `hfy-extn'.
1465Otherwise a plausible filename is constructed from `default-directory',
1466`buffer-name' and `hfy-extn'."
1467 (let* ((name (concat (buffer-name) hfy-extn))
1468 (src (buffer-file-name))
1469 (buf (get-buffer-create name)))
e3353a78
SM
1470 (with-current-buffer buf
1471 (setq buffer-file-name
1472 (if src (concat src hfy-extn)
153c5428 1473 (expand-file-name (if (string-match "^.*/\\([^/]*\\)\\'" name)
e3353a78
SM
1474 (match-string 1 name)
1475 name))))
acca02b0
SM
1476 buf)))
1477
1478(defun hfy-lookup (face style)
1479 "Get a CSS style name for FACE from STYLE."
1480 (cadr (assoc face style)))
1481
1482(defun hfy-link-style (style-string)
1483 "Copy, alter and return a STYLE-STRING to make it suitable for a hyperlink.
1484Uses `hfy-link-style-fun' to do this."
1485 (if (functionp hfy-link-style-fun)
1486 (funcall hfy-link-style-fun style-string)
1487 style-string))
1488
1489(defun hfy-sprintf-stylesheet (css file)
1490 "Return the inline CSS style sheet for FILE as a string."
153c5428
SM
1491 (let ((stylesheet
1492 (concat
1493 hfy-meta-tags
1494 "\n<style type=\"text/css\"><!-- \n"
1495 ;; Fix-me: Add handling of page breaks here + scan for ^L
1496 ;; where appropriate.
1497 (format "body %s\n" (cddr (assq 'default css)))
1498 (apply 'concat
1499 (mapcar
1500 (lambda (style)
1501 (format
1502 "span.%s %s\nspan.%s a %s\n"
1503 (cadr style) (cddr style)
1504 (cadr style) (hfy-link-style (cddr style))))
1505 css))
1506 " --></style>\n")))
acca02b0
SM
1507 (funcall hfy-page-header file stylesheet)))
1508
acca02b0
SM
1509;; tag all the dangerous characters we want to escape
1510;; (ie any "<> chars we _didn't_ put there explicitly for css markup)
1511(defun hfy-html-enkludge-buffer ()
30afcdff 1512 "Mark dangerous [\"<>] characters with the `hfy-quoteme' property.\n
acca02b0
SM
1513See also `hfy-html-dekludge-buffer'."
1514 ;;(message "hfy-html-enkludge-buffer");;DBUG
1515 (save-excursion
1516 (goto-char (point-min))
1517 (while (re-search-forward hfy-html-quote-regex nil t)
1518 (put-text-property (match-beginning 0) (point) 'hfy-quoteme t))) )
1519
1520;; dangerous char -> &entity;
1521(defun hfy-html-quote (char-string)
30afcdff 1522 "Map CHAR-STRING to an HTML safe string (entity) if need be."
acca02b0
SM
1523 ;;(message "hfy-html-quote");;DBUG
1524 (or (cadr (assoc char-string hfy-html-quote-map)) char-string) )
1525
1526;; actually entity-ise dangerous chars.
1527;; note that we can't do this until _after_ we have inserted the css
1528;; markup, since we use a position-based map to insert this, and if we
1529;; enter any other text before we do this, we'd have to track another
1530;; map of offsets, which would be tedious...
1531(defun hfy-html-dekludge-buffer ()
30afcdff
JB
1532 "Transform all dangerous characters marked with the `hfy-quoteme' property
1533using `hfy-html-quote'.\n
acca02b0
SM
1534See also `hfy-html-enkludge-buffer'."
1535 ;;(message "hfy-html-dekludge-buffer");;DBUG
1536 (save-excursion
1537 (goto-char (point-min))
1538 (while (re-search-forward hfy-html-quote-regex nil t)
1539 (if (get-text-property (match-beginning 0) 'hfy-quoteme)
1540 (replace-match (hfy-html-quote (match-string 1))) )) ))
1541
1542;; Borrowed from font-lock.el
1543(defmacro hfy-save-buffer-state (varlist &rest body)
1544 "Bind variables according to VARLIST and eval BODY restoring buffer state.
1545Do not record undo information during evaluation of BODY."
1546 (declare (indent 1) (debug let))
1547 (let ((modified (make-symbol "modified")))
1548 `(let* ,(append varlist
1549 `((,modified (buffer-modified-p))
1550 (buffer-undo-list t)
1551 (inhibit-read-only t)
1552 (inhibit-point-motion-hooks t)
1553 (inhibit-modification-hooks t)
1554 deactivate-mark
1555 buffer-file-name
1556 buffer-file-truename))
1557 (progn
1558 ,@body)
1559 (unless ,modified
1560 (restore-buffer-modified-p nil)))))
1561
1562(defun hfy-mark-trailing-whitespace ()
1563 "Tag trailing whitespace with a hfy property if it is currently highlighted."
1564 (when show-trailing-whitespace
1565 (let ((inhibit-read-only t))
1566 (save-excursion
1567 (goto-char (point-min))
1568 (hfy-save-buffer-state nil
1569 (while (re-search-forward "[ \t]+$" nil t)
1570 (put-text-property (match-beginning 0) (match-end 0)
1571 'hfy-show-trailing-whitespace t)))))))
1572
1573(defun hfy-unmark-trailing-whitespace ()
1574 "Undo the effect of `hfy-mark-trailing-whitespace'."
1575 (when show-trailing-whitespace
1576 (hfy-save-buffer-state nil
1577 (remove-text-properties (point-min) (point-max)
1578 '(hfy-show-trailing-whitespace)))))
1579
f02ff80d
J
1580(defun hfy-begin-span (style text-block text-id text-begins-block-p)
1581 "Default handler to begin a span of text.
1582Insert \"<span class=\"STYLE\" ...>\". See
1583`hfy-begin-span-handler' for more information."
1584 (when text-begins-block-p
1585 (insert
1586 (format "<span onclick=\"toggle_invis('%s');\">…</span>" text-block)))
1587
1588 (insert
1589 (if text-block
1590 (format "<span class=\"%s\" id=\"%s-%d\">" style text-block text-id)
1591 (format "<span class=\"%s\">" style))))
1592
1593(defun hfy-end-span ()
1594 "Default handler to end a span of text.
1595Insert \"</span>\". See `hfy-end-span-handler' for more
1596information."
1597 (insert "</span>"))
1598
1599(defvar hfy-begin-span-handler 'hfy-begin-span
1600 "Handler to begin a span of text.
1601The signature of the handler is \(lambda (STYLE TEXT-BLOCK
1602TEXT-ID TEXT-BEGINS-BLOCK-P) ...\). The handler must insert
1603appropriate tags to begin a span of text.
1604
1605STYLE is the name of the style that begins at point. It is
1606derived from the face attributes as part of `hfy-face-to-css'
1607callback. The other arguments TEXT-BLOCK, TEXT-ID,
1608TEXT-BEGINS-BLOCK-P are non-nil only if the buffer contains
1609invisible text.
1610
1611TEXT-BLOCK is a string that identifies a single chunk of visible
1612or invisible text of which the current position is a part. For
1613visible portions, it's value is \"nil\". For invisible portions,
1614it's value is computed as part of `hfy-invisible-name'.
1615
1616TEXT-ID marks a unique position within a block. It is set to
1617value of `point' at the current buffer position.
1618
1619TEXT-BEGINS-BLOCK-P is a boolean and is non-nil if the current
1620span also begins a invisible portion of text.
1621
1622An implementation can use TEXT-BLOCK, TEXT-ID,
1623TEXT-BEGINS-BLOCK-P to implement fold/unfold-on-mouse-click like
da5ecfa9 1624behavior.
f02ff80d
J
1625
1626The default handler is `hfy-begin-span'.")
1627
1628(defvar hfy-end-span-handler 'hfy-end-span
1629 "Handler to end a span of text.
1630The signature of the handler is \(lambda () ...\). The handler
1631must insert appropriate tags to end a span of text.
1632
1633The default handler is `hfy-end-span'.")
1634
acca02b0
SM
1635(defun hfy-fontify-buffer (&optional srcdir file)
1636 "Implement the guts of `htmlfontify-buffer'.
1637SRCDIR, if set, is the directory being htmlfontified.
1638FILE, if set, is the file name."
1639 (if srcdir (setq srcdir (directory-file-name srcdir)))
e3353a78 1640 (let* ( (html-buffer (hfy-buffer))
acca02b0
SM
1641 (css-sheet nil)
1642 (css-map nil)
1643 (invis-ranges nil)
1644 (rovl nil)
1645 (orig-ovls (overlays-in (point-min) (point-max)))
1646 (rmin (when mark-active (region-beginning)))
1647 (rmax (when mark-active (region-end ))) )
1648 (when (and mark-active
1649 transient-mark-mode)
1650 (unless (and (= rmin (point-min))
1651 (= rmax (point-max)))
1652 (setq rovl (make-overlay rmin rmax))
1653 (overlay-put rovl 'priority 1000)
1654 (overlay-put rovl 'face 'region)))
1655 ;; copy the buffer, including fontification, and switch to it:
1656 (hfy-mark-trailing-whitespace)
1657 (setq css-sheet (hfy-compile-stylesheet )
1658 css-map (hfy-compile-face-map )
1659 invis-ranges (hfy-find-invisible-ranges))
1660 (hfy-unmark-trailing-whitespace)
1661 (when rovl
1662 (delete-overlay rovl))
1663 (copy-to-buffer html-buffer (point-min) (point-max))
1664 (set-buffer html-buffer)
e1dbe924 1665 ;; rip out props that could interfere with our htmlization of the buffer:
2ea1c4aa 1666 (remove-text-properties (point-min) (point-max) hfy-ignored-properties)
acca02b0
SM
1667 ;; Apply overlay invisible spec
1668 (setq orig-ovls
1669 (sort orig-ovls
1670 (lambda (A B)
1671 (> (or (cadr (memq 'priority (overlay-properties A))) 0)
1672 (or (cadr (memq 'priority (overlay-properties B))) 0)))))
1673 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1674 ;; at this point, html-buffer retains the fontification of the parent:
1675 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1676 ;; we don't really need or want text in the html buffer to be invisible, as
1677 ;; that can make it look like we've rendered invalid xhtml when all that's
1678 ;; happened is some tags are in the invisible portions of the buffer:
1679 (setq buffer-invisibility-spec nil)
1680 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1681 ;; #####################################################################
1682 ;; if we are in etags mode, add properties to mark the anchors and links
1683 (if (and srcdir file)
1684 (progn
1685 (hfy-mark-tag-names srcdir file) ;; mark anchors
1686 (hfy-mark-tag-hrefs srcdir file))) ;; mark links
1687 ;; #####################################################################
1688 ;; mark the 'dangerous' characters
1689 ;;(message "marking dangerous characters")
1690 (hfy-html-enkludge-buffer)
1691 ;; trawl the position-based face-map, inserting span tags as we go
1692 ;; note that we cannot change any character positions before this point
1693 ;; or we will invalidate the map:
1694 ;; NB: This also means we have to trawl the map in descending file-offset
1695 ;; order, obviously.
1696 ;; ---------------------------------------------------------------------
1697 ;; Remember, inserting pushes properties to the right, which we don't
1698 ;; actually want to happen for link properties, so we have to flag
1699 ;; them and move them by hand - if you don't, you end up with
1700 ;;
1701 ;; <span class="foo"><a href="bar">texta</span><span class="bletch"></a>...
1702 ;;
1703 ;; instead of:
1704 ;;
1705 ;; <span class="foo"><a href="bar">texta</a></span><span class="bletch">...
1706 ;;
1707 ;; If my analysis of the problem is correct, we can detect link-ness by
1708 ;; either hfy-linkp or hfy-endl properties at the insertion point, but I
1709 ;; think we only need to relocate the hfy-endl property, as the hfy-linkp
1710 ;; property has already served its main purpose by this point.
1711 ;;(message "mapcar over the CSS-MAP")
62528af0 1712 ;; (message "invis-ranges:\n%S" invis-ranges)
72fe6b25
SM
1713 (dolist (point-face css-map)
1714 (let ((pt (car point-face))
1715 (fn (cdr point-face))
1716 (move-link nil))
1717 (goto-char pt)
1718 (setq move-link
1719 (or (get-text-property pt 'hfy-linkp)
1720 (get-text-property pt 'hfy-endl )))
1721 (if (eq 'end fn)
f02ff80d 1722 (funcall hfy-end-span-handler)
72fe6b25
SM
1723 (if (not (and srcdir file))
1724 nil
1725 (when move-link
1726 (remove-text-properties (point) (1+ (point)) '(hfy-endl nil))
1727 (put-text-property pt (1+ pt) 'hfy-endl t) ))
1728 ;; if we have invisible blocks, we need to do some extra magic:
f02ff80d
J
1729 (funcall hfy-begin-span-handler
1730 (hfy-lookup fn css-sheet)
1731 (and invis-ranges
1732 (format "%s" (hfy-invisible-name pt invis-ranges)))
1733 (and invis-ranges pt)
1734 (and invis-ranges (assq pt invis-ranges)))
72fe6b25
SM
1735 (if (not move-link) nil
1736 ;;(message "removing prop2 @ %d" (point))
1737 (if (remove-text-properties (point) (1+ (point)) '(hfy-endl nil))
1738 (put-text-property pt (1+ pt) 'hfy-endl t))))))
acca02b0
SM
1739 ;; #####################################################################
1740 ;; Invisibility
1741 ;; Maybe just make the text invisible in XHTML?
1742 ;; DONE -- big block of obsolete invisibility code elided here -- v
1743 ;; #####################################################################
1744 ;; (message "checking to see whether we should link...")
1745 (if (and srcdir file)
1746 (let ((lp 'hfy-link)
153c5428 1747 (pt (point-min))
acca02b0
SM
1748 (pr nil)
1749 (rr nil))
1750 ;; (message " yes we should.")
153c5428
SM
1751 ;; translate 'hfy-anchor properties to anchors
1752 (while (setq pt (next-single-property-change pt 'hfy-anchor))
1753 (if (setq pr (get-text-property pt 'hfy-anchor))
1754 (progn (goto-char pt)
1755 (remove-text-properties pt (1+ pt) '(hfy-anchor nil))
1756 (insert (concat "<a name=\"" pr "\"></a>")))))
1757 ;; translate alternate 'hfy-link and 'hfy-endl props to opening
1758 ;; and closing links. (this should avoid those spurious closes
1759 ;; we sometimes get by generating only paired tags)
1760 (setq pt (point-min))
1761 (while (setq pt (next-single-property-change pt lp))
1762 (if (not (setq pr (get-text-property pt lp))) nil
1763 (goto-char pt)
1764 (remove-text-properties pt (1+ pt) (list lp nil))
1765 (case lp
1766 (hfy-link
1767 (if (setq rr (get-text-property pt 'hfy-inst))
1768 (insert (format "<a name=\"%s\"></a>" rr)))
1769 (insert (format "<a href=\"%s\">" pr))
1770 (setq lp 'hfy-endl))
1771 (hfy-endl
1772 (insert "</a>") (setq lp 'hfy-link)) ))) ))
acca02b0
SM
1773
1774 ;; #####################################################################
1775 ;; transform the dangerous chars. This changes character positions
1776 ;; since entities have > char length.
1777 ;; note that this deletes the dangerous characters, and therefore
1778 ;; destroys any properties they may contain (such as 'hfy-endl),
1779 ;; so we have to do this after we use said properties:
1780 ;; (message "munging dangerous characters")
1781 (hfy-html-dekludge-buffer)
f02ff80d
J
1782 (unless (hfy-opt 'body-text-only)
1783 ;; insert the stylesheet at the top:
1784 (goto-char (point-min))
1785
1786 ;;(message "inserting stylesheet")
1787 (insert (hfy-sprintf-stylesheet css-sheet file))
1788
1789 (if (hfy-opt 'div-wrapper) (insert "<div class=\"default\">"))
1790 (insert "\n<pre>")
1791 (goto-char (point-max))
1792 (insert "</pre>\n")
1793 (if (hfy-opt 'div-wrapper) (insert "</div>"))
1794 ;;(message "inserting footer")
1795 (insert (funcall hfy-page-footer file)))
acca02b0 1796 ;; call any post html-generation hooks:
d1069532 1797 (run-hooks 'hfy-post-html-hook)
acca02b0
SM
1798 ;; return the html buffer
1799 (set-buffer-modified-p nil)
1800 html-buffer))
1801
f02ff80d
J
1802(defun htmlfontify-string (string)
1803 "Take a STRING and return a fontified version of it.
1804It is assumed that STRING has text properties that allow it to be
1805fontified. This is a simple convenience wrapper around
1806`htmlfontify-buffer'."
1807 (let* ((hfy-optimisations-1 (copy-sequence hfy-optimisations))
1808 (hfy-optimisations (add-to-list 'hfy-optimisations-1
1809 'skip-refontification)))
1810 (with-temp-buffer
1811 (insert string)
1812 (htmlfontify-buffer)
1813 (buffer-string))))
1814
acca02b0 1815(defun hfy-force-fontification ()
30afcdff 1816 "Try to force font-locking even when it is optimized away."
72fe6b25 1817 (run-hooks 'hfy-init-kludge-hook)
acca02b0
SM
1818 (eval-and-compile (require 'font-lock))
1819 (if (boundp 'font-lock-cache-position)
1820 (or font-lock-cache-position
1821 (set 'font-lock-cache-position (make-marker))))
1822 (if (not noninteractive)
1823 (progn
1824 (message "hfy interactive mode (%S %S)" window-system major-mode)
1825 (when (and font-lock-defaults
1826 font-lock-mode)
1827 (font-lock-fontify-region (point-min) (point-max) nil)))
1828 (message "hfy batch mode (%s:%S)"
1829 (or (buffer-file-name) (buffer-name)) major-mode)
1830 (when font-lock-defaults
1831 (font-lock-fontify-buffer)) ))
1832
0433ffa6 1833;;;###autoload
acca02b0
SM
1834(defun htmlfontify-buffer (&optional srcdir file)
1835 "Create a new buffer, named for the current buffer + a .html extension,
30afcdff 1836containing an inline CSS-stylesheet and formatted CSS-markup HTML
acca02b0
SM
1837that reproduces the look of the current Emacs buffer as closely
1838as possible.
1839
30afcdff
JB
1840Dangerous characters in the existing buffer are turned into HTML
1841entities, so you should even be able to do HTML-within-HTML
acca02b0
SM
1842fontified display.
1843
1844You should, however, note that random control or eight-bit
1845characters such as ^L (\x0c) or ¤ (\xa4) won't get mapped yet.
1846
1847If the SRCDIR and FILE arguments are set, lookup etags derived
30afcdff 1848entries in the `hfy-tags-cache' and add HTML anchors and
acca02b0
SM
1849hyperlinks as appropriate."
1850 (interactive)
1851 ;; pick up the file name in case we didn't receive it
1852 (if (not file)
1853 (progn (setq file (or (buffer-file-name) (buffer-name)))
153c5428 1854 (if (string-match "/\\([^/]*\\)\\'" file)
acca02b0
SM
1855 (setq file (match-string 1 file)))) )
1856
1857 (if (not (hfy-opt 'skip-refontification))
1858 (save-excursion ;; Keep region
1859 (hfy-force-fontification)))
06b60517 1860 (if (called-interactively-p 'any) ;; display the buffer in interactive mode:
acca02b0
SM
1861 (switch-to-buffer (hfy-fontify-buffer srcdir file))
1862 (hfy-fontify-buffer srcdir file)))
1863
1864;; recursive file listing
1865(defun hfy-list-files (directory)
1866 "Return a list of files under DIRECTORY.
1867Strips any leading \"./\" from each filename."
1868 ;;(message "hfy-list-files");;DBUG
c7015153 1869 ;; FIXME: this changes the dir of the current buffer. Is that right??
acca02b0
SM
1870 (cd directory)
1871 (mapcar (lambda (F) (if (string-match "^./\\(.*\\)" F) (match-string 1 F) F))
1872 (split-string (shell-command-to-string hfy-find-cmd))) )
1873
4c36be58 1874;; strip the filename off, return a directory name
53964682 1875;; not a particularly thorough implementation, but it will be
acca02b0
SM
1876;; fed pretty carefully, so it should be Ok:
1877(defun hfy-dirname (file)
1878 "Return everything preceding the last \"/\" from a relative filename FILE,
30afcdff
JB
1879on the assumption that this will produce a relative directory name.
1880Hardly bombproof, but good enough in the context in which it is being used."
acca02b0
SM
1881 ;;(message "hfy-dirname");;DBUG
1882 (let ((f (directory-file-name file)))
1883 (and (string-match "^\\(.*\\)/" f) (match-string 1 f))))
1884
1885;; create a directory, cf mkdir -p
1886(defun hfy-make-directory (dir)
30afcdff 1887 "Approx. equivalent of mkdir -p DIR."
acca02b0
SM
1888 ;;(message "hfy-make-directory");;DBUG
1889 (if (file-exists-p dir)
1890 (if (file-directory-p dir) t)
1891 (make-directory dir t)))
1892
1893(defun hfy-text-p (srcdir file)
30afcdff 1894 "Is SRCDIR/FILE text? Uses `hfy-istext-command' to determine this."
e3353a78
SM
1895 (let* ((cmd (format hfy-istext-command (expand-file-name file srcdir)))
1896 (rsp (shell-command-to-string cmd)))
153c5428 1897 (string-match "text" rsp)))
acca02b0
SM
1898
1899;; open a file, check fontification, if fontified, write a fontified copy
1900;; to the destination directory, otherwise just copy the file:
1901(defun hfy-copy-and-fontify-file (srcdir dstdir file)
1902 "Open FILE in SRCDIR - if fontified, write a fontified copy to DSTDIR
1903adding an extension of `hfy-extn'. Fontification is actually done by
1904`htmlfontify-buffer'. If the buffer is not fontified, just copy it."
1905 ;;(message "hfy-copy-and-fontify-file");;DBUG
1906 (let (;;(fast-lock-minimum-size hfy-fast-lock-save)
1907 ;;(font-lock-support-mode 'fast-lock-mode)
1908 ;;(window-system (or window-system 'htmlfontify))
1909 (target nil)
1910 (source nil)
1911 (html nil))
1912 (cd srcdir)
e3353a78
SM
1913 (with-current-buffer (setq source (find-file-noselect file))
1914 ;; FIXME: Shouldn't this use expand-file-name? --Stef
acca02b0
SM
1915 (setq target (concat dstdir "/" file))
1916 (hfy-make-directory (hfy-dirname target))
1917 (if (not (hfy-opt 'skip-refontification)) (hfy-force-fontification))
1918 (if (or (hfy-fontified-p) (hfy-text-p srcdir file))
1919 (progn (setq html (hfy-fontify-buffer srcdir file))
1920 (set-buffer html)
1921 (write-file (concat target hfy-extn))
1922 (kill-buffer html))
1923 ;; #o0200 == 128, but emacs20 doesn't know that
1924 (if (and (file-exists-p target) (not (file-writable-p target)))
1925 (set-file-modes target (logior (file-modes target) 128)))
1926 (copy-file (buffer-file-name source) target 'overwrite))
1927 (kill-buffer source)) ))
1928
1929;; list of tags in file in srcdir
153c5428 1930(defun hfy-tags-for-file (cache-hash file)
acca02b0 1931 "List of etags tags that have definitions in this FILE.
153c5428 1932CACHE-HASH is the tags cache."
acca02b0 1933 ;;(message "hfy-tags-for-file");;DBUG
153c5428
SM
1934 (let* ((tag-list nil))
1935 (if cache-hash
acca02b0
SM
1936 (maphash
1937 (lambda (K V)
1938 (if (assoc file V)
153c5428
SM
1939 (setq tag-list (cons K tag-list))))
1940 cache-hash))
acca02b0
SM
1941 tag-list))
1942
1943;; mark the tags native to this file for anchors
1944(defun hfy-mark-tag-names (srcdir file)
30afcdff 1945 "Mark tags in FILE (lookup SRCDIR in `hfy-tags-cache') with the `hfy-anchor'
acca02b0
SM
1946property, with a value of \"tag.line-number\"."
1947 ;;(message "(hfy-mark-tag-names %s %s)" srcdir file);;DBUG
153c5428
SM
1948 (let* ((cache-entry (assoc srcdir hfy-tags-cache))
1949 (cache-hash (cadr cache-entry)))
1950 (if cache-hash
acca02b0
SM
1951 (mapcar
1952 (lambda (TAG)
1953 (mapcar
1954 (lambda (TLIST)
1955 (if (string= file (car TLIST))
1956 (let* ((line (cadr TLIST) )
1957 (chr (caddr TLIST) )
1958 (link (format "%s.%d" TAG line) ))
1959 (put-text-property (+ 1 chr)
1960 (+ 2 chr)
1961 'hfy-anchor link))))
1962 (gethash TAG cache-hash)))
153c5428 1963 (hfy-tags-for-file cache-hash file)))))
acca02b0
SM
1964
1965(defun hfy-relstub (file &optional start)
1966 "Return a \"../\" stub of the appropriate length for the current source
30afcdff 1967tree depth, as determined from FILE (a filename).
acca02b0
SM
1968START is the offset at which to start looking for the / character in FILE."
1969 ;;(message "hfy-relstub");;DBUG
1970 (let ((c ""))
1971 (while (setq start (string-match "/" file start))
153c5428
SM
1972 (setq start (1+ start)) (setq c (concat c "../")))
1973 c))
acca02b0
SM
1974
1975(defun hfy-href-stub (this-file def-files tag)
30afcdff
JB
1976 "Return an href stub for a tag href in THIS-FILE.
1977If DEF-FILES (list of files containing definitions for the tag in question)
acca02b0
SM
1978contains only one entry, the href should link straight to that file.
1979Otherwise, the link should be to the index file.\n
1980We are not yet concerned with the file extensions/tag line number and so on at
1981this point.\n
1982If `hfy-split-index' is set, and the href wil be to an index file rather than
1983a source file, append a .X to `hfy-index-file', where X is the uppercased
1984first character of TAG.\n
30afcdff 1985See also `hfy-relstub', `hfy-index-file'."
acca02b0 1986 ;;(message "hfy-href-stub");;DBUG
e3353a78
SM
1987 ;; FIXME: Why not use something like
1988 ;; (file-relative-name (if ...) (file-name-directory this-file)) ? --Stef
acca02b0
SM
1989 (concat
1990 (hfy-relstub this-file)
1991 (if (= 1 (length def-files)) (car def-files)
1992 (if (not hfy-split-index) hfy-index-file
1993 (concat hfy-index-file "." (upcase (substring tag 0 1)))))) )
1994
1995(defun hfy-href (this-file def-files tag tag-map)
1996 "Return a relative href to the tag in question, based on\n
1997THIS-FILE `hfy-link-extn' `hfy-extn' DEF-FILES TAG and TAG-MAP\n
1998THIS-FILE is the current source file
1999DEF-FILES is a list of file containing possible link endpoints for TAG
30afcdff 2000TAG is the tag in question
acca02b0
SM
2001TAG-MAP is the entry in `hfy-tags-cache'."
2002 ;;(message "hfy-href");;DBUG
2003 (concat
2004 (hfy-href-stub this-file def-files tag)
2005 (or hfy-link-extn hfy-extn) "#" tag ;;(.src -> .html)
2006 (if (= 1 (length def-files))
2007 (concat "." (format "%d" (cadr (assoc (car def-files) tag-map)))))) )
2008
2009(defun hfy-word-regex (string)
2010 "Return a regex that matches STRING as the first `match-string', with non
2011word characters on either side."
e3353a78 2012 ;; FIXME: Should this use [^$[:alnum:]_] instead? --Stef
acca02b0
SM
2013 (concat "[^$A-Za-z_0-9]\\(" (regexp-quote string) "\\)[^A-Za-z_0-9]"))
2014
2015;; mark all tags for hyperlinking, except the tags at
2016;; their own points of definition, iyswim:
2017(defun hfy-mark-tag-hrefs (srcdir file)
30afcdff
JB
2018 "Mark href start points with the `hfy-link' prop (value: href string).\n
2019Mark href end points with the `hfy-endl' prop (value t).\n
acca02b0
SM
2020Avoid overlapping links, and mark links in descending length of
2021tag name in order to prevent subtags from usurping supertags,
2022\(eg \"term\" for \"terminal\").
2023SRCDIR is the directory being \"published\".
2024FILE is the specific file we are rendering."
2025 ;;(message "hfy-mark-tag-hrefs");;DBUG
2026 (let ((cache-entry (assoc srcdir hfy-tags-cache))
2027 (list-cache (assoc srcdir hfy-tags-sortl))
2028 (rmap-cache (assoc srcdir hfy-tags-rmap ))
2029 (no-comment (hfy-opt 'zap-comment-links))
2030 (no-strings (hfy-opt 'zap-string-links ))
2031 (cache-hash nil)
2032 (tags-list nil)
2033 (tags-rmap nil)
2034 (case-fold-search nil))
2035 ;; extract the tag mapping hashes (fwd and rev) and the tag list:
2036 (if (and (setq cache-hash (cadr cache-entry))
2037 (setq tags-rmap (cadr rmap-cache ))
2038 (setq tags-list (cadr list-cache )))
2039 (mapcar
2040 (lambda (TAG)
2041 (let* ((start nil)
2042 (stop nil)
2043 (href nil)
2044 (name nil)
2045 (case-fold-search nil)
2046 (tmp-point nil)
2047 (maybe-start nil)
2048 (face-at nil)
2049 (rmap-entry nil)
2050 (rnew-elt nil)
2051 (rmap-line nil)
2052 (tag-regex (hfy-word-regex TAG))
2053 (tag-map (gethash TAG cache-hash))
72fe6b25 2054 (tag-files (mapcar #'car tag-map)))
acca02b0
SM
2055 ;; find instances of TAG and do what needs to be done:
2056 (goto-char (point-min))
2057 (while (search-forward TAG nil 'NOERROR)
2058 (setq tmp-point (point)
2059 maybe-start (- (match-beginning 0) 1))
2060 (goto-char maybe-start)
2061 (if (not (looking-at tag-regex))
2062 nil
2063 (setq start (match-beginning 1))
2064 (setq stop (match-end 1))
2065 (setq face-at
2066 (and (or no-comment no-strings) (hfy-face-at start)))
2067 (if (listp face-at)
2068 (setq face-at (cadr (memq :inherit face-at))))
2069 (if (or (text-property-any start (1+ stop) 'hfy-linkp t)
2070 (and no-comment (eq 'font-lock-comment-face face-at))
2071 (and no-strings (eq 'font-lock-string-face face-at)))
2072 nil ;; already a link, NOOP
2073
2074 ;; set a reverse map entry:
2075 (setq rmap-line (line-number-at-pos)
2076 rmap-entry (gethash TAG tags-rmap)
2077 rnew-elt (list file rmap-line start)
2078 rmap-entry (cons rnew-elt rmap-entry)
2079 name (format "%s.%d" TAG rmap-line))
2080 (put-text-property start (1+ start) 'hfy-inst name)
2081 (puthash TAG rmap-entry tags-rmap)
2082
2083 ;; mark the link. link to index if the tag has > 1 def
2084 ;; add the line number to the #name if it does not:
2085 (setq href (hfy-href file tag-files TAG tag-map))
2086 (put-text-property start (1+ start) 'hfy-link href)
2087 (put-text-property stop (1+ stop ) 'hfy-endl t )
2088 (put-text-property start (1+ stop ) 'hfy-linkp t )))
2089 (goto-char tmp-point)) ))
2090 tags-list) )))
2091
2092(defun hfy-shell ()
5c32d3f2 2093 "Return `shell-file-name', or \"/bin/sh\" if it is a non-Bourne shell."
acca02b0
SM
2094 (if (string-match "\\<bash\\>\\|\\<sh\\>\\|\\<dash\\>" shell-file-name)
2095 shell-file-name
2096 (or hfy-shell-file-name "/bin/sh")))
2097
2098;; cache the #(tag => file line point) entries for files under srcdir
2099;; and cache the descending sorted list of tags in the relevant alist,
2100;; also keyed by srcdir:
2101(defun hfy-load-tags-cache (srcdir)
2102 "Run `hfy-etags-cmd' on SRCDIR, then call `hfy-parse-tags-buffer'."
2103 ;;(message "hfy-load-tags-cache");;DBUG
2104 (let ((etags-buffer (get-buffer-create "*hfy-tags*"))
2105 (etags-command (format hfy-etags-cmd hfy-etags-bin))
2106 (shell-file-name (hfy-shell)))
2107 (cd srcdir)
2108 (shell-command etags-command etags-buffer)
2109 (hfy-parse-tags-buffer srcdir etags-buffer)) )
2110
2111;; break this out from `hfy-load-tags-cache' to make the tar file
2112;; functionality easier to implement.
2113;; ( tar file functionality not merged here because it requires a
2114;; hacked copy of etags capable of tagging stdin: if Francesco
c91c771d 2115;; Potortì accepts a patch, or otherwise implements stdin tagging,
acca02b0
SM
2116;; then I will provide a `htmlfontify-tar-file' defun )
2117(defun hfy-parse-tags-buffer (srcdir buffer)
2118 "Parse a BUFFER containing etags formatted output, loading the
2119`hfy-tags-cache' and `hfy-tags-sortl' entries for SRCDIR."
2120 (let ((cache-entry (assoc srcdir hfy-tags-cache))
2121 (tlist-cache (assoc srcdir hfy-tags-sortl))
2122 (trmap-cache (assoc srcdir hfy-tags-rmap ))
2123 (cache-hash nil) (trmap-hash nil) (tags-list nil)
2124 (hash-entry nil) (tag-string nil) (tag-line nil)
2125 (tag-point nil) (new-entry nil) (etags-file nil))
2126
e1dbe924 2127 ;; (re)initialize the tag reverse map:
acca02b0
SM
2128 (if trmap-cache (setq trmap-hash (cadr trmap-cache))
2129 (setq trmap-hash (make-hash-table :test 'equal))
2130 (setq hfy-tags-rmap (list (list srcdir trmap-hash) hfy-tags-rmap)))
2131 (clrhash trmap-hash)
2132
e1dbe924 2133 ;; (re)initialize the tag cache:
acca02b0
SM
2134 (if cache-entry (setq cache-hash (cadr cache-entry))
2135 (setq cache-hash (make-hash-table :test 'equal))
2136 (setq hfy-tags-cache (list (list srcdir cache-hash) hfy-tags-cache)))
2137 (clrhash cache-hash)
2138
2139 ;; cache the TAG => ((file line point) (file line point) ... ) entries:
e3353a78 2140 (with-current-buffer buffer
acca02b0
SM
2141 (goto-char (point-min))
2142
2143 (while (and (looking-at "^\x0c") (= 0 (forward-line 1)))
2144 ;;(message "^L boundary")
2145 (if (and (looking-at "^\\(.+\\),\\([0-9]+\\)$")
2146 (= 0 (forward-line 1)))
2147 (progn
2148 (setq etags-file (match-string 1))
2149 ;;(message "TAGS for file: %s" etags-file)
2150 (while (and (looking-at hfy-etag-regex) (= 0 (forward-line 1)))
2151 (setq tag-string (match-string 1))
2152 (if (= 0 (length tag-string)) nil ;; noop
2153 (setq tag-line (round (string-to-number (match-string 2))))
2154 (setq tag-point (round (string-to-number (match-string 3))))
2155 (setq hash-entry (gethash tag-string cache-hash))
2156 (setq new-entry (list etags-file tag-line tag-point))
72fe6b25 2157 (push new-entry hash-entry)
acca02b0
SM
2158 ;;(message "HASH-ENTRY %s %S" tag-string new-entry)
2159 (puthash tag-string hash-entry cache-hash)))) )))
2160
2161 ;; cache a list of tags in descending length order:
06b60517 2162 (maphash (lambda (K _V) (push K tags-list)) cache-hash)
acca02b0
SM
2163 (setq tags-list (sort tags-list (lambda (A B) (< (length B) (length A)))))
2164
2165 ;; put the tag list into the cache:
2166 (if tlist-cache (setcar (cdr tlist-cache) tags-list)
72fe6b25 2167 (push (list srcdir tags-list) hfy-tags-sortl))
acca02b0
SM
2168
2169 ;; return the number of tags found:
2170 (length tags-list) ))
2171
2172(defun hfy-prepare-index-i (srcdir dstdir filename &optional stub map)
2173 "Prepare a tags index buffer for SRCDIR.
2174`hfy-tags-cache' must already have an entry for SRCDIR for this to work.
2175`hfy-page-header', `hfy-page-footer', `hfy-link-extn' and `hfy-extn'
2176all play a part here.\n
30afcdff 2177If STUB is set, prepare an (appropriately named) index buffer
acca02b0
SM
2178specifically for entries beginning with STUB.\n
2179If MAP is set, use that instead of `hfy-tags-cache'.
2180FILENAME is the name of the file being indexed.
2181DSTDIR is the output directory, where files will be written."
2182 ;;(message "hfy-write-index");;DBUG
2183 (let ((cache-entry (assoc srcdir (or map hfy-tags-cache)))
2184 (cache-hash nil)
2185 (tag-list nil)
2186 (index-file
2187 (concat filename (if stub (concat "." stub) "") hfy-extn))
2188 (index-buf nil))
2189 (if (not (and cache-entry
2190 (setq cache-hash (cadr cache-entry))
2191 (setq index-buf (get-buffer-create index-file))))
2192 nil ;; noop
06b60517 2193 (maphash (lambda (K _V) (push K tag-list)) cache-hash)
acca02b0
SM
2194 (setq tag-list (sort tag-list 'string<))
2195 (set-buffer index-buf)
2196 (erase-buffer)
2197 (insert (funcall hfy-page-header filename "<!-- CSS -->"))
2198 (insert "<table class=\"index\">\n")
2199
72fe6b25
SM
2200 (dolist (TAG tag-list)
2201 (let ((tag-started nil))
2202 (dolist (DEF (gethash TAG cache-hash))
2203 (if (and stub (not (string-match (concat "^" stub) TAG)))
2204 nil ;; we have a stub and it didn't match: NOOP
2205 (let ((file (car DEF))
2206 (line (cadr DEF)))
2207 (insert
2208 (format
2209 (concat
2210 " <tr> \n"
2211 " <td>%s</td> \n"
2212 " <td><a href=\"%s%s\">%s</a></td> \n"
2213 " <td><a href=\"%s%s#%s.%d\">%d</a></td>\n"
2214 " </tr> \n")
2215 (if (string= TAG tag-started) "&nbsp;"
2216 (format "<a name=\"%s\">%s</a>" TAG TAG))
2217 file (or hfy-link-extn hfy-extn) file
2218 file (or hfy-link-extn hfy-extn) TAG line line))
2219 (setq tag-started TAG))))))
acca02b0
SM
2220 (insert "</table>\n")
2221 (insert (funcall hfy-page-footer filename))
2222 (and dstdir (cd dstdir))
2223 (set-visited-file-name index-file)
2224 index-buf) ))
2225
2226(defun hfy-prepare-index (srcdir dstdir)
30afcdff 2227 "Return a list of index buffer(s), as determined by `hfy-split-index'.
acca02b0
SM
2228SRCDIR and DSTDIR are the source and output directories respectively."
2229 (if (not hfy-split-index)
2230 (list (hfy-prepare-index-i srcdir dstdir hfy-index-file nil))
2231 (let ((stub-list nil)
2232 (cache-hash nil)
2233 (index-list nil)
2234 (cache-entry (assoc srcdir hfy-tags-cache)))
2235 (if (and cache-entry (setq cache-hash (cadr cache-entry)))
2236 (maphash
06b60517 2237 (lambda (K _V)
acca02b0
SM
2238 (let ((stub (upcase (substring K 0 1))))
2239 (if (member stub stub-list)
2240 nil ;; seen this already: NOOP
2241 (setq
2242 stub-list (cons stub stub-list)
2243 index-list (cons (hfy-prepare-index-i srcdir
2244 dstdir
2245 hfy-index-file
2246 stub)
153c5428
SM
2247 index-list)) )))
2248 cache-hash) )
2249 index-list)))
acca02b0
SM
2250
2251(defun hfy-prepare-tag-map (srcdir dstdir)
30afcdff
JB
2252 "Prepare the counterpart(s) to the index buffer(s) - a list of buffers
2253with the same structure, but listing (and linking to) instances of tags
2254\(as opposed to their definitions).\n
acca02b0 2255SRCDIR and DSTDIR are the source and output directories respectively.
30afcdff 2256See also `hfy-prepare-index', `hfy-split-index'."
acca02b0
SM
2257 (if (not hfy-split-index)
2258 (list (hfy-prepare-index-i srcdir
2259 dstdir
2260 hfy-instance-file
2261 nil
2262 hfy-tags-rmap))
2263 (let ((stub-list nil)
2264 (cache-hash nil)
2265 (index-list nil)
2266 (cache-entry (assoc srcdir hfy-tags-rmap)))
2267
2268 (if (and cache-entry (setq cache-hash (cadr cache-entry)))
2269 (maphash
06b60517 2270 (lambda (K _V)
acca02b0
SM
2271 (let ((stub (upcase (substring K 0 1))))
2272 (if (member stub stub-list)
2273 nil ;; seen this already: NOOP
2274 (setq
2275 stub-list (cons stub stub-list)
2276 index-list (cons (hfy-prepare-index-i srcdir
2277 dstdir
2278 hfy-instance-file
2279 stub
2280 hfy-tags-rmap)
153c5428
SM
2281 index-list)) )))
2282 cache-hash) )
2283 index-list)))
acca02b0
SM
2284
2285(defun hfy-subtract-maps (srcdir)
2286 "Internal function - strips definitions of tags from the instance map.
2287SRCDIR is the directory being \"published\".
30afcdff 2288See also `hfy-tags-cache', `hfy-tags-rmap'."
acca02b0
SM
2289 (let ((new-list nil)
2290 (old-list nil)
2291 (def-list nil)
2292 (exc-list nil)
2293 (fwd-map (cadr (assoc srcdir hfy-tags-cache)))
2294 (rev-map (cadr (assoc srcdir hfy-tags-rmap )))
2295 (taglist (cadr (assoc srcdir hfy-tags-sortl))))
72fe6b25
SM
2296 (dolist (TAG taglist)
2297 (setq def-list (gethash TAG fwd-map)
2298 old-list (gethash TAG rev-map)
2299 exc-list (mapcar (lambda (P) (list (car P) (cadr P))) def-list)
2300 new-list nil)
2301 (dolist (P old-list)
2302 (or (member (list (car P) (cadr P)) exc-list)
2303 (push P new-list)))
2304 (puthash TAG new-list rev-map))))
acca02b0
SM
2305
2306(defun htmlfontify-run-etags (srcdir)
2307 "Load the etags cache for SRCDIR.
30afcdff 2308See also `hfy-load-tags-cache'."
acca02b0 2309 (interactive "D source directory: ")
153c5428 2310 (hfy-load-tags-cache (directory-file-name srcdir)))
acca02b0
SM
2311
2312;;(defun hfy-test-read-args (foo bar)
2313;; (interactive "D source directory: \nD target directory: ")
2314;; (message "foo: %S\nbar: %S" foo bar))
2315
2316(defun hfy-save-kill-buffers (buffer-list &optional dstdir)
72fe6b25
SM
2317 (dolist (B buffer-list)
2318 (set-buffer B)
2319 (and dstdir (file-directory-p dstdir) (cd dstdir))
2320 (save-buffer)
2321 (kill-buffer B)))
acca02b0 2322
0433ffa6 2323;;;###autoload
acca02b0
SM
2324(defun htmlfontify-copy-and-link-dir (srcdir dstdir &optional f-ext l-ext)
2325 "Trawl SRCDIR and write fontified-and-hyperlinked output in DSTDIR.
2326F-EXT and L-EXT specify values for `hfy-extn' and `hfy-link-extn'.\n
2327You may also want to set `hfy-page-header' and `hfy-page-footer'."
2328 (interactive "D source directory: \nD output directory: ")
2329 ;;(message "htmlfontify-copy-and-link-dir")
2330 (setq srcdir (directory-file-name srcdir))
2331 (setq dstdir (directory-file-name dstdir))
2332 (let ((source-files "SETME: list of source files, relative to srcdir")
2333 (tr-cache (assoc srcdir hfy-tags-rmap))
2334 (hfy-extn (or f-ext ".html"))
2335 (hfy-link-extn (or l-ext ".html")))
2336 ;; oops, forgot to load etags for srcdir:
2337 (if tr-cache nil
2338 (message "autoload of tags cache")
2339 (hfy-load-tags-cache srcdir)
2340 (setq tr-cache (assoc srcdir hfy-tags-rmap)))
2341 ;; clear out the old cache:
2342 (clrhash (cadr tr-cache))
2343 (hfy-make-directory dstdir)
2344 (setq source-files (hfy-list-files srcdir))
72fe6b25
SM
2345 (dolist (file source-files)
2346 (hfy-copy-and-fontify-file srcdir dstdir file))
acca02b0
SM
2347 (hfy-subtract-maps srcdir)
2348 (hfy-save-kill-buffers (hfy-prepare-index srcdir dstdir) dstdir)
2349 (hfy-save-kill-buffers (hfy-prepare-tag-map srcdir dstdir) dstdir) ))
2350
2351;; name of the init file we want:
2352(defun hfy-initfile ()
2353 "Return the expected location of the htmlfontify specific init/custom file."
2354 (let* ((file (or (getenv "HFY_INITFILE") ".hfy.el")))
2355 (expand-file-name file "~") ))
2356
2357
2358;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2359;; incomplete as yet : transfer hook settings to hfy init file:
2360;; (defalias 'hfy-set-hooks 'custom-set-variables)
2361
2362;; (defun hfy-pp-hook (H)
153c5428 2363;; (and (string-match "-hook\\'" (symbol-name H))
acca02b0
SM
2364;; (boundp H)
2365;; (symbol-value H)
2366;; (insert (format "\n '(%S %S)" H (symbol-value H)))
2367;; )
2368;; )
2369
2370;; (defun hfy-save-hooks ()
2371;; (let ((custom-file (hfy-initfile)))
2372;; (custom-save-delete 'hfy-set-hooks)
2373;; (let ((standard-output (current-buffer)))
2374;; (princ "(hfy-set-hooks\n;;auto-generated, only one copy allowed\n")
2375;; (mapatoms 'hfy-pp-hook)
2376;; (insert "\n)")
2377;; )
2378;; )
2379;; )
2380;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2381
2382(defalias 'hfy-init-progn 'progn)
2383
2384(defun hfy-save-initvar (sym)
2385 (princ (format "(setq %s\n '" sym))
2386 (pp (symbol-value sym))
2387 (princ ")\n"))
2388
2389(defun htmlfontify-save-initfile ()
2390 "Save the htmlfontify settings to the htmlfontify init file."
2391 (interactive)
2392 (let* ((start-pos nil)
2393 (custom-file (hfy-initfile))
2394 (standard-output (find-file-noselect custom-file 'nowarn)))
2395 (save-excursion
2396 (custom-save-delete 'hfy-init-progn)
2397 (setq start-pos (point))
2398 (princ "(hfy-init-progn\n;;auto-generated, only one copy allowed\n")
72fe6b25
SM
2399 ;; FIXME: This saving&restoring of global customization
2400 ;; variables can interfere with other customization settings for
2401 ;; those vars (in .emacs or in Customize).
acca02b0 2402 (mapc 'hfy-save-initvar
72fe6b25 2403 '(auto-mode-alist interpreter-mode-alist))
acca02b0
SM
2404 (princ ")\n")
2405 (indent-region start-pos (point) nil))
2406 (custom-save-all) ))
2407
2408(defun htmlfontify-load-initfile ()
2409 "Load the htmlfontify specific init/custom file."
2410 (interactive)
2411 (let ((file (hfy-initfile)))
2412 (load file 'NOERROR nil nil) ))
2413
cbcfee6e 2414\f
ba318903 2415;;;### (autoloads nil "hfy-cmap" "hfy-cmap.el" "27dc80b0f7187aaf582805a8f887819a")
fb029763 2416;;; Generated autoloads from hfy-cmap.el
920d0654 2417
cbcfee6e
GM
2418(autoload 'htmlfontify-load-rgb-file "hfy-cmap" "\
2419Load an X11 style rgb.txt FILE.
2420Search `hfy-rgb-load-path' if FILE is not specified.
2421Loads the variable `hfy-rgb-txt-colour-map', which is used by
2422`hfy-fallback-colour-values'.
2423
2424\(fn &optional FILE)" t nil)
2425
2426(autoload 'hfy-fallback-colour-values "hfy-cmap" "\
2427Use a fallback method for obtaining the rgb values for a color.
2428
2429\(fn COLOUR-STRING)" nil nil)
2430
2431;;;***
2432\f
2433
acca02b0 2434(provide 'htmlfontify)
acca02b0 2435
5d5870b8
GM
2436;; Local Variables:
2437;; coding: utf-8
2438;; End:
2439
cbcfee6e 2440;;; htmlfontify.el ends here