Mercurial > hg > Members > kokubo > emacs
comparison .emacs.d/htmlize.el @ 0:2764b4f45f9f
1st commit
author | Shohei KOKUBO <e105744@ie.u-ryukyu.ac.jp> |
---|---|
date | Mon, 21 Apr 2014 04:30:59 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:2764b4f45f9f |
---|---|
1 ;;; htmlize.el --- Convert buffer text and decorations to HTML. | |
2 | |
3 ;; Copyright (C) 1997-2003,2005,2006,2009,2011,2012 Hrvoje Niksic | |
4 | |
5 ;; Author: Hrvoje Niksic <hniksic@xemacs.org> | |
6 ;; Keywords: hypermedia, extensions | |
7 ;; Version: 1.47 | |
8 | |
9 ;; This program is free software; you can redistribute it and/or modify | |
10 ;; it under the terms of the GNU General Public License as published by | |
11 ;; the Free Software Foundation; either version 2, or (at your option) | |
12 ;; any later version. | |
13 | |
14 ;; This program is distributed in the hope that it will be useful, | |
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 ;; GNU General Public License for more details. | |
18 | |
19 ;; You should have received a copy of the GNU General Public License | |
20 ;; along with this program; see the file COPYING. If not, write to the | |
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
22 ;; Boston, MA 02111-1307, USA. | |
23 | |
24 ;;; Commentary: | |
25 | |
26 ;; This package converts the buffer text and the associated | |
27 ;; decorations to HTML. Mail to <hniksic@xemacs.org> to discuss | |
28 ;; features and additions. All suggestions are more than welcome. | |
29 | |
30 ;; To use it, just switch to the buffer you want HTML-ized and type | |
31 ;; `M-x htmlize-buffer'. You will be switched to a new buffer that | |
32 ;; contains the resulting HTML code. You can edit and inspect this | |
33 ;; buffer, or you can just save it with C-x C-w. `M-x htmlize-file' | |
34 ;; will find a file, fontify it, and save the HTML version in | |
35 ;; FILE.html, without any additional intervention. `M-x | |
36 ;; htmlize-many-files' allows you to htmlize any number of files in | |
37 ;; the same manner. `M-x htmlize-many-files-dired' does the same for | |
38 ;; files marked in a dired buffer. | |
39 | |
40 ;; htmlize supports three types of HTML output, selected by setting | |
41 ;; `htmlize-output-type': `css', `inline-css', and `font'. In `css' | |
42 ;; mode, htmlize uses cascading style sheets to specify colors; it | |
43 ;; generates classes that correspond to Emacs faces and uses <span | |
44 ;; class=FACE>...</span> to color parts of text. In this mode, the | |
45 ;; produced HTML is valid under the 4.01 strict DTD, as confirmed by | |
46 ;; the W3C validator. `inline-css' is like `css', except the CSS is | |
47 ;; put directly in the STYLE attribute of the SPAN element, making it | |
48 ;; possible to paste the generated HTML into existing HTML documents. | |
49 ;; In `font' mode, htmlize uses <font color="...">...</font> to | |
50 ;; colorize HTML, which is not standard-compliant, but works better in | |
51 ;; older browsers. `css' mode is the default. | |
52 | |
53 ;; You can also use htmlize from your Emacs Lisp code. When called | |
54 ;; non-interactively, `htmlize-buffer' and `htmlize-region' will | |
55 ;; return the resulting HTML buffer, but will not change current | |
56 ;; buffer or move the point. htmlize will do its best to work on | |
57 ;; non-windowing Emacs sessions but the result will be limited to | |
58 ;; colors supported by the terminal. | |
59 | |
60 ;; htmlize aims for compatibility with Emacsen version 21 and later. | |
61 ;; Please let me know if it doesn't work on the version of XEmacs or | |
62 ;; GNU Emacs that you are using. The package relies on the presence | |
63 ;; of CL extensions, especially for cross-emacs compatibility; please | |
64 ;; don't try to remove that dependency. I see no practical problems | |
65 ;; with using the full power of the CL extensions, except that one | |
66 ;; might learn to like them too much. | |
67 | |
68 ;; The latest version is available as a git repository at: | |
69 ;; | |
70 ;; <http://fly.srk.fer.hr/~hniksic/emacs/htmlize.git> | |
71 ;; | |
72 ;; The snapshot of the latest release can be obtained at: | |
73 ;; | |
74 ;; <http://fly.srk.fer.hr/~hniksic/emacs/htmlize.el.cgi> | |
75 ;; | |
76 ;; You can find a sample of htmlize's output (possibly generated with | |
77 ;; an older version) at: | |
78 ;; | |
79 ;; <http://fly.srk.fer.hr/~hniksic/emacs/htmlize.el.html> | |
80 | |
81 ;; Thanks go to the many people who have sent reports and contributed | |
82 ;; comments, suggestions, and fixes. They include Ron Gut, Bob | |
83 ;; Weiner, Toni Drabik, Peter Breton, Ville Skytta, Thomas Vogels, | |
84 ;; Juri Linkov, Maciek Pasternacki, and many others. | |
85 | |
86 ;; User quotes: "You sir, are a sick, sick, _sick_ person. :)" | |
87 ;; -- Bill Perry, author of Emacs/W3 | |
88 | |
89 | |
90 ;;; Code: | |
91 | |
92 (require 'cl) | |
93 (eval-when-compile | |
94 (defvar unresolved) | |
95 (if (string-match "XEmacs" emacs-version) | |
96 (byte-compiler-options | |
97 (warnings (- unresolved)))) | |
98 (defvar font-lock-auto-fontify) | |
99 (defvar font-lock-support-mode) | |
100 (defvar global-font-lock-mode)) | |
101 | |
102 (defconst htmlize-version "1.47") | |
103 | |
104 (defgroup htmlize nil | |
105 "Convert buffer text and faces to HTML." | |
106 :group 'hypermedia) | |
107 | |
108 (defcustom htmlize-head-tags "" | |
109 "Additional tags to insert within HEAD of the generated document." | |
110 :type 'string | |
111 :group 'htmlize) | |
112 | |
113 (defcustom htmlize-output-type 'css | |
114 "Output type of generated HTML, one of `css', `inline-css', or `font'. | |
115 When set to `css' (the default), htmlize will generate a style sheet | |
116 with description of faces, and use it in the HTML document, specifying | |
117 the faces in the actual text with <span class=\"FACE\">. | |
118 | |
119 When set to `inline-css', the style will be generated as above, but | |
120 placed directly in the STYLE attribute of the span ELEMENT: <span | |
121 style=\"STYLE\">. This makes it easier to paste the resulting HTML to | |
122 other documents. | |
123 | |
124 When set to `font', the properties will be set using layout tags | |
125 <font>, <b>, <i>, <u>, and <strike>. | |
126 | |
127 `css' output is normally preferred, but `font' is still useful for | |
128 supporting old, pre-CSS browsers, and both `inline-css' and `font' for | |
129 easier embedding of colorized text in foreign HTML documents (no style | |
130 sheet to carry around)." | |
131 :type '(choice (const css) (const inline-css) (const font)) | |
132 :group 'htmlize) | |
133 | |
134 (defcustom htmlize-use-images t | |
135 "Whether htmlize generates `img' for images attached to buffer contents." | |
136 :type 'boolean | |
137 :group 'htmlize) | |
138 | |
139 (defcustom htmlize-force-inline-images nil | |
140 "Non-nil means generate all images inline using data URLs. | |
141 Normally htmlize converts image descriptors with :file properties to | |
142 relative URIs, and those with :data properties to data URIs. With this | |
143 flag set, the images specified as a file name are loaded into memory and | |
144 embedded in the HTML as data URIs." | |
145 :type 'boolean | |
146 :group 'htmlize) | |
147 | |
148 (defcustom htmlize-max-alt-text 100 | |
149 "Maximum size of text to use as ALT text in images. | |
150 | |
151 Normally when htmlize encounters text covered by the `display' property | |
152 that specifies an image, it generates an `alt' attribute containing the | |
153 original text. If the text is larger than `htmlize-max-alt-text' characters, | |
154 this will not be done.") | |
155 | |
156 (defcustom htmlize-transform-image 'htmlize-default-transform-image | |
157 "Function called to modify the image descriptor. | |
158 | |
159 The function is called with the image descriptor found in the buffer and | |
160 the text the image is supposed to replace. It should return a (possibly | |
161 different) image descriptor property list or a replacement string to use | |
162 instead of of the original buffer text. | |
163 | |
164 Returning nil is the same as returning the original text." | |
165 :type 'boolean | |
166 :group 'htmlize) | |
167 | |
168 (defcustom htmlize-generate-hyperlinks t | |
169 "Non-nil means auto-generate the links from URLs and mail addresses in buffer. | |
170 | |
171 This is on by default; set it to nil if you don't want htmlize to | |
172 autogenerate such links. Note that this option only turns off automatic | |
173 search for contents that looks like URLs and converting them to links. | |
174 It has no effect on whether htmlize respects the `htmlize-link' property." | |
175 :type 'boolean | |
176 :group 'htmlize) | |
177 | |
178 (defcustom htmlize-hyperlink-style " | |
179 a { | |
180 color: inherit; | |
181 background-color: inherit; | |
182 font: inherit; | |
183 text-decoration: inherit; | |
184 } | |
185 a:hover { | |
186 text-decoration: underline; | |
187 } | |
188 " | |
189 "The CSS style used for hyperlinks when in CSS mode." | |
190 :type 'string | |
191 :group 'htmlize) | |
192 | |
193 (defcustom htmlize-replace-form-feeds t | |
194 "Non-nil means replace form feeds in source code with HTML separators. | |
195 Form feeds are the ^L characters at line beginnings that are sometimes | |
196 used to separate sections of source code. If this variable is set to | |
197 `t', form feed characters are replaced with the <hr> separator. If this | |
198 is a string, it specifies the replacement to use. Note that <pre> is | |
199 temporarily closed before the separator is inserted, so the default | |
200 replacement is effectively \"</pre><hr /><pre>\". If you specify | |
201 another replacement, don't forget to close and reopen the <pre> if you | |
202 want the output to remain valid HTML. | |
203 | |
204 If you need more elaborate processing, set this to nil and use | |
205 htmlize-after-hook." | |
206 :type 'boolean | |
207 :group 'htmlize) | |
208 | |
209 (defcustom htmlize-html-charset nil | |
210 "The charset declared by the resulting HTML documents. | |
211 When non-nil, causes htmlize to insert the following in the HEAD section | |
212 of the generated HTML: | |
213 | |
214 <meta http-equiv=\"Content-Type\" content=\"text/html; charset=CHARSET\"> | |
215 | |
216 where CHARSET is the value you've set for htmlize-html-charset. Valid | |
217 charsets are defined by MIME and include strings like \"iso-8859-1\", | |
218 \"iso-8859-15\", \"utf-8\", etc. | |
219 | |
220 If you are using non-Latin-1 charsets, you might need to set this for | |
221 your documents to render correctly. Also, the W3C validator requires | |
222 submitted HTML documents to declare a charset. So if you care about | |
223 validation, you can use this to prevent the validator from bitching. | |
224 | |
225 Needless to say, if you set this, you should actually make sure that | |
226 the buffer is in the encoding you're claiming it is in. (This is | |
227 normally achieved by using the correct file coding system for the | |
228 buffer.) If you don't understand what that means, you should probably | |
229 leave this option in its default setting." | |
230 :type '(choice (const :tag "Unset" nil) | |
231 string) | |
232 :group 'htmlize) | |
233 | |
234 (defcustom htmlize-convert-nonascii-to-entities t | |
235 "Whether non-ASCII characters should be converted to HTML entities. | |
236 | |
237 When this is non-nil, characters with codes in the 128-255 range will be | |
238 considered Latin 1 and rewritten as \"&#CODE;\". Characters with codes | |
239 above 255 will be converted to \"&#UCS;\", where UCS denotes the Unicode | |
240 code point of the character. If the code point cannot be determined, | |
241 the character will be copied unchanged, as would be the case if the | |
242 option were nil. | |
243 | |
244 When the option is nil, the non-ASCII characters are copied to HTML | |
245 without modification. In that case, the web server and/or the browser | |
246 must be set to understand the encoding that was used when saving the | |
247 buffer. (You might also want to specify it by setting | |
248 `htmlize-html-charset'.) | |
249 | |
250 Note that in an HTML entity \"&#CODE;\", CODE is always a UCS code point, | |
251 which has nothing to do with the charset the page is in. For example, | |
252 \"©\" *always* refers to the copyright symbol, regardless of charset | |
253 specified by the META tag or the charset sent by the HTTP server. In | |
254 other words, \"©\" is exactly equivalent to \"©\". | |
255 | |
256 For most people htmlize will work fine with this option left at the | |
257 default setting; don't change it unless you know what you're doing." | |
258 :type 'sexp | |
259 :group 'htmlize) | |
260 | |
261 (defcustom htmlize-ignore-face-size 'absolute | |
262 "Whether face size should be ignored when generating HTML. | |
263 If this is nil, face sizes are used. If set to t, sizes are ignored | |
264 If set to `absolute', only absolute size specifications are ignored. | |
265 Please note that font sizes only work with CSS-based output types." | |
266 :type '(choice (const :tag "Don't ignore" nil) | |
267 (const :tag "Ignore all" t) | |
268 (const :tag "Ignore absolute" absolute)) | |
269 :group 'htmlize) | |
270 | |
271 (defcustom htmlize-css-name-prefix "" | |
272 "The prefix used for CSS names. | |
273 The CSS names that htmlize generates from face names are often too | |
274 generic for CSS files; for example, `font-lock-type-face' is transformed | |
275 to `type'. Use this variable to add a prefix to the generated names. | |
276 The string \"htmlize-\" is an example of a reasonable prefix." | |
277 :type 'string | |
278 :group 'htmlize) | |
279 | |
280 (defcustom htmlize-use-rgb-txt t | |
281 "Whether `rgb.txt' should be used to convert color names to RGB. | |
282 | |
283 This conversion means determining, for instance, that the color | |
284 \"IndianRed\" corresponds to the (205, 92, 92) RGB triple. `rgb.txt' | |
285 is the X color database that maps hundreds of color names to such RGB | |
286 triples. When this variable is non-nil, `htmlize' uses `rgb.txt' to | |
287 look up color names. | |
288 | |
289 If this variable is nil, htmlize queries Emacs for RGB components of | |
290 colors using `color-instance-rgb-components' and `color-values'. | |
291 This can yield incorrect results on non-true-color displays. | |
292 | |
293 If the `rgb.txt' file is not found (which will be the case if you're | |
294 running Emacs on non-X11 systems), this option is ignored." | |
295 :type 'boolean | |
296 :group 'htmlize) | |
297 | |
298 (defcustom htmlize-html-major-mode nil | |
299 "The mode the newly created HTML buffer will be put in. | |
300 Set this to nil if you prefer the default (fundamental) mode." | |
301 :type '(radio (const :tag "No mode (fundamental)" nil) | |
302 (function-item html-mode) | |
303 (function :tag "User-defined major mode")) | |
304 :group 'htmlize) | |
305 | |
306 (defvar htmlize-before-hook nil | |
307 "Hook run before htmlizing a buffer. | |
308 The hook functions are run in the source buffer (not the resulting HTML | |
309 buffer).") | |
310 | |
311 (defvar htmlize-after-hook nil | |
312 "Hook run after htmlizing a buffer. | |
313 Unlike `htmlize-before-hook', these functions are run in the generated | |
314 HTML buffer. You may use them to modify the outlook of the final HTML | |
315 output.") | |
316 | |
317 (defvar htmlize-file-hook nil | |
318 "Hook run by `htmlize-file' after htmlizing a file, but before saving it.") | |
319 | |
320 (defvar htmlize-buffer-places) | |
321 | |
322 ;;; Some cross-Emacs compatibility. | |
323 | |
324 ;; I try to conditionalize on features rather than Emacs version, but | |
325 ;; in some cases checking against the version *is* necessary. | |
326 (defconst htmlize-running-xemacs (string-match "XEmacs" emacs-version)) | |
327 | |
328 ;; We need a function that efficiently finds the next change of a | |
329 ;; property regardless of whether the change occurred because of a | |
330 ;; text property or an extent/overlay. | |
331 (cond | |
332 (htmlize-running-xemacs | |
333 (defun htmlize-next-change (pos prop &optional limit) | |
334 (if prop | |
335 (next-single-char-property-change pos prop nil (or limit (point-max))) | |
336 (next-property-change pos nil (or limit (point-max))))) | |
337 (defun htmlize-next-face-change (pos &optional limit) | |
338 (htmlize-next-change pos 'face limit))) | |
339 (t | |
340 (defun htmlize-next-change (pos prop &optional limit) | |
341 (if prop | |
342 (next-single-char-property-change pos prop nil limit) | |
343 (next-char-property-change pos limit))) | |
344 (defun htmlize-overlay-faces-at (pos) | |
345 (delq nil (mapcar (lambda (o) (overlay-get o 'face)) (overlays-at pos)))) | |
346 (defun htmlize-next-face-change (pos &optional limit) | |
347 ;; (htmlize-next-change pos 'face limit) would skip over entire | |
348 ;; overlays that specify the `face' property, even when they | |
349 ;; contain smaller text properties that also specify `face'. | |
350 ;; Emacs display engine merges those faces, and so must we. | |
351 (or limit | |
352 (setq limit (point-max))) | |
353 (let ((next-prop (next-single-property-change pos 'face nil limit)) | |
354 (overlay-faces (htmlize-overlay-faces-at pos))) | |
355 (while (progn | |
356 (setq pos (next-overlay-change pos)) | |
357 (and (< pos next-prop) | |
358 (equal overlay-faces (htmlize-overlay-faces-at pos))))) | |
359 (setq pos (min pos next-prop)) | |
360 ;; Additionally, we include the entire region that specifies the | |
361 ;; `display' property. | |
362 (when (get-char-property pos 'display) | |
363 (setq pos (next-single-char-property-change pos 'display nil limit))) | |
364 pos))) | |
365 (t | |
366 (error "htmlize requires next-single-property-change or \ | |
367 next-single-char-property-change"))) | |
368 | |
369 (defmacro htmlize-lexlet (&rest letforms) | |
370 (declare (indent 1) (debug let)) | |
371 (if (and (boundp 'lexical-binding) | |
372 lexical-binding) | |
373 `(let ,@letforms) | |
374 ;; cl extensions have a macro implementing lexical let | |
375 `(lexical-let ,@letforms))) | |
376 | |
377 ;; Simple overlay emulation for XEmacs | |
378 | |
379 (cond | |
380 (htmlize-running-xemacs | |
381 (defalias 'htmlize-make-overlay 'make-extent) | |
382 (defalias 'htmlize-overlay-put 'set-extent-property) | |
383 (defalias 'htmlize-overlay-get 'extent-property) | |
384 (defun htmlize-overlays-in (beg end) (extent-list nil beg end)) | |
385 (defalias 'htmlize-delete-overlay 'detach-extent)) | |
386 (t | |
387 (defalias 'htmlize-make-overlay 'make-overlay) | |
388 (defalias 'htmlize-overlay-put 'overlay-put) | |
389 (defalias 'htmlize-overlay-get 'overlay-get) | |
390 (defalias 'htmlize-overlays-in 'overlays-in) | |
391 (defalias 'htmlize-delete-overlay 'delete-overlay))) | |
392 | |
393 | |
394 ;;; Transformation of buffer text: HTML escapes, untabification, etc. | |
395 | |
396 (defvar htmlize-basic-character-table | |
397 ;; Map characters in the 0-127 range to either one-character strings | |
398 ;; or to numeric entities. | |
399 (let ((table (make-vector 128 ?\0))) | |
400 ;; Map characters in the 32-126 range to themselves, others to | |
401 ;; &#CODE entities; | |
402 (dotimes (i 128) | |
403 (setf (aref table i) (if (and (>= i 32) (<= i 126)) | |
404 (char-to-string i) | |
405 (format "&#%d;" i)))) | |
406 ;; Set exceptions manually. | |
407 (setf | |
408 ;; Don't escape newline, carriage return, and TAB. | |
409 (aref table ?\n) "\n" | |
410 (aref table ?\r) "\r" | |
411 (aref table ?\t) "\t" | |
412 ;; Escape &, <, and >. | |
413 (aref table ?&) "&" | |
414 (aref table ?<) "<" | |
415 (aref table ?>) ">" | |
416 ;; Not escaping '"' buys us a measurable speedup. It's only | |
417 ;; necessary to quote it for strings used in attribute values, | |
418 ;; which htmlize doesn't typically do. | |
419 ;(aref table ?\") """ | |
420 ) | |
421 table)) | |
422 | |
423 ;; A cache of HTML representation of non-ASCII characters. Depending | |
424 ;; on the setting of `htmlize-convert-nonascii-to-entities', this maps | |
425 ;; non-ASCII characters to either "&#<code>;" or "<char>" (mapconcat's | |
426 ;; mapper must always return strings). It's only filled as characters | |
427 ;; are encountered, so that in a buffer with e.g. French text, it will | |
428 ;; only ever contain French accented characters as keys. It's cleared | |
429 ;; on each entry to htmlize-buffer-1 to allow modifications of | |
430 ;; `htmlize-convert-nonascii-to-entities' to take effect. | |
431 (defvar htmlize-extended-character-cache (make-hash-table :test 'eq)) | |
432 | |
433 (defun htmlize-protect-string (string) | |
434 "HTML-protect string, escaping HTML metacharacters and I18N chars." | |
435 ;; Only protecting strings that actually contain unsafe or non-ASCII | |
436 ;; chars removes a lot of unnecessary funcalls and consing. | |
437 (if (not (string-match "[^\r\n\t -%'-;=?-~]" string)) | |
438 string | |
439 (mapconcat (lambda (char) | |
440 (cond | |
441 ((< char 128) | |
442 ;; ASCII: use htmlize-basic-character-table. | |
443 (aref htmlize-basic-character-table char)) | |
444 ((gethash char htmlize-extended-character-cache) | |
445 ;; We've already seen this char; return the cached | |
446 ;; string. | |
447 ) | |
448 ((not htmlize-convert-nonascii-to-entities) | |
449 ;; If conversion to entities is not desired, always | |
450 ;; copy the char literally. | |
451 (setf (gethash char htmlize-extended-character-cache) | |
452 (char-to-string char))) | |
453 ((< char 256) | |
454 ;; Latin 1: no need to call encode-char. | |
455 (setf (gethash char htmlize-extended-character-cache) | |
456 (format "&#%d;" char))) | |
457 ((encode-char char 'ucs) | |
458 ;; Must check if encode-char works for CHAR; | |
459 ;; it fails for Arabic and possibly elsewhere. | |
460 (setf (gethash char htmlize-extended-character-cache) | |
461 (format "&#%d;" (encode-char char 'ucs)))) | |
462 (t | |
463 ;; encode-char doesn't work for this char. Copy it | |
464 ;; unchanged and hope for the best. | |
465 (setf (gethash char htmlize-extended-character-cache) | |
466 (char-to-string char))))) | |
467 string ""))) | |
468 | |
469 (defun htmlize-attr-escape (string) | |
470 ;; Like htmlize-protect-string, but also escapes double-quoted | |
471 ;; strings to make it usable in attribute values. | |
472 (setq string (htmlize-protect-string string)) | |
473 (if (not (string-match "\"" string)) | |
474 string | |
475 (mapconcat (lambda (char) | |
476 (if (eql char ?\") | |
477 """ | |
478 (char-to-string char))) | |
479 string ""))) | |
480 | |
481 (defsubst htmlize-concat (list) | |
482 (if (and (consp list) (null (cdr list))) | |
483 ;; Don't create a new string in the common case where the list only | |
484 ;; consists of one element. | |
485 (car list) | |
486 (apply #'concat list))) | |
487 | |
488 (defun htmlize-format-link (linkprops text) | |
489 (let ((uri (if (stringp linkprops) | |
490 linkprops | |
491 (plist-get linkprops :uri))) | |
492 (escaped-text (htmlize-protect-string text))) | |
493 (if uri | |
494 (format "<a href=\"%s\">%s</a>" (htmlize-attr-escape uri) escaped-text) | |
495 escaped-text))) | |
496 | |
497 (defun htmlize-escape-or-link (string) | |
498 ;; Escape STRING and/or add hyperlinks. STRING comes from a | |
499 ;; `display' property. | |
500 (let ((pos 0) (end (length string)) outlist) | |
501 (while (< pos end) | |
502 (let* ((link (get-char-property pos 'htmlize-link string)) | |
503 (next-link-change (next-single-property-change | |
504 pos 'htmlize-link string end)) | |
505 (chunk (substring string pos next-link-change))) | |
506 (push | |
507 (cond (link | |
508 (htmlize-format-link link chunk)) | |
509 ((get-char-property 0 'htmlize-literal chunk) | |
510 chunk) | |
511 (t | |
512 (htmlize-protect-string chunk))) | |
513 outlist) | |
514 (setq pos next-link-change))) | |
515 (htmlize-concat (nreverse outlist)))) | |
516 | |
517 (defun htmlize-display-prop-to-html (display text) | |
518 (let (desc) | |
519 (cond ((stringp display) | |
520 ;; Emacs ignores recursive display properties. | |
521 (htmlize-escape-or-link display)) | |
522 ((not (eq (car-safe display) 'image)) | |
523 (htmlize-protect-string text)) | |
524 ((null (setq desc (funcall htmlize-transform-image | |
525 (cdr display) text))) | |
526 (htmlize-escape-or-link text)) | |
527 ((stringp desc) | |
528 (htmlize-escape-or-link desc)) | |
529 (t | |
530 (htmlize-generate-image desc text))))) | |
531 | |
532 (defun htmlize-string-to-html (string) | |
533 ;; Convert the string to HTML, including images attached as | |
534 ;; `display' property and links as `htmlize-link' property. In a | |
535 ;; string without images or links, this is equivalent to | |
536 ;; `htmlize-protect-string'. | |
537 (let ((pos 0) (end (length string)) outlist) | |
538 (while (< pos end) | |
539 (let* ((display (get-char-property pos 'display string)) | |
540 (next-display-change (next-single-property-change | |
541 pos 'display string end)) | |
542 (chunk (substring string pos next-display-change))) | |
543 (push | |
544 (if display | |
545 (htmlize-display-prop-to-html display chunk) | |
546 (htmlize-escape-or-link chunk)) | |
547 outlist) | |
548 (setq pos next-display-change))) | |
549 (htmlize-concat (nreverse outlist)))) | |
550 | |
551 (defun htmlize-default-transform-image (imgprops _text) | |
552 "Default transformation of image descriptor to something usable in HTML. | |
553 | |
554 If `htmlize-use-images' is nil, the function always returns nil, meaning | |
555 use original text. Otherwise, it tries to find the image for images that | |
556 specify a file name. If `htmlize-force-inline-images' is non-nil, it also | |
557 converts the :file attribute to :data and returns the modified property | |
558 list." | |
559 (when htmlize-use-images | |
560 (when (plist-get imgprops :file) | |
561 (let ((location (plist-get (cdr (find-image (list imgprops))) :file))) | |
562 (when location | |
563 (setq imgprops (plist-put (copy-list imgprops) :file location))))) | |
564 (if htmlize-force-inline-images | |
565 (let ((location (plist-get imgprops :file)) | |
566 data) | |
567 (when location | |
568 (with-temp-buffer | |
569 (condition-case nil | |
570 (progn | |
571 (insert-file-contents-literally location) | |
572 (setq data (buffer-string))) | |
573 (error nil)))) | |
574 ;; if successful, return the new plist, otherwise return | |
575 ;; nil, which will use the original text | |
576 (and data | |
577 (plist-put (plist-put imgprops :file nil) | |
578 :data data))) | |
579 imgprops))) | |
580 | |
581 (defun htmlize-alt-text (_imgprops origtext) | |
582 (and (/= (length origtext) 0) | |
583 (<= (length origtext) htmlize-max-alt-text) | |
584 (not (string-match "[\0-\x1f]" origtext)) | |
585 origtext)) | |
586 | |
587 (defun htmlize-generate-image (imgprops origtext) | |
588 (let* ((alt-text (htmlize-alt-text imgprops origtext)) | |
589 (alt-attr (if alt-text | |
590 (format " alt=\"%s\"" (htmlize-attr-escape alt-text)) | |
591 ""))) | |
592 (cond ((plist-get imgprops :file) | |
593 ;; Try to find the image in image-load-path | |
594 (let* ((found-props (cdr (find-image (list imgprops)))) | |
595 (file (or (plist-get found-props :file) | |
596 (plist-get imgprops :file)))) | |
597 (format "<img src=\"%s\"%s />" | |
598 (htmlize-attr-escape (file-relative-name file)) | |
599 alt-attr))) | |
600 ((plist-get imgprops :data) | |
601 (format "<img src=\"data:image/%s;base64,%s\"%s />" | |
602 (or (plist-get imgprops :type) "") | |
603 (base64-encode-string (plist-get imgprops :data)) | |
604 alt-attr))))) | |
605 | |
606 (defconst htmlize-ellipsis "...") | |
607 (put-text-property 0 (length htmlize-ellipsis) 'htmlize-ellipsis t htmlize-ellipsis) | |
608 | |
609 (defun htmlize-match-inv-spec (inv) | |
610 (member* inv buffer-invisibility-spec | |
611 :key (lambda (i) | |
612 (if (symbolp i) i (car i))))) | |
613 | |
614 (defun htmlize-decode-invisibility-spec (invisible) | |
615 ;; Return t, nil, or `ellipsis', depending on how invisible text should be inserted. | |
616 | |
617 (if (not (listp buffer-invisibility-spec)) | |
618 ;; If buffer-invisibility-spec is not a list, then all | |
619 ;; characters with non-nil `invisible' property are visible. | |
620 (not invisible) | |
621 | |
622 ;; Otherwise, the value of a non-nil `invisible' property can be: | |
623 ;; 1. a symbol -- make the text invisible if it matches | |
624 ;; buffer-invisibility-spec. | |
625 ;; 2. a list of symbols -- make the text invisible if | |
626 ;; any symbol in the list matches | |
627 ;; buffer-invisibility-spec. | |
628 ;; If the match of buffer-invisibility-spec has a non-nil | |
629 ;; CDR, replace the invisible text with an ellipsis. | |
630 (let ((match (if (symbolp invisible) | |
631 (htmlize-match-inv-spec invisible) | |
632 (some #'htmlize-match-inv-spec invisible)))) | |
633 (cond ((null match) t) | |
634 ((cdr-safe (car match)) 'ellipsis) | |
635 (t nil))))) | |
636 | |
637 (defun htmlize-add-before-after-strings (beg end text) | |
638 ;; Find overlays specifying before-string and after-string in [beg, | |
639 ;; pos). If any are found, splice them into TEXT and return the new | |
640 ;; text. | |
641 (let (additions) | |
642 (dolist (overlay (overlays-in beg end)) | |
643 (let ((before (overlay-get overlay 'before-string)) | |
644 (after (overlay-get overlay 'after-string))) | |
645 (when after | |
646 (push (cons (- (overlay-end overlay) beg) | |
647 after) | |
648 additions)) | |
649 (when before | |
650 (push (cons (- (overlay-start overlay) beg) | |
651 before) | |
652 additions)))) | |
653 (if additions | |
654 (let ((textlist nil) | |
655 (strpos 0)) | |
656 (dolist (add (stable-sort additions #'< :key #'car)) | |
657 (let ((addpos (car add)) | |
658 (addtext (cdr add))) | |
659 (push (substring text strpos addpos) textlist) | |
660 (push addtext textlist) | |
661 (setq strpos addpos))) | |
662 (push (substring text strpos) textlist) | |
663 (apply #'concat (nreverse textlist))) | |
664 text))) | |
665 | |
666 (defun htmlize-copy-prop (prop beg end string) | |
667 ;; Copy the specified property from the specified region of the | |
668 ;; buffer to the target string. We cannot rely on Emacs to copy the | |
669 ;; property because we want to handle properties coming from both | |
670 ;; text properties and overlays. | |
671 (let ((pos beg)) | |
672 (while (< pos end) | |
673 (let ((value (get-char-property pos prop)) | |
674 (next-change (htmlize-next-change pos prop end))) | |
675 (when value | |
676 (put-text-property (- pos beg) (- next-change beg) | |
677 prop value string)) | |
678 (setq pos next-change))))) | |
679 | |
680 (defun htmlize-get-text-with-display (beg end) | |
681 ;; Like buffer-substring-no-properties, except it copies the | |
682 ;; `display' property from the buffer, if found. | |
683 (let ((text (buffer-substring-no-properties beg end))) | |
684 (htmlize-copy-prop 'display beg end text) | |
685 (htmlize-copy-prop 'htmlize-link beg end text) | |
686 (unless htmlize-running-xemacs | |
687 (setq text (htmlize-add-before-after-strings beg end text))) | |
688 text)) | |
689 | |
690 (defun htmlize-buffer-substring-no-invisible (beg end) | |
691 ;; Like buffer-substring-no-properties, but don't copy invisible | |
692 ;; parts of the region. Where buffer-substring-no-properties | |
693 ;; mandates an ellipsis to be shown, htmlize-ellipsis is inserted. | |
694 (let ((pos beg) | |
695 visible-list invisible show last-show next-change) | |
696 ;; Iterate over the changes in the `invisible' property and filter | |
697 ;; out the portions where it's non-nil, i.e. where the text is | |
698 ;; invisible. | |
699 (while (< pos end) | |
700 (setq invisible (get-char-property pos 'invisible) | |
701 next-change (htmlize-next-change pos 'invisible end) | |
702 show (htmlize-decode-invisibility-spec invisible)) | |
703 (cond ((eq show t) | |
704 (push (htmlize-get-text-with-display pos next-change) | |
705 visible-list)) | |
706 ((and (eq show 'ellipsis) | |
707 (not (eq last-show 'ellipsis)) | |
708 ;; Conflate successive ellipses. | |
709 (push htmlize-ellipsis visible-list)))) | |
710 (setq pos next-change last-show show)) | |
711 (htmlize-concat (nreverse visible-list)))) | |
712 | |
713 (defun htmlize-trim-ellipsis (text) | |
714 ;; Remove htmlize-ellipses ("...") from the beginning of TEXT if it | |
715 ;; starts with it. It checks for the special property of the | |
716 ;; ellipsis so it doesn't work on ordinary text that begins with | |
717 ;; "...". | |
718 (if (get-text-property 0 'htmlize-ellipsis text) | |
719 (substring text (length htmlize-ellipsis)) | |
720 text)) | |
721 | |
722 (defconst htmlize-tab-spaces | |
723 ;; A table of strings with spaces. (aref htmlize-tab-spaces 5) is | |
724 ;; like (make-string 5 ?\ ), except it doesn't cons. | |
725 (let ((v (make-vector 32 nil))) | |
726 (dotimes (i (length v)) | |
727 (setf (aref v i) (make-string i ?\ ))) | |
728 v)) | |
729 | |
730 (defun htmlize-untabify (text start-column) | |
731 "Untabify TEXT, assuming it starts at START-COLUMN." | |
732 (let ((column start-column) | |
733 (last-match 0) | |
734 (chunk-start 0) | |
735 chunks match-pos tab-size) | |
736 (while (string-match "[\t\n]" text last-match) | |
737 (setq match-pos (match-beginning 0)) | |
738 (cond ((eq (aref text match-pos) ?\t) | |
739 ;; Encountered a tab: create a chunk of text followed by | |
740 ;; the expanded tab. | |
741 (push (substring text chunk-start match-pos) chunks) | |
742 ;; Increase COLUMN by the length of the text we've | |
743 ;; skipped since last tab or newline. (Encountering | |
744 ;; newline resets it.) | |
745 (incf column (- match-pos last-match)) | |
746 ;; Calculate tab size based on tab-width and COLUMN. | |
747 (setq tab-size (- tab-width (% column tab-width))) | |
748 ;; Expand the tab, carefully recreating the `display' | |
749 ;; property if one was on the TAB. | |
750 (let ((display (get-text-property match-pos 'display text)) | |
751 (expanded-tab (aref htmlize-tab-spaces tab-size))) | |
752 (when display | |
753 (put-text-property 0 tab-size 'display display expanded-tab)) | |
754 (push expanded-tab chunks)) | |
755 (incf column tab-size) | |
756 (setq chunk-start (1+ match-pos))) | |
757 (t | |
758 ;; Reset COLUMN at beginning of line. | |
759 (setq column 0))) | |
760 (setq last-match (1+ match-pos))) | |
761 ;; If no chunks have been allocated, it means there have been no | |
762 ;; tabs to expand. Return TEXT unmodified. | |
763 (if (null chunks) | |
764 text | |
765 (when (< chunk-start (length text)) | |
766 ;; Push the remaining chunk. | |
767 (push (substring text chunk-start) chunks)) | |
768 ;; Generate the output from the available chunks. | |
769 (htmlize-concat (nreverse chunks))))) | |
770 | |
771 (defun htmlize-extract-text (beg end trailing-ellipsis) | |
772 ;; Extract buffer text, sans the invisible parts. Then | |
773 ;; untabify it and escape the HTML metacharacters. | |
774 (let ((text (htmlize-buffer-substring-no-invisible beg end))) | |
775 (when trailing-ellipsis | |
776 (setq text (htmlize-trim-ellipsis text))) | |
777 ;; If TEXT ends up empty, don't change trailing-ellipsis. | |
778 (when (> (length text) 0) | |
779 (setq trailing-ellipsis | |
780 (get-text-property (1- (length text)) | |
781 'htmlize-ellipsis text))) | |
782 (setq text (htmlize-untabify text (current-column))) | |
783 (setq text (htmlize-string-to-html text)) | |
784 (values text trailing-ellipsis))) | |
785 | |
786 (defun htmlize-despam-address (string) | |
787 "Replace every occurrence of '@' in STRING with %40. | |
788 This is used to protect mailto links without modifying their meaning." | |
789 ;; Suggested by Ville Skytta. | |
790 (while (string-match "@" string) | |
791 (setq string (replace-match "%40" nil t string))) | |
792 string) | |
793 | |
794 (defun htmlize-make-tmp-overlay (beg end props) | |
795 (let ((overlay (htmlize-make-overlay beg end))) | |
796 (htmlize-overlay-put overlay 'htmlize-tmp-overlay t) | |
797 (while props | |
798 (htmlize-overlay-put overlay (pop props) (pop props))) | |
799 overlay)) | |
800 | |
801 (defun htmlize-delete-tmp-overlays () | |
802 (dolist (overlay (htmlize-overlays-in (point-min) (point-max))) | |
803 (when (htmlize-overlay-get overlay 'htmlize-tmp-overlay) | |
804 (htmlize-delete-overlay overlay)))) | |
805 | |
806 (defun htmlize-make-link-overlay (beg end uri) | |
807 (htmlize-make-tmp-overlay beg end `(htmlize-link (:uri ,uri)))) | |
808 | |
809 (defun htmlize-create-auto-links () | |
810 "Add `htmlize-link' property to all mailto links in the buffer." | |
811 (save-excursion | |
812 (goto-char (point-min)) | |
813 (while (re-search-forward | |
814 "<\\(\\(mailto:\\)?\\([-=+_.a-zA-Z0-9]+@[-_.a-zA-Z0-9]+\\)\\)>" | |
815 nil t) | |
816 (let* ((address (match-string 3)) | |
817 (beg (match-beginning 0)) (end (match-end 0)) | |
818 (uri (concat "mailto:" (htmlize-despam-address address)))) | |
819 (htmlize-make-link-overlay beg end uri))) | |
820 (goto-char (point-min)) | |
821 (while (re-search-forward "<\\(\\(URL:\\)?\\([a-zA-Z]+://[^;]+\\)\\)>" | |
822 nil t) | |
823 (htmlize-make-link-overlay | |
824 (match-beginning 0) (match-end 0) (match-string 3))))) | |
825 | |
826 ;; Tests for htmlize-create-auto-links: | |
827 | |
828 ;; <mailto:hniksic@xemacs.org> | |
829 ;; <http://fly.srk.fer.hr> | |
830 ;; <URL:http://www.xemacs.org> | |
831 ;; <http://www.mail-archive.com/bbdb-info@xemacs.org/> | |
832 ;; <hniksic@xemacs.org> | |
833 ;; <xalan-dev-sc.10148567319.hacuhiucknfgmpfnjcpg-john=doe.com@xml.apache.org> | |
834 | |
835 (defun htmlize-shadow-form-feeds () | |
836 (let ((s "\n<hr />")) | |
837 (put-text-property 0 (length s) 'htmlize-literal t s) | |
838 (let ((disp `(display ,s))) | |
839 (while (re-search-forward "\n\^L" nil t) | |
840 (htmlize-make-tmp-overlay (match-beginning 0) (match-end 0) disp))))) | |
841 | |
842 (defun htmlize-defang-local-variables () | |
843 ;; Juri Linkov reports that an HTML-ized "Local variables" can lead | |
844 ;; visiting the HTML to fail with "Local variables list is not | |
845 ;; properly terminated". He suggested changing the phrase to | |
846 ;; syntactically equivalent HTML that Emacs doesn't recognize. | |
847 (goto-char (point-min)) | |
848 (while (search-forward "Local Variables:" nil t) | |
849 (replace-match "Local Variables:" nil t))) | |
850 | |
851 | |
852 ;;; Color handling. | |
853 | |
854 (defvar htmlize-x-library-search-path | |
855 `(,data-directory | |
856 "/etc/X11/rgb.txt" | |
857 "/usr/share/X11/rgb.txt" | |
858 ;; the remainder of this list really belongs in a museum | |
859 "/usr/X11R6/lib/X11/" | |
860 "/usr/X11R5/lib/X11/" | |
861 "/usr/lib/X11R6/X11/" | |
862 "/usr/lib/X11R5/X11/" | |
863 "/usr/local/X11R6/lib/X11/" | |
864 "/usr/local/X11R5/lib/X11/" | |
865 "/usr/local/lib/X11R6/X11/" | |
866 "/usr/local/lib/X11R5/X11/" | |
867 "/usr/X11/lib/X11/" | |
868 "/usr/lib/X11/" | |
869 "/usr/local/lib/X11/" | |
870 "/usr/X386/lib/X11/" | |
871 "/usr/x386/lib/X11/" | |
872 "/usr/XFree86/lib/X11/" | |
873 "/usr/unsupported/lib/X11/" | |
874 "/usr/athena/lib/X11/" | |
875 "/usr/local/x11r5/lib/X11/" | |
876 "/usr/lpp/Xamples/lib/X11/" | |
877 "/usr/openwin/lib/X11/" | |
878 "/usr/openwin/share/lib/X11/")) | |
879 | |
880 (defun htmlize-get-color-rgb-hash (&optional rgb-file) | |
881 "Return a hash table mapping X color names to RGB values. | |
882 The keys in the hash table are X11 color names, and the values are the | |
883 #rrggbb RGB specifications, extracted from `rgb.txt'. | |
884 | |
885 If RGB-FILE is nil, the function will try hard to find a suitable file | |
886 in the system directories. | |
887 | |
888 If no rgb.txt file is found, return nil." | |
889 (let ((rgb-file (or rgb-file (locate-file | |
890 "rgb.txt" | |
891 htmlize-x-library-search-path))) | |
892 (hash nil)) | |
893 (when rgb-file | |
894 (with-temp-buffer | |
895 (insert-file-contents rgb-file) | |
896 (setq hash (make-hash-table :test 'equal)) | |
897 (while (not (eobp)) | |
898 (cond ((looking-at "^\\s-*\\([!#]\\|$\\)") | |
899 ;; Skip comments and empty lines. | |
900 ) | |
901 ((looking-at | |
902 "[ \t]*\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\(.*\\)") | |
903 (setf (gethash (downcase (match-string 4)) hash) | |
904 (format "#%02x%02x%02x" | |
905 (string-to-number (match-string 1)) | |
906 (string-to-number (match-string 2)) | |
907 (string-to-number (match-string 3))))) | |
908 (t | |
909 (error | |
910 "Unrecognized line in %s: %s" | |
911 rgb-file | |
912 (buffer-substring (point) (progn (end-of-line) (point)))))) | |
913 (forward-line 1)))) | |
914 hash)) | |
915 | |
916 ;; Compile the RGB map when loaded. On systems where rgb.txt is | |
917 ;; missing, the value of the variable will be nil, and rgb.txt will | |
918 ;; not be used. | |
919 (defvar htmlize-color-rgb-hash (htmlize-get-color-rgb-hash)) | |
920 | |
921 ;;; Face handling. | |
922 | |
923 (defun htmlize-face-specifies-property (face prop) | |
924 ;; Return t if face specifies PROP, as opposed to it being inherited | |
925 ;; from the default face. The problem with e.g. | |
926 ;; `face-foreground-instance' is that it returns an instance for | |
927 ;; EVERY face because every face inherits from the default face. | |
928 ;; However, we'd like htmlize-face-{fore,back}ground to return nil | |
929 ;; when called with a face that doesn't specify its own foreground | |
930 ;; or background. | |
931 (or (eq face 'default) | |
932 (assq 'global (specifier-spec-list (face-property face prop))))) | |
933 | |
934 (defun htmlize-face-color-internal (face fg) | |
935 ;; Used only under GNU Emacs. Return the color of FACE, but don't | |
936 ;; return "unspecified-fg" or "unspecified-bg". If the face is | |
937 ;; `default' and the color is unspecified, look up the color in | |
938 ;; frame parameters. | |
939 (let* ((function (if fg #'face-foreground #'face-background)) | |
940 (color (funcall function face nil t))) | |
941 (when (and (eq face 'default) (null color)) | |
942 (setq color (cdr (assq (if fg 'foreground-color 'background-color) | |
943 (frame-parameters))))) | |
944 (when (or (eq color 'unspecified) | |
945 (equal color "unspecified-fg") | |
946 (equal color "unspecified-bg")) | |
947 (setq color nil)) | |
948 (when (and (eq face 'default) | |
949 (null color)) | |
950 ;; Assuming black on white doesn't seem right, but I can't think | |
951 ;; of anything better to do. | |
952 (setq color (if fg "black" "white"))) | |
953 color)) | |
954 | |
955 (defun htmlize-face-foreground (face) | |
956 ;; Return the name of the foreground color of FACE. If FACE does | |
957 ;; not specify a foreground color, return nil. | |
958 (cond (htmlize-running-xemacs | |
959 ;; XEmacs. | |
960 (and (htmlize-face-specifies-property face 'foreground) | |
961 (color-instance-name (face-foreground-instance face)))) | |
962 (t | |
963 ;; GNU Emacs. | |
964 (htmlize-face-color-internal face t)))) | |
965 | |
966 (defun htmlize-face-background (face) | |
967 ;; Return the name of the background color of FACE. If FACE does | |
968 ;; not specify a background color, return nil. | |
969 (cond (htmlize-running-xemacs | |
970 ;; XEmacs. | |
971 (and (htmlize-face-specifies-property face 'background) | |
972 (color-instance-name (face-background-instance face)))) | |
973 (t | |
974 ;; GNU Emacs. | |
975 (htmlize-face-color-internal face nil)))) | |
976 | |
977 ;; Convert COLOR to the #RRGGBB string. If COLOR is already in that | |
978 ;; format, it's left unchanged. | |
979 | |
980 (defun htmlize-color-to-rgb (color) | |
981 (let ((rgb-string nil)) | |
982 (cond ((null color) | |
983 ;; Ignore nil COLOR because it means that the face is not | |
984 ;; specifying any color. Hence (htmlize-color-to-rgb nil) | |
985 ;; returns nil. | |
986 ) | |
987 ((string-match "\\`#" color) | |
988 ;; The color is already in #rrggbb format. | |
989 (setq rgb-string color)) | |
990 ((and htmlize-use-rgb-txt | |
991 htmlize-color-rgb-hash) | |
992 ;; Use of rgb.txt is requested, and it's available on the | |
993 ;; system. Use it. | |
994 (setq rgb-string (gethash (downcase color) htmlize-color-rgb-hash))) | |
995 (t | |
996 ;; We're getting the RGB components from Emacs. | |
997 (let ((rgb | |
998 (if (fboundp 'color-instance-rgb-components) | |
999 (mapcar (lambda (arg) | |
1000 (/ arg 256)) | |
1001 (color-instance-rgb-components | |
1002 (make-color-instance color))) | |
1003 (mapcar (lambda (arg) | |
1004 (/ arg 256)) | |
1005 (color-values color))))) | |
1006 (when rgb | |
1007 (setq rgb-string (apply #'format "#%02x%02x%02x" rgb)))))) | |
1008 ;; If RGB-STRING is still nil, it means the color cannot be found, | |
1009 ;; for whatever reason. In that case just punt and return COLOR. | |
1010 ;; Most browsers support a decent set of color names anyway. | |
1011 (or rgb-string color))) | |
1012 | |
1013 ;; We store the face properties we care about into an | |
1014 ;; `htmlize-fstruct' type. That way we only have to analyze face | |
1015 ;; properties, which can be time consuming, once per each face. The | |
1016 ;; mapping between Emacs faces and htmlize-fstructs is established by | |
1017 ;; htmlize-make-face-map. The name "fstruct" refers to variables of | |
1018 ;; type `htmlize-fstruct', while the term "face" is reserved for Emacs | |
1019 ;; faces. | |
1020 | |
1021 (defstruct htmlize-fstruct | |
1022 foreground ; foreground color, #rrggbb | |
1023 background ; background color, #rrggbb | |
1024 size ; size | |
1025 boldp ; whether face is bold | |
1026 italicp ; whether face is italic | |
1027 underlinep ; whether face is underlined | |
1028 overlinep ; whether face is overlined | |
1029 strikep ; whether face is struck through | |
1030 css-name ; CSS name of face | |
1031 ) | |
1032 | |
1033 (defun htmlize-face-set-from-keyword-attr (fstruct attr value) | |
1034 ;; For ATTR and VALUE, set the equivalent value in FSTRUCT. | |
1035 (case attr | |
1036 (:foreground | |
1037 (setf (htmlize-fstruct-foreground fstruct) (htmlize-color-to-rgb value))) | |
1038 (:background | |
1039 (setf (htmlize-fstruct-background fstruct) (htmlize-color-to-rgb value))) | |
1040 (:height | |
1041 (setf (htmlize-fstruct-size fstruct) value)) | |
1042 (:weight | |
1043 (when (string-match (symbol-name value) "bold") | |
1044 (setf (htmlize-fstruct-boldp fstruct) t))) | |
1045 (:slant | |
1046 (setf (htmlize-fstruct-italicp fstruct) (or (eq value 'italic) | |
1047 (eq value 'oblique)))) | |
1048 (:bold | |
1049 (setf (htmlize-fstruct-boldp fstruct) value)) | |
1050 (:italic | |
1051 (setf (htmlize-fstruct-italicp fstruct) value)) | |
1052 (:underline | |
1053 (setf (htmlize-fstruct-underlinep fstruct) value)) | |
1054 (:overline | |
1055 (setf (htmlize-fstruct-overlinep fstruct) value)) | |
1056 (:strike-through | |
1057 (setf (htmlize-fstruct-strikep fstruct) value)))) | |
1058 | |
1059 (defun htmlize-face-size (face) | |
1060 ;; The size (height) of FACE, taking inheritance into account. | |
1061 ;; Only works in Emacs 21 and later. | |
1062 (let* ((face-list (list face)) | |
1063 (head face-list) | |
1064 (tail face-list)) | |
1065 (while head | |
1066 (let ((inherit (face-attribute (car head) :inherit))) | |
1067 (cond ((listp inherit) | |
1068 (setcdr tail (copy-list inherit)) | |
1069 (setq tail (last tail))) | |
1070 ((eq inherit 'unspecified)) | |
1071 (t | |
1072 (setcdr tail (list inherit)) | |
1073 (setq tail (cdr tail))))) | |
1074 (pop head)) | |
1075 (let ((size-list | |
1076 (loop | |
1077 for f in face-list | |
1078 for h = (face-attribute f :height) | |
1079 collect (if (eq h 'unspecified) nil h)))) | |
1080 (reduce 'htmlize-merge-size (cons nil size-list))))) | |
1081 | |
1082 (defun htmlize-face-css-name (face) | |
1083 ;; Generate the css-name property for the given face. Emacs places | |
1084 ;; no restrictions on the names of symbols that represent faces -- | |
1085 ;; any characters may be in the name, even control chars. We try | |
1086 ;; hard to beat the face name into shape, both esthetically and | |
1087 ;; according to CSS1 specs. | |
1088 (let ((name (downcase (symbol-name face)))) | |
1089 (when (string-match "\\`font-lock-" name) | |
1090 ;; font-lock-FOO-face -> FOO. | |
1091 (setq name (replace-match "" t t name))) | |
1092 (when (string-match "-face\\'" name) | |
1093 ;; Drop the redundant "-face" suffix. | |
1094 (setq name (replace-match "" t t name))) | |
1095 (while (string-match "[^-a-zA-Z0-9]" name) | |
1096 ;; Drop the non-alphanumerics. | |
1097 (setq name (replace-match "X" t t name))) | |
1098 (when (string-match "\\`[-0-9]" name) | |
1099 ;; CSS identifiers may not start with a digit. | |
1100 (setq name (concat "X" name))) | |
1101 ;; After these transformations, the face could come out empty. | |
1102 (when (equal name "") | |
1103 (setq name "face")) | |
1104 ;; Apply the prefix. | |
1105 (concat htmlize-css-name-prefix name))) | |
1106 | |
1107 (defun htmlize-face-to-fstruct (face) | |
1108 "Convert Emacs face FACE to fstruct." | |
1109 (let ((fstruct (make-htmlize-fstruct | |
1110 :foreground (htmlize-color-to-rgb | |
1111 (htmlize-face-foreground face)) | |
1112 :background (htmlize-color-to-rgb | |
1113 (htmlize-face-background face))))) | |
1114 (if htmlize-running-xemacs | |
1115 ;; XEmacs doesn't provide a way to detect whether a face is | |
1116 ;; bold or italic, so we need to examine the font instance. | |
1117 (let* ((font-instance (face-font-instance face)) | |
1118 (props (font-instance-properties font-instance))) | |
1119 (when (equalp (cdr (assq 'WEIGHT_NAME props)) "bold") | |
1120 (setf (htmlize-fstruct-boldp fstruct) t)) | |
1121 (when (or (equalp (cdr (assq 'SLANT props)) "i") | |
1122 (equalp (cdr (assq 'SLANT props)) "o")) | |
1123 (setf (htmlize-fstruct-italicp fstruct) t)) | |
1124 (setf (htmlize-fstruct-strikep fstruct) | |
1125 (face-strikethru-p face)) | |
1126 (setf (htmlize-fstruct-underlinep fstruct) | |
1127 (face-underline-p face))) | |
1128 ;; GNU Emacs | |
1129 (dolist (attr '(:weight :slant :underline :overline :strike-through)) | |
1130 (let ((value (face-attribute face attr nil t))) | |
1131 (when (and value (not (eq value 'unspecified))) | |
1132 (htmlize-face-set-from-keyword-attr fstruct attr value)))) | |
1133 (let ((size (htmlize-face-size face))) | |
1134 (unless (eql size 1.0) ; ignore non-spec | |
1135 (setf (htmlize-fstruct-size fstruct) size)))) | |
1136 (setf (htmlize-fstruct-css-name fstruct) (htmlize-face-css-name face)) | |
1137 fstruct)) | |
1138 | |
1139 (defmacro htmlize-copy-attr-if-set (attr-list dest source) | |
1140 ;; Generate code with the following pattern: | |
1141 ;; (progn | |
1142 ;; (when (htmlize-fstruct-ATTR source) | |
1143 ;; (setf (htmlize-fstruct-ATTR dest) (htmlize-fstruct-ATTR source))) | |
1144 ;; ...) | |
1145 ;; for the given list of boolean attributes. | |
1146 (cons 'progn | |
1147 (loop for attr in attr-list | |
1148 for attr-sym = (intern (format "htmlize-fstruct-%s" attr)) | |
1149 collect `(when (,attr-sym ,source) | |
1150 (setf (,attr-sym ,dest) (,attr-sym ,source)))))) | |
1151 | |
1152 (defun htmlize-merge-size (merged next) | |
1153 ;; Calculate the size of the merge of MERGED and NEXT. | |
1154 (cond ((null merged) next) | |
1155 ((integerp next) next) | |
1156 ((null next) merged) | |
1157 ((floatp merged) (* merged next)) | |
1158 ((integerp merged) (round (* merged next))))) | |
1159 | |
1160 (defun htmlize-merge-two-faces (merged next) | |
1161 (htmlize-copy-attr-if-set | |
1162 (foreground background boldp italicp underlinep overlinep strikep) | |
1163 merged next) | |
1164 (setf (htmlize-fstruct-size merged) | |
1165 (htmlize-merge-size (htmlize-fstruct-size merged) | |
1166 (htmlize-fstruct-size next))) | |
1167 merged) | |
1168 | |
1169 (defun htmlize-merge-faces (fstruct-list) | |
1170 (cond ((null fstruct-list) | |
1171 ;; Nothing to do, return a dummy face. | |
1172 (make-htmlize-fstruct)) | |
1173 ((null (cdr fstruct-list)) | |
1174 ;; Optimize for the common case of a single face, simply | |
1175 ;; return it. | |
1176 (car fstruct-list)) | |
1177 (t | |
1178 (reduce #'htmlize-merge-two-faces | |
1179 (cons (make-htmlize-fstruct) fstruct-list))))) | |
1180 | |
1181 ;; GNU Emacs 20+ supports attribute lists in `face' properties. For | |
1182 ;; example, you can use `(:foreground "red" :weight bold)' as an | |
1183 ;; overlay's "face", or you can even use a list of such lists, etc. | |
1184 ;; We call those "attrlists". | |
1185 ;; | |
1186 ;; htmlize supports attrlist by converting them to fstructs, the same | |
1187 ;; as with regular faces. | |
1188 | |
1189 (defun htmlize-attrlist-to-fstruct (attrlist) | |
1190 ;; Like htmlize-face-to-fstruct, but accepts an ATTRLIST as input. | |
1191 (let ((fstruct (make-htmlize-fstruct))) | |
1192 (cond ((eq (car attrlist) 'foreground-color) | |
1193 ;; ATTRLIST is (foreground-color . COLOR) | |
1194 (setf (htmlize-fstruct-foreground fstruct) | |
1195 (htmlize-color-to-rgb (cdr attrlist)))) | |
1196 ((eq (car attrlist) 'background-color) | |
1197 ;; ATTRLIST is (background-color . COLOR) | |
1198 (setf (htmlize-fstruct-background fstruct) | |
1199 (htmlize-color-to-rgb (cdr attrlist)))) | |
1200 (t | |
1201 ;; ATTRLIST is a plist. | |
1202 (while attrlist | |
1203 (let ((attr (pop attrlist)) | |
1204 (value (pop attrlist))) | |
1205 (when (and value (not (eq value 'unspecified))) | |
1206 (htmlize-face-set-from-keyword-attr fstruct attr value)))))) | |
1207 (setf (htmlize-fstruct-css-name fstruct) "ATTRLIST") | |
1208 fstruct)) | |
1209 | |
1210 (defun htmlize-decode-face-prop (prop) | |
1211 "Turn face property PROP into a list of face-like objects." | |
1212 ;; PROP can be a symbol naming a face, a string naming such a | |
1213 ;; symbol, a cons (foreground-color . COLOR) or (background-color | |
1214 ;; COLOR), a property list (:attr1 val1 :attr2 val2 ...), or a list | |
1215 ;; of any of those. | |
1216 ;; | |
1217 ;; (htmlize-decode-face-prop 'face) -> (face) | |
1218 ;; (htmlize-decode-face-prop '(face1 face2)) -> (face1 face2) | |
1219 ;; (htmlize-decode-face-prop '(:attr "val")) -> ((:attr "val")) | |
1220 ;; (htmlize-decode-face-prop '((:attr "val") face (foreground-color "red"))) | |
1221 ;; -> ((:attr "val") face (foreground-color "red")) | |
1222 ;; | |
1223 ;; Unrecognized atoms or non-face symbols/strings are silently | |
1224 ;; stripped away. | |
1225 (cond ((null prop) | |
1226 nil) | |
1227 ((symbolp prop) | |
1228 (and (facep prop) | |
1229 (list prop))) | |
1230 ((stringp prop) | |
1231 (and (facep (intern-soft prop)) | |
1232 (list prop))) | |
1233 ((atom prop) | |
1234 nil) | |
1235 ((and (symbolp (car prop)) | |
1236 (eq ?: (aref (symbol-name (car prop)) 0))) | |
1237 (list prop)) | |
1238 ((or (eq (car prop) 'foreground-color) | |
1239 (eq (car prop) 'background-color)) | |
1240 (list prop)) | |
1241 (t | |
1242 (apply #'nconc (mapcar #'htmlize-decode-face-prop prop))))) | |
1243 | |
1244 (defun htmlize-make-face-map (faces) | |
1245 ;; Return a hash table mapping Emacs faces to htmlize's fstructs. | |
1246 ;; The keys are either face symbols or attrlists, so the test | |
1247 ;; function must be `equal'. | |
1248 (let ((face-map (make-hash-table :test 'equal)) | |
1249 css-names) | |
1250 (dolist (face faces) | |
1251 (unless (gethash face face-map) | |
1252 ;; Haven't seen FACE yet; convert it to an fstruct and cache | |
1253 ;; it. | |
1254 (let ((fstruct (if (symbolp face) | |
1255 (htmlize-face-to-fstruct face) | |
1256 (htmlize-attrlist-to-fstruct face)))) | |
1257 (setf (gethash face face-map) fstruct) | |
1258 (let* ((css-name (htmlize-fstruct-css-name fstruct)) | |
1259 (new-name css-name) | |
1260 (i 0)) | |
1261 ;; Uniquify the face's css-name by using NAME-1, NAME-2, | |
1262 ;; etc. | |
1263 (while (member new-name css-names) | |
1264 (setq new-name (format "%s-%s" css-name (incf i)))) | |
1265 (unless (equal new-name css-name) | |
1266 (setf (htmlize-fstruct-css-name fstruct) new-name)) | |
1267 (push new-name css-names))))) | |
1268 face-map)) | |
1269 | |
1270 (defun htmlize-unstringify-face (face) | |
1271 "If FACE is a string, return it interned, otherwise return it unchanged." | |
1272 (if (stringp face) | |
1273 (intern face) | |
1274 face)) | |
1275 | |
1276 (defun htmlize-faces-in-buffer () | |
1277 "Return a list of faces used in the current buffer. | |
1278 Under XEmacs, this returns the set of faces specified by the extents | |
1279 with the `face' property. (This covers text properties as well.) Under | |
1280 GNU Emacs, it returns the set of faces specified by the `face' text | |
1281 property and by buffer overlays that specify `face'." | |
1282 (let (faces) | |
1283 ;; Testing for (fboundp 'map-extents) doesn't work because W3 | |
1284 ;; defines `map-extents' under FSF. | |
1285 (if htmlize-running-xemacs | |
1286 (let (face-prop) | |
1287 (map-extents (lambda (extent ignored) | |
1288 (setq face-prop (extent-face extent) | |
1289 ;; FACE-PROP can be a face or a list of | |
1290 ;; faces. | |
1291 faces (if (listp face-prop) | |
1292 (union face-prop faces) | |
1293 (adjoin face-prop faces))) | |
1294 nil) | |
1295 nil | |
1296 ;; Specify endpoints explicitly to respect | |
1297 ;; narrowing. | |
1298 (point-min) (point-max) nil nil 'face)) | |
1299 ;; FSF Emacs code. | |
1300 ;; Faces used by text properties. | |
1301 (let ((pos (point-min)) face-prop next) | |
1302 (while (< pos (point-max)) | |
1303 (setq face-prop (get-text-property pos 'face) | |
1304 next (or (next-single-property-change pos 'face) (point-max))) | |
1305 (setq faces (nunion (htmlize-decode-face-prop face-prop) | |
1306 faces :test 'equal)) | |
1307 (setq pos next))) | |
1308 ;; Faces used by overlays. | |
1309 (dolist (overlay (overlays-in (point-min) (point-max))) | |
1310 (let ((face-prop (overlay-get overlay 'face))) | |
1311 (setq faces (nunion (htmlize-decode-face-prop face-prop) | |
1312 faces :test 'equal))))) | |
1313 faces)) | |
1314 | |
1315 ;; htmlize-faces-at-point returns the faces in use at point. The | |
1316 ;; faces are sorted by increasing priority, i.e. the last face takes | |
1317 ;; precedence. | |
1318 ;; | |
1319 ;; Under XEmacs, this returns all the faces in all the extents at | |
1320 ;; point. Under GNU Emacs, this returns all the faces in the `face' | |
1321 ;; property and all the faces in the overlays at point. | |
1322 | |
1323 (cond (htmlize-running-xemacs | |
1324 (defun htmlize-faces-at-point () | |
1325 (let (extent extent-list face-list face-prop) | |
1326 (while (setq extent (extent-at (point) nil 'face extent)) | |
1327 (push extent extent-list)) | |
1328 ;; extent-list is in reverse display order, meaning that | |
1329 ;; smallest ones come last. That is the order we want, | |
1330 ;; except it can be overridden by the `priority' property. | |
1331 (setq extent-list (stable-sort extent-list #'< | |
1332 :key #'extent-priority)) | |
1333 (dolist (extent extent-list) | |
1334 (setq face-prop (extent-face extent)) | |
1335 ;; extent's face-list is in reverse order from what we | |
1336 ;; want, but the `nreverse' below will take care of it. | |
1337 (setq face-list (if (listp face-prop) | |
1338 (append face-prop face-list) | |
1339 (cons face-prop face-list)))) | |
1340 (nreverse face-list)))) | |
1341 (t | |
1342 (defun htmlize-faces-at-point () | |
1343 (let (all-faces) | |
1344 ;; Faces from text properties. | |
1345 (let ((face-prop (get-text-property (point) 'face))) | |
1346 ;; we need to reverse the `face' prop because we want | |
1347 ;; more specific faces to come later | |
1348 (setq all-faces (nreverse (htmlize-decode-face-prop face-prop)))) | |
1349 ;; Faces from overlays. | |
1350 (let ((overlays | |
1351 ;; Collect overlays at point that specify `face'. | |
1352 (delete-if-not (lambda (o) | |
1353 (overlay-get o 'face)) | |
1354 (overlays-at (point)))) | |
1355 list face-prop) | |
1356 ;; Sort the overlays so the smaller (more specific) ones | |
1357 ;; come later. The number of overlays at each one | |
1358 ;; position should be very small, so the sort shouldn't | |
1359 ;; slow things down. | |
1360 (setq overlays (sort* overlays | |
1361 ;; Sort by ascending... | |
1362 #'< | |
1363 ;; ...overlay size. | |
1364 :key (lambda (o) | |
1365 (- (overlay-end o) | |
1366 (overlay-start o))))) | |
1367 ;; Overlay priorities, if present, override the above | |
1368 ;; established order. Larger overlay priority takes | |
1369 ;; precedence and therefore comes later in the list. | |
1370 (setq overlays (stable-sort | |
1371 overlays | |
1372 ;; Reorder (stably) by acending... | |
1373 #'< | |
1374 ;; ...overlay priority. | |
1375 :key (lambda (o) | |
1376 (or (overlay-get o 'priority) 0)))) | |
1377 (dolist (overlay overlays) | |
1378 (setq face-prop (overlay-get overlay 'face) | |
1379 list (nconc (htmlize-decode-face-prop face-prop) list))) | |
1380 ;; Under "Merging Faces" the manual explicitly states | |
1381 ;; that faces specified by overlays take precedence over | |
1382 ;; faces specified by text properties. | |
1383 (setq all-faces (nconc all-faces list))) | |
1384 all-faces)))) | |
1385 | |
1386 ;; htmlize supports generating HTML in several flavors, some of which | |
1387 ;; use CSS, and others the <font> element. We take an OO approach and | |
1388 ;; define "methods" that indirect to the functions that depend on | |
1389 ;; `htmlize-output-type'. The currently used methods are `doctype', | |
1390 ;; `insert-head', `body-tag', and `text-markup'. Not all output types | |
1391 ;; define all methods. | |
1392 ;; | |
1393 ;; Methods are called either with (htmlize-method METHOD ARGS...) | |
1394 ;; special form, or by accessing the function with | |
1395 ;; (htmlize-method-function 'METHOD) and calling (funcall FUNCTION). | |
1396 ;; The latter form is useful in tight loops because `htmlize-method' | |
1397 ;; conses. | |
1398 | |
1399 (defmacro htmlize-method (method &rest args) | |
1400 ;; Expand to (htmlize-TYPE-METHOD ...ARGS...). TYPE is the value of | |
1401 ;; `htmlize-output-type' at run time. | |
1402 `(funcall (htmlize-method-function ',method) ,@args)) | |
1403 | |
1404 (defun htmlize-method-function (method) | |
1405 ;; Return METHOD's function definition for the current output type. | |
1406 ;; The returned object can be safely funcalled. | |
1407 (let ((sym (intern (format "htmlize-%s-%s" htmlize-output-type method)))) | |
1408 (indirect-function (if (fboundp sym) | |
1409 sym | |
1410 (let ((default (intern (concat "htmlize-default-" | |
1411 (symbol-name method))))) | |
1412 (if (fboundp default) | |
1413 default | |
1414 'ignore)))))) | |
1415 | |
1416 (defvar htmlize-memoization-table (make-hash-table :test 'equal)) | |
1417 | |
1418 (defmacro htmlize-memoize (key generator) | |
1419 "Return the value of GENERATOR, memoized as KEY. | |
1420 That means that GENERATOR will be evaluated and returned the first time | |
1421 it's called with the same value of KEY. All other times, the cached | |
1422 \(memoized) value will be returned." | |
1423 (let ((value (gensym))) | |
1424 `(let ((,value (gethash ,key htmlize-memoization-table))) | |
1425 (unless ,value | |
1426 (setq ,value ,generator) | |
1427 (setf (gethash ,key htmlize-memoization-table) ,value)) | |
1428 ,value))) | |
1429 | |
1430 ;;; Default methods. | |
1431 | |
1432 (defun htmlize-default-doctype () | |
1433 nil ; no doc-string | |
1434 ;; Note that the `font' output is technically invalid under this DTD | |
1435 ;; because the DTD doesn't allow embedding <font> in <pre>. | |
1436 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">" | |
1437 ) | |
1438 | |
1439 (defun htmlize-default-body-tag (face-map) | |
1440 nil ; no doc-string | |
1441 face-map ; shut up the byte-compiler | |
1442 "<body>") | |
1443 | |
1444 ;;; CSS based output support. | |
1445 | |
1446 ;; Internal function; not a method. | |
1447 (defun htmlize-css-specs (fstruct) | |
1448 (let (result) | |
1449 (when (htmlize-fstruct-foreground fstruct) | |
1450 (push (format "color: %s;" (htmlize-fstruct-foreground fstruct)) | |
1451 result)) | |
1452 (when (htmlize-fstruct-background fstruct) | |
1453 (push (format "background-color: %s;" | |
1454 (htmlize-fstruct-background fstruct)) | |
1455 result)) | |
1456 (let ((size (htmlize-fstruct-size fstruct))) | |
1457 (when (and size (not (eq htmlize-ignore-face-size t))) | |
1458 (cond ((floatp size) | |
1459 (push (format "font-size: %d%%;" (* 100 size)) result)) | |
1460 ((not (eq htmlize-ignore-face-size 'absolute)) | |
1461 (push (format "font-size: %spt;" (/ size 10.0)) result))))) | |
1462 (when (htmlize-fstruct-boldp fstruct) | |
1463 (push "font-weight: bold;" result)) | |
1464 (when (htmlize-fstruct-italicp fstruct) | |
1465 (push "font-style: italic;" result)) | |
1466 (when (htmlize-fstruct-underlinep fstruct) | |
1467 (push "text-decoration: underline;" result)) | |
1468 (when (htmlize-fstruct-overlinep fstruct) | |
1469 (push "text-decoration: overline;" result)) | |
1470 (when (htmlize-fstruct-strikep fstruct) | |
1471 (push "text-decoration: line-through;" result)) | |
1472 (nreverse result))) | |
1473 | |
1474 (defun htmlize-css-insert-head (buffer-faces face-map) | |
1475 (insert " <style type=\"text/css\">\n <!--\n") | |
1476 (insert " body {\n " | |
1477 (mapconcat #'identity | |
1478 (htmlize-css-specs (gethash 'default face-map)) | |
1479 "\n ") | |
1480 "\n }\n") | |
1481 (dolist (face (sort* (copy-list buffer-faces) #'string-lessp | |
1482 :key (lambda (f) | |
1483 (htmlize-fstruct-css-name (gethash f face-map))))) | |
1484 (let* ((fstruct (gethash face face-map)) | |
1485 (cleaned-up-face-name | |
1486 (let ((s | |
1487 ;; Use `prin1-to-string' rather than `symbol-name' | |
1488 ;; to get the face name because the "face" can also | |
1489 ;; be an attrlist, which is not a symbol. | |
1490 (prin1-to-string face))) | |
1491 ;; If the name contains `--' or `*/', remove them. | |
1492 (while (string-match "--" s) | |
1493 (setq s (replace-match "-" t t s))) | |
1494 (while (string-match "\\*/" s) | |
1495 (setq s (replace-match "XX" t t s))) | |
1496 s)) | |
1497 (specs (htmlize-css-specs fstruct))) | |
1498 (insert " ." (htmlize-fstruct-css-name fstruct)) | |
1499 (if (null specs) | |
1500 (insert " {") | |
1501 (insert " {\n /* " cleaned-up-face-name " */\n " | |
1502 (mapconcat #'identity specs "\n "))) | |
1503 (insert "\n }\n"))) | |
1504 (insert htmlize-hyperlink-style | |
1505 " -->\n </style>\n")) | |
1506 | |
1507 (defun htmlize-css-text-markup (fstruct-list buffer) | |
1508 ;; Open the markup needed to insert text colored with FACES into | |
1509 ;; BUFFER. Return the function that closes the markup. | |
1510 | |
1511 ;; In CSS mode, this is easy: just nest the text in one <span | |
1512 ;; class=...> tag for each face in FSTRUCT-LIST. | |
1513 (dolist (fstruct fstruct-list) | |
1514 (princ "<span class=\"" buffer) | |
1515 (princ (htmlize-fstruct-css-name fstruct) buffer) | |
1516 (princ "\">" buffer)) | |
1517 (htmlize-lexlet ((fstruct-list fstruct-list) (buffer buffer)) | |
1518 (lambda () | |
1519 (dolist (fstruct fstruct-list) | |
1520 (ignore fstruct) ; shut up the byte-compiler | |
1521 (princ "</span>" buffer))))) | |
1522 | |
1523 ;; `inline-css' output support. | |
1524 | |
1525 (defun htmlize-inline-css-body-tag (face-map) | |
1526 (format "<body style=\"%s\">" | |
1527 (mapconcat #'identity (htmlize-css-specs (gethash 'default face-map)) | |
1528 " "))) | |
1529 | |
1530 (defun htmlize-inline-css-text-markup (fstruct-list buffer) | |
1531 (let* ((merged (htmlize-merge-faces fstruct-list)) | |
1532 (style (htmlize-memoize | |
1533 merged | |
1534 (let ((specs (htmlize-css-specs merged))) | |
1535 (and specs | |
1536 (mapconcat #'identity (htmlize-css-specs merged) " ")))))) | |
1537 (when style | |
1538 (princ "<span style=\"" buffer) | |
1539 (princ style buffer) | |
1540 (princ "\">" buffer)) | |
1541 (htmlize-lexlet ((style style) (buffer buffer)) | |
1542 (lambda () | |
1543 (when style | |
1544 (princ "</span>" buffer)))))) | |
1545 | |
1546 ;;; `font' tag based output support. | |
1547 | |
1548 (defun htmlize-font-body-tag (face-map) | |
1549 (let ((fstruct (gethash 'default face-map))) | |
1550 (format "<body text=\"%s\" bgcolor=\"%s\">" | |
1551 (htmlize-fstruct-foreground fstruct) | |
1552 (htmlize-fstruct-background fstruct)))) | |
1553 | |
1554 (defun htmlize-font-text-markup (fstruct-list buffer) | |
1555 ;; In `font' mode, we use the traditional HTML means of altering | |
1556 ;; presentation: <font> tag for colors, <b> for bold, <u> for | |
1557 ;; underline, and <strike> for strike-through. | |
1558 (let* ((merged (htmlize-merge-faces fstruct-list)) | |
1559 (markup (htmlize-memoize | |
1560 merged | |
1561 (cons (concat | |
1562 (and (htmlize-fstruct-foreground merged) | |
1563 (format "<font color=\"%s\">" (htmlize-fstruct-foreground merged))) | |
1564 (and (htmlize-fstruct-boldp merged) "<b>") | |
1565 (and (htmlize-fstruct-italicp merged) "<i>") | |
1566 (and (htmlize-fstruct-underlinep merged) "<u>") | |
1567 (and (htmlize-fstruct-strikep merged) "<strike>")) | |
1568 (concat | |
1569 (and (htmlize-fstruct-strikep merged) "</strike>") | |
1570 (and (htmlize-fstruct-underlinep merged) "</u>") | |
1571 (and (htmlize-fstruct-italicp merged) "</i>") | |
1572 (and (htmlize-fstruct-boldp merged) "</b>") | |
1573 (and (htmlize-fstruct-foreground merged) "</font>")))))) | |
1574 (princ (car markup) buffer) | |
1575 (htmlize-lexlet ((markup markup) (buffer buffer)) | |
1576 (lambda () | |
1577 (princ (cdr markup) buffer))))) | |
1578 | |
1579 (defun htmlize-buffer-1 () | |
1580 ;; Internal function; don't call it from outside this file. Htmlize | |
1581 ;; current buffer, writing the resulting HTML to a new buffer, and | |
1582 ;; return it. Unlike htmlize-buffer, this doesn't change current | |
1583 ;; buffer or use switch-to-buffer. | |
1584 (save-excursion | |
1585 ;; Protect against the hook changing the current buffer. | |
1586 (save-excursion | |
1587 (run-hooks 'htmlize-before-hook)) | |
1588 ;; Convince font-lock support modes to fontify the entire buffer | |
1589 ;; in advance. | |
1590 (htmlize-ensure-fontified) | |
1591 (clrhash htmlize-extended-character-cache) | |
1592 (clrhash htmlize-memoization-table) | |
1593 ;; It's important that the new buffer inherits default-directory | |
1594 ;; from the current buffer. | |
1595 (let ((htmlbuf (generate-new-buffer (if (buffer-file-name) | |
1596 (htmlize-make-file-name | |
1597 (file-name-nondirectory | |
1598 (buffer-file-name))) | |
1599 "*html*"))) | |
1600 (completed nil)) | |
1601 (unwind-protect | |
1602 (let* ((buffer-faces (htmlize-faces-in-buffer)) | |
1603 (face-map (htmlize-make-face-map (adjoin 'default buffer-faces))) | |
1604 (places (gensym)) | |
1605 (title (if (buffer-file-name) | |
1606 (file-name-nondirectory (buffer-file-name)) | |
1607 (buffer-name)))) | |
1608 (when htmlize-generate-hyperlinks | |
1609 (htmlize-create-auto-links)) | |
1610 (when htmlize-replace-form-feeds | |
1611 (htmlize-shadow-form-feeds)) | |
1612 | |
1613 ;; Initialize HTMLBUF and insert the HTML prolog. | |
1614 (with-current-buffer htmlbuf | |
1615 (buffer-disable-undo) | |
1616 (insert (htmlize-method doctype) ?\n | |
1617 (format "<!-- Created by htmlize-%s in %s mode. -->\n" | |
1618 htmlize-version htmlize-output-type) | |
1619 "<html>\n ") | |
1620 (put places 'head-start (point-marker)) | |
1621 (insert "<head>\n" | |
1622 " <title>" (htmlize-protect-string title) "</title>\n" | |
1623 (if htmlize-html-charset | |
1624 (format (concat " <meta http-equiv=\"Content-Type\" " | |
1625 "content=\"text/html; charset=%s\">\n") | |
1626 htmlize-html-charset) | |
1627 "") | |
1628 htmlize-head-tags) | |
1629 (htmlize-method insert-head buffer-faces face-map) | |
1630 (insert " </head>") | |
1631 (put places 'head-end (point-marker)) | |
1632 (insert "\n ") | |
1633 (put places 'body-start (point-marker)) | |
1634 (insert (htmlize-method body-tag face-map) | |
1635 "\n ") | |
1636 (put places 'content-start (point-marker)) | |
1637 (insert "<pre>\n")) | |
1638 (let ((text-markup | |
1639 ;; Get the inserter method, so we can funcall it inside | |
1640 ;; the loop. Not calling `htmlize-method' in the loop | |
1641 ;; body yields a measurable speed increase. | |
1642 (htmlize-method-function 'text-markup)) | |
1643 ;; Declare variables used in loop body outside the loop | |
1644 ;; because it's faster to establish `let' bindings only | |
1645 ;; once. | |
1646 next-change text face-list trailing-ellipsis | |
1647 fstruct-list last-fstruct-list | |
1648 (close-markup (lambda ()))) | |
1649 ;; This loop traverses and reads the source buffer, appending | |
1650 ;; the resulting HTML to HTMLBUF. This method is fast | |
1651 ;; because: 1) it doesn't require examining the text | |
1652 ;; properties char by char (htmlize-next-face-change is used | |
1653 ;; to move between runs with the same face), and 2) it doesn't | |
1654 ;; require frequent buffer switches, which are slow because | |
1655 ;; they rebind all buffer-local vars. | |
1656 (goto-char (point-min)) | |
1657 (while (not (eobp)) | |
1658 (setq next-change (htmlize-next-face-change (point))) | |
1659 ;; Get faces in use between (point) and NEXT-CHANGE, and | |
1660 ;; convert them to fstructs. | |
1661 (setq face-list (htmlize-faces-at-point) | |
1662 fstruct-list (delq nil (mapcar (lambda (f) | |
1663 (gethash f face-map)) | |
1664 face-list))) | |
1665 (multiple-value-setq (text trailing-ellipsis) | |
1666 (htmlize-extract-text (point) next-change trailing-ellipsis)) | |
1667 ;; Don't bother writing anything if there's no text (this | |
1668 ;; happens in invisible regions). | |
1669 (when (> (length text) 0) | |
1670 ;; Open the new markup if necessary and insert the text. | |
1671 (when (not (equalp fstruct-list last-fstruct-list)) | |
1672 (funcall close-markup) | |
1673 (setq last-fstruct-list fstruct-list | |
1674 close-markup (funcall text-markup fstruct-list htmlbuf))) | |
1675 (princ text htmlbuf)) | |
1676 (goto-char next-change)) | |
1677 | |
1678 ;; We've gone through the buffer; close the markup from | |
1679 ;; the last run, if any. | |
1680 (funcall close-markup)) | |
1681 | |
1682 ;; Insert the epilog and post-process the buffer. | |
1683 (with-current-buffer htmlbuf | |
1684 (insert "</pre>") | |
1685 (put places 'content-end (point-marker)) | |
1686 (insert "\n </body>") | |
1687 (put places 'body-end (point-marker)) | |
1688 (insert "\n</html>\n") | |
1689 (htmlize-defang-local-variables) | |
1690 (goto-char (point-min)) | |
1691 (when htmlize-html-major-mode | |
1692 ;; What sucks about this is that the minor modes, most notably | |
1693 ;; font-lock-mode, won't be initialized. Oh well. | |
1694 (funcall htmlize-html-major-mode)) | |
1695 (set (make-local-variable 'htmlize-buffer-places) | |
1696 (symbol-plist places)) | |
1697 (run-hooks 'htmlize-after-hook) | |
1698 (buffer-enable-undo)) | |
1699 (setq completed t) | |
1700 htmlbuf) | |
1701 | |
1702 (when (not completed) | |
1703 (kill-buffer htmlbuf)) | |
1704 (htmlize-delete-tmp-overlays))))) | |
1705 | |
1706 ;; Utility functions. | |
1707 | |
1708 (defmacro htmlize-with-fontify-message (&rest body) | |
1709 ;; When forcing fontification of large buffers in | |
1710 ;; htmlize-ensure-fontified, inform the user that he is waiting for | |
1711 ;; font-lock, not for htmlize to finish. | |
1712 `(progn | |
1713 (if (> (buffer-size) 65536) | |
1714 (message "Forcing fontification of %s..." | |
1715 (buffer-name (current-buffer)))) | |
1716 ,@body | |
1717 (if (> (buffer-size) 65536) | |
1718 (message "Forcing fontification of %s...done" | |
1719 (buffer-name (current-buffer)))))) | |
1720 | |
1721 (defun htmlize-ensure-fontified () | |
1722 ;; If font-lock is being used, ensure that the "support" modes | |
1723 ;; actually fontify the buffer. If font-lock is not in use, we | |
1724 ;; don't care because, except in htmlize-file, we don't force | |
1725 ;; font-lock on the user. | |
1726 (when (and (boundp 'font-lock-mode) | |
1727 font-lock-mode) | |
1728 ;; In part taken from ps-print-ensure-fontified in GNU Emacs 21. | |
1729 (cond | |
1730 ((and (boundp 'jit-lock-mode) | |
1731 (symbol-value 'jit-lock-mode)) | |
1732 (htmlize-with-fontify-message | |
1733 (jit-lock-fontify-now (point-min) (point-max)))) | |
1734 ((and (boundp 'lazy-lock-mode) | |
1735 (symbol-value 'lazy-lock-mode)) | |
1736 (htmlize-with-fontify-message | |
1737 (lazy-lock-fontify-region (point-min) (point-max)))) | |
1738 ((and (boundp 'lazy-shot-mode) | |
1739 (symbol-value 'lazy-shot-mode)) | |
1740 (htmlize-with-fontify-message | |
1741 ;; lazy-shot is amazing in that it must *refontify* the region, | |
1742 ;; even if the whole buffer has already been fontified. <sigh> | |
1743 (lazy-shot-fontify-region (point-min) (point-max)))) | |
1744 ;; There's also fast-lock, but we don't need to handle specially, | |
1745 ;; I think. fast-lock doesn't really defer fontification, it | |
1746 ;; just saves it to an external cache so it's not done twice. | |
1747 ))) | |
1748 | |
1749 | |
1750 ;;;###autoload | |
1751 (defun htmlize-buffer (&optional buffer) | |
1752 "Convert BUFFER to HTML, preserving colors and decorations. | |
1753 | |
1754 The generated HTML is available in a new buffer, which is returned. | |
1755 When invoked interactively, the new buffer is selected in the current | |
1756 window. The title of the generated document will be set to the buffer's | |
1757 file name or, if that's not available, to the buffer's name. | |
1758 | |
1759 Note that htmlize doesn't fontify your buffers, it only uses the | |
1760 decorations that are already present. If you don't set up font-lock or | |
1761 something else to fontify your buffers, the resulting HTML will be | |
1762 plain. Likewise, if you don't like the choice of colors, fix the mode | |
1763 that created them, or simply alter the faces it uses." | |
1764 (interactive) | |
1765 (let ((htmlbuf (with-current-buffer (or buffer (current-buffer)) | |
1766 (htmlize-buffer-1)))) | |
1767 (when (interactive-p) | |
1768 (switch-to-buffer htmlbuf)) | |
1769 htmlbuf)) | |
1770 | |
1771 ;;;###autoload | |
1772 (defun htmlize-region (beg end) | |
1773 "Convert the region to HTML, preserving colors and decorations. | |
1774 See `htmlize-buffer' for details." | |
1775 (interactive "r") | |
1776 ;; Don't let zmacs region highlighting end up in HTML. | |
1777 (when (fboundp 'zmacs-deactivate-region) | |
1778 (zmacs-deactivate-region)) | |
1779 (let ((htmlbuf (save-restriction | |
1780 (narrow-to-region beg end) | |
1781 (htmlize-buffer-1)))) | |
1782 (when (interactive-p) | |
1783 (switch-to-buffer htmlbuf)) | |
1784 htmlbuf)) | |
1785 | |
1786 (defun htmlize-region-for-paste (beg end) | |
1787 "Htmlize the region and return just the HTML as a string. | |
1788 This forces the `inline-css' style and only returns the HTML body, | |
1789 but without the BODY tag. This should make it useful for inserting | |
1790 the text to another HTML buffer." | |
1791 (let* ((htmlize-output-type 'inline-css) | |
1792 (htmlbuf (htmlize-region beg end))) | |
1793 (unwind-protect | |
1794 (with-current-buffer htmlbuf | |
1795 (buffer-substring (plist-get htmlize-buffer-places 'content-start) | |
1796 (plist-get htmlize-buffer-places 'content-end))) | |
1797 (kill-buffer htmlbuf)))) | |
1798 | |
1799 (defun htmlize-make-file-name (file) | |
1800 "Make an HTML file name from FILE. | |
1801 | |
1802 In its default implementation, this simply appends `.html' to FILE. | |
1803 This function is called by htmlize to create the buffer file name, and | |
1804 by `htmlize-file' to create the target file name. | |
1805 | |
1806 More elaborate transformations are conceivable, such as changing FILE's | |
1807 extension to `.html' (\"file.c\" -> \"file.html\"). If you want them, | |
1808 overload this function to do it and htmlize will comply." | |
1809 (concat file ".html")) | |
1810 | |
1811 ;; Older implementation of htmlize-make-file-name that changes FILE's | |
1812 ;; extension to ".html". | |
1813 ;(defun htmlize-make-file-name (file) | |
1814 ; (let ((extension (file-name-extension file)) | |
1815 ; (sans-extension (file-name-sans-extension file))) | |
1816 ; (if (or (equal extension "html") | |
1817 ; (equal extension "htm") | |
1818 ; (equal sans-extension "")) | |
1819 ; (concat file ".html") | |
1820 ; (concat sans-extension ".html")))) | |
1821 | |
1822 ;;;###autoload | |
1823 (defun htmlize-file (file &optional target) | |
1824 "Load FILE, fontify it, convert it to HTML, and save the result. | |
1825 | |
1826 Contents of FILE are inserted into a temporary buffer, whose major mode | |
1827 is set with `normal-mode' as appropriate for the file type. The buffer | |
1828 is subsequently fontified with `font-lock' and converted to HTML. Note | |
1829 that, unlike `htmlize-buffer', this function explicitly turns on | |
1830 font-lock. If a form of highlighting other than font-lock is desired, | |
1831 please use `htmlize-buffer' directly on buffers so highlighted. | |
1832 | |
1833 Buffers currently visiting FILE are unaffected by this function. The | |
1834 function does not change current buffer or move the point. | |
1835 | |
1836 If TARGET is specified and names a directory, the resulting file will be | |
1837 saved there instead of to FILE's directory. If TARGET is specified and | |
1838 does not name a directory, it will be used as output file name." | |
1839 (interactive (list (read-file-name | |
1840 "HTML-ize file: " | |
1841 nil nil nil (and (buffer-file-name) | |
1842 (file-name-nondirectory | |
1843 (buffer-file-name)))))) | |
1844 (let ((output-file (if (and target (not (file-directory-p target))) | |
1845 target | |
1846 (expand-file-name | |
1847 (htmlize-make-file-name (file-name-nondirectory file)) | |
1848 (or target (file-name-directory file))))) | |
1849 ;; Try to prevent `find-file-noselect' from triggering | |
1850 ;; font-lock because we'll fontify explicitly below. | |
1851 (font-lock-mode nil) | |
1852 (font-lock-auto-fontify nil) | |
1853 (global-font-lock-mode nil) | |
1854 ;; Ignore the size limit for the purposes of htmlization. | |
1855 (font-lock-maximum-size nil) | |
1856 ;; Disable font-lock support modes. This will only work in | |
1857 ;; more recent Emacs versions, so htmlize-buffer-1 still needs | |
1858 ;; to call htmlize-ensure-fontified. | |
1859 (font-lock-support-mode nil)) | |
1860 (with-temp-buffer | |
1861 ;; Insert FILE into the temporary buffer. | |
1862 (insert-file-contents file) | |
1863 ;; Set the file name so normal-mode and htmlize-buffer-1 pick it | |
1864 ;; up. Restore it afterwards so with-temp-buffer's kill-buffer | |
1865 ;; doesn't complain about killing a modified buffer. | |
1866 (let ((buffer-file-name file)) | |
1867 ;; Set the major mode for the sake of font-lock. | |
1868 (normal-mode) | |
1869 (font-lock-mode 1) | |
1870 (unless font-lock-mode | |
1871 ;; In GNU Emacs (font-lock-mode 1) doesn't force font-lock, | |
1872 ;; contrary to the documentation. This seems to work. | |
1873 (font-lock-fontify-buffer)) | |
1874 ;; htmlize the buffer and save the HTML. | |
1875 (with-current-buffer (htmlize-buffer-1) | |
1876 (unwind-protect | |
1877 (progn | |
1878 (run-hooks 'htmlize-file-hook) | |
1879 (write-region (point-min) (point-max) output-file)) | |
1880 (kill-buffer (current-buffer))))))) | |
1881 ;; I haven't decided on a useful return value yet, so just return | |
1882 ;; nil. | |
1883 nil) | |
1884 | |
1885 ;;;###autoload | |
1886 (defun htmlize-many-files (files &optional target-directory) | |
1887 "Convert FILES to HTML and save the corresponding HTML versions. | |
1888 | |
1889 FILES should be a list of file names to convert. This function calls | |
1890 `htmlize-file' on each file; see that function for details. When | |
1891 invoked interactively, you are prompted for a list of files to convert, | |
1892 terminated with RET. | |
1893 | |
1894 If TARGET-DIRECTORY is specified, the HTML files will be saved to that | |
1895 directory. Normally, each HTML file is saved to the directory of the | |
1896 corresponding source file." | |
1897 (interactive | |
1898 (list | |
1899 (let (list file) | |
1900 ;; Use empty string as DEFAULT because setting DEFAULT to nil | |
1901 ;; defaults to the directory name, which is not what we want. | |
1902 (while (not (equal (setq file (read-file-name | |
1903 "HTML-ize file (RET to finish): " | |
1904 (and list (file-name-directory | |
1905 (car list))) | |
1906 "" t)) | |
1907 "")) | |
1908 (push file list)) | |
1909 (nreverse list)))) | |
1910 ;; Verify that TARGET-DIRECTORY is indeed a directory. If it's a | |
1911 ;; file, htmlize-file will use it as target, and that doesn't make | |
1912 ;; sense. | |
1913 (and target-directory | |
1914 (not (file-directory-p target-directory)) | |
1915 (error "target-directory must name a directory: %s" target-directory)) | |
1916 (dolist (file files) | |
1917 (htmlize-file file target-directory))) | |
1918 | |
1919 ;;;###autoload | |
1920 (defun htmlize-many-files-dired (arg &optional target-directory) | |
1921 "HTMLize dired-marked files." | |
1922 (interactive "P") | |
1923 (htmlize-many-files (dired-get-marked-files nil arg) target-directory)) | |
1924 | |
1925 (provide 'htmlize) | |
1926 | |
1927 ;; Local Variables: | |
1928 ;; byte-compile-warnings: (not cl-functions lexical unresolved obsolete) | |
1929 ;; lexical-binding: t | |
1930 ;; End: | |
1931 | |
1932 ;;; htmlize.el ends here |