Merge from emacs-23; up to 2010-06-12T11:17:12Z!eliz@gnu.org.
[bpt/emacs.git] / lisp / gnus / shr.el
CommitLineData
367f7f81
LMI
1;;; shr.el --- Simple HTML Renderer
2
73b0cd50 3;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
367f7f81
LMI
4
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Keywords: html
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; This package takes a HTML parse tree (as provided by
26;; libxml-parse-html-region) and renders it in the current buffer. It
27;; does not do CSS, JavaScript or anything advanced: It's geared
28;; towards rendering typical short snippets of HTML, like what you'd
29;; find in HTML email and the like.
30
31;;; Code:
32
3d319c8f 33(eval-when-compile (require 'cl))
71e691a5
G
34(require 'browse-url)
35
870409d4
G
36(defgroup shr nil
37 "Simple HTML Renderer"
38 :group 'mail)
39
40(defcustom shr-max-image-proportion 0.9
41 "How big pictures displayed are in relation to the window they're in.
42A value of 0.7 means that they are allowed to take up 70% of the
43width and height of the window. If they are larger than this,
44and Emacs supports it, then the images will be rescaled down to
45fit these criteria."
46 :version "24.1"
47 :group 'shr
48 :type 'float)
49
50(defcustom shr-blocked-images nil
51 "Images that have URLs matching this regexp will be blocked."
52 :version "24.1"
53 :group 'shr
54 :type 'regexp)
55
a5057546 56(defcustom shr-table-horizontal-line ?
d3098750
LMI
57 "Character used to draw horizontal table lines."
58 :group 'shr
59 :type 'character)
60
a5057546 61(defcustom shr-table-vertical-line ?
d3098750 62 "Character used to draw vertical table lines."
afba0c4b 63 :group 'shr
030158f3 64 :type 'character)
afba0c4b 65
a5057546 66(defcustom shr-table-corner ?
d3098750 67 "Character used to draw table corners."
6b7df8d3 68 :group 'shr
030158f3 69 :type 'character)
6b7df8d3
G
70
71(defcustom shr-hr-line ?-
d3098750 72 "Character used to draw hr lines."
afba0c4b 73 :group 'shr
030158f3 74 :type 'character)
afba0c4b 75
d0e0de31 76(defcustom shr-width fill-column
bb7f5cbc
G
77 "Frame width to use for rendering.
78May either be an integer specifying a fixed width in characters,
79or nil, meaning that the full width of the window should be
80used."
81 :type '(choice (integer :tag "Fixed width in characters")
82 (const :tag "Use the width of the window" nil))
d0e0de31
JD
83 :group 'shr)
84
130e977f
LMI
85(defvar shr-content-function nil
86 "If bound, this should be a function that will return the content.
87This is used for cid: URLs, and the function is called with the
88cid: URL as the argument.")
89
b9bdaf74
KY
90(defvar shr-put-image-function 'shr-put-image
91 "Function called to put image and alt string.")
92
6eee2678
LMI
93(defface shr-strike-through '((t (:strike-through t)))
94 "Font for <s> elements."
95 :group 'shr)
96
f8774e35 97(defface shr-link
7ef1d634 98 '((t (:inherit link)))
df26ce09 99 "Font for link elements."
c2f51e23
G
100 :group 'shr)
101
66627fa9
G
102;;; Internal variables.
103
870409d4
G
104(defvar shr-folding-mode nil)
105(defvar shr-state nil)
106(defvar shr-start nil)
a41c2e6d 107(defvar shr-indentation 0)
130e977f 108(defvar shr-inhibit-images nil)
66627fa9 109(defvar shr-list-mode nil)
3d319c8f 110(defvar shr-content-cache nil)
83ffd571 111(defvar shr-kinsoku-shorten nil)
99e65b2d 112(defvar shr-table-depth 0)
04db63bc 113(defvar shr-stylesheet nil)
dbd5ffad 114(defvar shr-base nil)
870409d4 115
71e691a5
G
116(defvar shr-map
117 (let ((map (make-sparse-keymap)))
118 (define-key map "a" 'shr-show-alt-text)
119 (define-key map "i" 'shr-browse-image)
120 (define-key map "I" 'shr-insert-image)
121 (define-key map "u" 'shr-copy-url)
122 (define-key map "v" 'shr-browse-url)
cdf1fca4 123 (define-key map "o" 'shr-save-contents)
71e691a5
G
124 (define-key map "\r" 'shr-browse-url)
125 map))
126
66627fa9
G
127;; Public functions and commands.
128
1518e4f0
G
129(defun shr-visit-file (file)
130 (interactive "fHTML file name: ")
131 (pop-to-buffer "*html*")
132 (erase-buffer)
133 (shr-insert-document
134 (with-temp-buffer
135 (insert-file-contents file)
136 (libxml-parse-html-region (point-min) (point-max)))))
137
66627fa9
G
138;;;###autoload
139(defun shr-insert-document (dom)
3d319c8f 140 (setq shr-content-cache nil)
66627fa9 141 (let ((shr-state nil)
bb7f5cbc 142 (shr-start nil)
dbd5ffad 143 (shr-base nil)
bb7f5cbc 144 (shr-width (or shr-width (window-width))))
66627fa9
G
145 (shr-descend (shr-transform-dom dom))))
146
147(defun shr-copy-url ()
148 "Copy the URL under point to the kill ring.
149If called twice, then try to fetch the URL and see whether it
150redirects somewhere else."
151 (interactive)
152 (let ((url (get-text-property (point) 'shr-url)))
153 (cond
154 ((not url)
155 (message "No URL under point"))
156 ;; Resolve redirected URLs.
157 ((equal url (car kill-ring))
158 (url-retrieve
159 url
160 (lambda (a)
161 (when (and (consp a)
162 (eq (car a) :redirect))
163 (with-temp-buffer
164 (insert (cadr a))
165 (goto-char (point-min))
166 ;; Remove common tracking junk from the URL.
167 (when (re-search-forward ".utm_.*" nil t)
168 (replace-match "" t t))
169 (message "Copied %s" (buffer-string))
170 (copy-region-as-kill (point-min) (point-max)))))))
171 ;; Copy the URL to the kill ring.
172 (t
173 (with-temp-buffer
174 (insert url)
175 (copy-region-as-kill (point-min) (point-max))
176 (message "Copied %s" url))))))
177
178(defun shr-show-alt-text ()
179 "Show the ALT text of the image under point."
180 (interactive)
181 (let ((text (get-text-property (point) 'shr-alt)))
182 (if (not text)
183 (message "No image under point")
184 (message "%s" text))))
185
186(defun shr-browse-image ()
187 "Browse the image under point."
188 (interactive)
8b6f6573 189 (let ((url (get-text-property (point) 'image-url)))
66627fa9
G
190 (if (not url)
191 (message "No image under point")
192 (message "Browsing %s..." url)
193 (browse-url url))))
194
3d319c8f
LMI
195(defun shr-insert-image ()
196 "Insert the image under point into the buffer."
197 (interactive)
8b6f6573 198 (let ((url (get-text-property (point) 'image-url)))
3d319c8f
LMI
199 (if (not url)
200 (message "No image under point")
201 (message "Inserting %s..." url)
202 (url-retrieve url 'shr-image-fetched
203 (list (current-buffer) (1- (point)) (point-marker))
204 t))))
205
66627fa9
G
206;;; Utility functions.
207
870409d4
G
208(defun shr-transform-dom (dom)
209 (let ((result (list (pop dom))))
210 (dolist (arg (pop dom))
211 (push (cons (intern (concat ":" (symbol-name (car arg))) obarray)
212 (cdr arg))
213 result))
214 (dolist (sub dom)
215 (if (stringp sub)
953d41c4 216 (push (cons 'text sub) result)
870409d4
G
217 (push (shr-transform-dom sub) result)))
218 (nreverse result)))
219
870409d4 220(defun shr-descend (dom)
ebe79557
LMI
221 (let ((function (intern (concat "shr-tag-" (symbol-name (car dom))) obarray))
222 (style (cdr (assq :style (cdr dom))))
04db63bc 223 (shr-stylesheet shr-stylesheet)
ebe79557 224 (start (point)))
b31b26b4
G
225 (when style
226 (if (string-match "color" style)
227 (setq shr-stylesheet (nconc (shr-parse-style style)
228 shr-stylesheet))
229 (setq style nil)))
870409d4
G
230 (if (fboundp function)
231 (funcall function (cdr dom))
ebe79557 232 (shr-generic (cdr dom)))
b31b26b4
G
233 ;; If style is set, then this node has set the color.
234 (when style
235 (shr-colorize-region start (point)
236 (cdr (assq 'color shr-stylesheet))
237 (cdr (assq 'background-color shr-stylesheet))))))
870409d4
G
238
239(defun shr-generic (cont)
240 (dolist (sub cont)
241 (cond
953d41c4 242 ((eq (car sub) 'text)
870409d4 243 (shr-insert (cdr sub)))
a41c2e6d 244 ((listp (cdr sub))
870409d4
G
245 (shr-descend sub)))))
246
ed797193
G
247(defmacro shr-char-breakable-p (char)
248 "Return non-nil if a line can be broken before and after CHAR."
249 `(aref fill-find-break-point-function-table ,char))
250(defmacro shr-char-nospace-p (char)
251 "Return non-nil if no space is required before and after CHAR."
252 `(aref fill-nospace-between-words-table ,char))
253
254;; KINSOKU is a Japanese word meaning a rule that should not be violated.
255;; In Emacs, it is a term used for characters, e.g. punctuation marks,
256;; parentheses, and so on, that should not be placed in the beginning
257;; of a line or the end of a line.
258(defmacro shr-char-kinsoku-bol-p (char)
259 "Return non-nil if a line ought not to begin with CHAR."
260 `(aref (char-category-set ,char) ?>))
261(defmacro shr-char-kinsoku-eol-p (char)
262 "Return non-nil if a line ought not to end with CHAR."
263 `(aref (char-category-set ,char) ?<))
264(unless (shr-char-kinsoku-bol-p (make-char 'japanese-jisx0208 33 35))
265 (load "kinsoku" nil t))
266
66627fa9 267(defun shr-insert (text)
6b7df8d3
G
268 (when (and (eq shr-state 'image)
269 (not (string-match "\\`[ \t\n]+\\'" text)))
66627fa9
G
270 (insert "\n")
271 (setq shr-state nil))
272 (cond
273 ((eq shr-folding-mode 'none)
274 (insert text))
275 (t
73db8b08
KY
276 (when (and (string-match "\\`[ \t\n]" text)
277 (not (bolp))
278 (not (eq (char-after (1- (point))) ? )))
279 (insert " "))
280 (dolist (elem (split-string text))
281 (when (and (bolp)
282 (> shr-indentation 0))
283 (shr-indent))
73db8b08 284 ;; No space is needed behind a wide character categorized as
b41c2f65
KY
285 ;; kinsoku-bol, between characters both categorized as nospace,
286 ;; or at the beginning of a line.
73db8b08 287 (let (prev)
48ba8195
KY
288 (when (and (> (current-column) shr-indentation)
289 (eq (preceding-char) ? )
20438017 290 (or (= (line-beginning-position) (1- (point)))
ed797193
G
291 (and (shr-char-breakable-p
292 (setq prev (char-after (- (point) 2))))
293 (shr-char-kinsoku-bol-p prev))
294 (and (shr-char-nospace-p prev)
295 (shr-char-nospace-p (aref elem 0)))))
73db8b08 296 (delete-char -1)))
48ba8195
KY
297 ;; The shr-start is a special variable that is used to pass
298 ;; upwards the first point in the buffer where the text really
299 ;; starts.
300 (unless shr-start
301 (setq shr-start (point)))
73db8b08 302 (insert elem)
e7102c0a
KY
303 (let (found)
304 (while (and (> (current-column) shr-width)
305 (progn
306 (setq found (shr-find-fill-point))
b40950bf 307 (not (eolp))))
fe98a42f
KY
308 (when (eq (preceding-char) ? )
309 (delete-char -1))
310 (insert "\n")
311 (unless found
e7102c0a
KY
312 (put-text-property (1- (point)) (point) 'shr-break t)
313 ;; No space is needed at the beginning of a line.
314 (when (eq (following-char) ? )
315 (delete-char 1)))
316 (when (> shr-indentation 0)
317 (shr-indent))
318 (end-of-line))
319 (insert " ")))
73db8b08
KY
320 (unless (string-match "[ \t\n]\\'" text)
321 (delete-char -1)))))
66627fa9 322
6b7df8d3 323(defun shr-find-fill-point ()
83ffd571
KY
324 (when (> (move-to-column shr-width) shr-width)
325 (backward-char 1))
ed797193
G
326 (let ((bp (point))
327 failed)
328 (while (not (or (setq failed (= (current-column) shr-indentation))
329 (eq (preceding-char) ? )
330 (eq (following-char) ? )
331 (shr-char-breakable-p (preceding-char))
332 (shr-char-breakable-p (following-char))
7454326a
G
333 (if (eq (preceding-char) ?')
334 (not (memq (char-after (- (point) 2))
335 (list nil ?\n ? )))
7454326a 336 (and (shr-char-kinsoku-bol-p (preceding-char))
6568edea 337 (shr-char-breakable-p (following-char))
7454326a 338 (not (shr-char-kinsoku-bol-p (following-char)))))
ed797193 339 (shr-char-kinsoku-eol-p (following-char))))
83ffd571 340 (backward-char 1))
ed797193
G
341 (if (and (not (or failed (eolp)))
342 (eq (preceding-char) ?'))
343 (while (not (or (setq failed (eolp))
344 (eq (following-char) ? )
345 (shr-char-breakable-p (following-char))
346 (shr-char-kinsoku-eol-p (following-char))))
347 (forward-char 1)))
83ffd571 348 (if failed
20438017 349 ;; There's no breakable point, so we give it up.
ed797193
G
350 (let (found)
351 (goto-char bp)
352 (unless shr-kinsoku-shorten
353 (while (and (setq found (re-search-forward
354 "\\(\\c>\\)\\| \\|\\c<\\|\\c|"
355 (line-end-position) 'move))
356 (eq (preceding-char) ?')))
357 (if (and found (not (match-beginning 1)))
358 (goto-char (match-beginning 0)))))
b40950bf
KY
359 (or
360 (eolp)
ed797193
G
361 ;; Don't put kinsoku-bol characters at the beginning of a line,
362 ;; or kinsoku-eol characters at the end of a line.
363 (cond
364 (shr-kinsoku-shorten
365 (while (and (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
366 (shr-char-kinsoku-eol-p (preceding-char)))
367 (backward-char 1))
368 (when (setq failed (= (current-column) shr-indentation))
369 ;; There's no breakable point that doesn't violate kinsoku,
370 ;; so we look for the second best position.
371 (while (and (progn
372 (forward-char 1)
373 (<= (current-column) shr-width))
374 (progn
375 (setq bp (point))
376 (shr-char-kinsoku-eol-p (following-char)))))
377 (goto-char bp)))
378 ((shr-char-kinsoku-eol-p (preceding-char))
379 (if (shr-char-kinsoku-eol-p (following-char))
380 ;; There are consecutive kinsoku-eol characters.
381 (setq failed t)
382 (let ((count 4))
383 (while
384 (progn
385 (backward-char 1)
386 (and (> (setq count (1- count)) 0)
387 (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
388 (or (shr-char-kinsoku-eol-p (preceding-char))
389 (shr-char-kinsoku-bol-p (following-char)))))))
390 (if (setq failed (= (current-column) shr-indentation))
b40950bf 391 ;; There's no breakable point that doesn't violate kinsoku,
ed797193
G
392 ;; so we go to the second best position.
393 (if (looking-at "\\(\\c<+\\)\\c<")
394 (goto-char (match-end 1))
395 (forward-char 1)))))
396 (t
397 (if (shr-char-kinsoku-bol-p (preceding-char))
398 ;; There are consecutive kinsoku-bol characters.
399 (setq failed t)
b40950bf
KY
400 (let ((count 4))
401 (while (and (>= (setq count (1- count)) 0)
ed797193
G
402 (shr-char-kinsoku-bol-p (following-char))
403 (shr-char-breakable-p (following-char)))
404 (forward-char 1))))))
405 (when (eq (following-char) ? )
406 (forward-char 1))))
407 (not failed)))
6b7df8d3 408
dbd5ffad
LMI
409(defun shr-expand-url (url)
410 (cond
411 ;; Absolute URL.
2b455160
LMI
412 ((or (not url)
413 (string-match "\\`[a-z]*:" url)
dbd5ffad
LMI
414 (not shr-base))
415 url)
416 ((and (not (string-match "/\\'" shr-base))
891e751b 417 (not (string-match "\\`/" url)))
dbd5ffad
LMI
418 (concat shr-base "/" url))
419 (t
420 (concat shr-base url))))
421
66627fa9
G
422(defun shr-ensure-newline ()
423 (unless (zerop (current-column))
424 (insert "\n")))
a41c2e6d
G
425
426(defun shr-ensure-paragraph ()
427 (unless (bobp)
f7aa248a 428 (if (<= (current-column) shr-indentation)
71e691a5
G
429 (unless (save-excursion
430 (forward-line -1)
431 (looking-at " *$"))
a41c2e6d
G
432 (insert "\n"))
433 (if (save-excursion
434 (beginning-of-line)
5d2ef6db 435 (looking-at " *$"))
a41c2e6d
G
436 (insert "\n")
437 (insert "\n\n")))))
438
66627fa9 439(defun shr-indent ()
f7aa248a
G
440 (when (> shr-indentation 0)
441 (insert (make-string shr-indentation ? ))))
870409d4 442
a41c2e6d 443(defun shr-fontize-cont (cont &rest types)
870409d4
G
444 (let (shr-start)
445 (shr-generic cont)
a41c2e6d
G
446 (dolist (type types)
447 (shr-add-font (or shr-start (point)) (point) type))))
870409d4 448
6c85a14f
LMI
449;; Add an overlay in the region, but avoid putting the font properties
450;; on blank text at the start of the line, and the newline at the end,
451;; to avoid ugliness.
870409d4 452(defun shr-add-font (start end type)
6c85a14f
LMI
453 (save-excursion
454 (goto-char start)
455 (while (< (point) end)
456 (when (bolp)
457 (skip-chars-forward " "))
458 (let ((overlay (make-overlay (point) (min (line-end-position) end))))
459 (overlay-put overlay 'face type))
460 (if (< (line-end-position) end)
461 (forward-line 1)
462 (goto-char end)))))
870409d4 463
71e691a5
G
464(defun shr-browse-url ()
465 "Browse the URL under point."
466 (interactive)
467 (let ((url (get-text-property (point) 'shr-url)))
181cb5fb
G
468 (cond
469 ((not url)
470 (message "No link under point"))
471 ((string-match "^mailto:" url)
be3c11b3 472 (browse-url-mailto url))
181cb5fb
G
473 (t
474 (browse-url url)))))
71e691a5 475
cdf1fca4
LMI
476(defun shr-save-contents (directory)
477 "Save the contents from URL in a file."
478 (interactive "DSave contents of URL to directory: ")
479 (let ((url (get-text-property (point) 'shr-url)))
480 (if (not url)
481 (message "No link under point")
482 (url-retrieve (shr-encode-url url)
483 'shr-store-contents (list url directory)))))
484
485(defun shr-store-contents (status url directory)
486 (unless (plist-get status :error)
487 (when (or (search-forward "\n\n" nil t)
488 (search-forward "\r\n\r\n" nil t))
489 (write-region (point) (point-max)
490 (expand-file-name (file-name-nondirectory url)
491 directory)))))
492
870409d4
G
493(defun shr-image-fetched (status buffer start end)
494 (when (and (buffer-name buffer)
495 (not (plist-get status :error)))
496 (url-store-in-cache (current-buffer))
497 (when (or (search-forward "\n\n" nil t)
498 (search-forward "\r\n\r\n" nil t))
499 (let ((data (buffer-substring (point) (point-max))))
500 (with-current-buffer buffer
cb51ba08
LI
501 (save-excursion
502 (let ((alt (buffer-substring start end))
503 (inhibit-read-only t))
504 (delete-region start end)
505 (goto-char start)
b9bdaf74 506 (funcall shr-put-image-function data alt)))))))
870409d4
G
507 (kill-buffer (current-buffer)))
508
99e65b2d 509(defun shr-put-image (data alt)
b9bdaf74 510 "Put image DATA with a string ALT. Return image."
4abff904
JD
511 (if (display-graphic-p)
512 (let ((image (ignore-errors
513 (shr-rescale-image data))))
514 (when image
c0f9edce
G
515 ;; When inserting big-ish pictures, put them at the
516 ;; beginning of the line.
517 (when (and (> (current-column) 0)
518 (> (car (image-size image t)) 400))
519 (insert "\n"))
b9bdaf74
KY
520 (insert-image image (or alt "*")))
521 image)
99e65b2d 522 (insert alt)))
870409d4
G
523
524(defun shr-rescale-image (data)
525 (if (or (not (fboundp 'imagemagick-types))
526 (not (get-buffer-window (current-buffer))))
527 (create-image data nil t)
528 (let* ((image (create-image data nil t))
a41c2e6d 529 (size (image-size image t))
870409d4
G
530 (width (car size))
531 (height (cdr size))
532 (edges (window-inside-pixel-edges
533 (get-buffer-window (current-buffer))))
534 (window-width (truncate (* shr-max-image-proportion
535 (- (nth 2 edges) (nth 0 edges)))))
536 (window-height (truncate (* shr-max-image-proportion
537 (- (nth 3 edges) (nth 1 edges)))))
538 scaled-image)
539 (when (> height window-height)
540 (setq image (or (create-image data 'imagemagick t
541 :height window-height)
542 image))
543 (setq size (image-size image t)))
544 (when (> (car size) window-width)
545 (setq image (or
546 (create-image data 'imagemagick t
547 :width window-width)
548 image)))
84d89ede
LMI
549 (when (and (fboundp 'create-animated-image)
550 (eq (image-type data nil t) 'gif))
551 (setq image (create-animated-image data 'gif t)))
870409d4
G
552 image)))
553
85a45a69
GM
554;; url-cache-extract autoloads url-cache.
555(declare-function url-cache-create-filename "url-cache" (url))
556(autoload 'mm-disable-multibyte "mm-util")
be3c11b3 557(autoload 'browse-url-mailto "browse-url")
85a45a69 558
870409d4
G
559(defun shr-get-image-data (url)
560 "Get image data for URL.
561Return a string with image data."
562 (with-temp-buffer
563 (mm-disable-multibyte)
71e691a5 564 (when (ignore-errors
ab67634f 565 (url-cache-extract (url-cache-create-filename (shr-encode-url url)))
71e691a5
G
566 t)
567 (when (or (search-forward "\n\n" nil t)
568 (search-forward "\r\n\r\n" nil t))
569 (buffer-substring (point) (point-max))))))
870409d4 570
40de2c6d
KY
571(defun shr-image-displayer (content-function)
572 "Return a function to display an image.
573CONTENT-FUNCTION is a function to retrieve an image for a cid url that
574is an argument. The function to be returned takes three arguments URL,
e2d0ba98 575START, and END. Note that START and END should be merkers."
40de2c6d 576 `(lambda (url start end)
f8d8a97b
KY
577 (when url
578 (if (string-match "\\`cid:" url)
579 ,(when content-function
580 `(let ((image (funcall ,content-function
581 (substring url (match-end 0)))))
582 (when image
583 (goto-char start)
b9bdaf74 584 (funcall shr-put-image-function
195b2593 585 image (buffer-substring start end))
e2d0ba98 586 (delete-region (point) end))))
f8d8a97b
KY
587 (url-retrieve url 'shr-image-fetched
588 (list (current-buffer) start end)
589 t)))))
40de2c6d 590
66627fa9
G
591(defun shr-heading (cont &rest types)
592 (shr-ensure-paragraph)
593 (apply #'shr-fontize-cont cont types)
594 (shr-ensure-paragraph))
595
85a45a69
GM
596(autoload 'widget-convert-button "wid-edit")
597
04db63bc 598(defun shr-urlify (start url &optional title)
de635afe
G
599 (widget-convert-button
600 'url-link start (point)
04db63bc 601 :help-echo (if title (format "%s (%s)" url title) url)
de635afe
G
602 :keymap shr-map
603 url)
f8774e35 604 (put-text-property start (point) 'face 'shr-link)
de635afe
G
605 (put-text-property start (point) 'shr-url url))
606
607(defun shr-encode-url (url)
608 "Encode URL."
609 (browse-url-url-encode-chars url "[)$ ]"))
610
ebe79557
LMI
611(autoload 'shr-color-visible "shr-color")
612(autoload 'shr-color->hexadecimal "shr-color")
144b7b5c
G
613
614(defun shr-color-check (fg bg)
615 "Check that FG is visible on BG.
616Returns (fg bg) with corrected values.
617Returns nil if the colors that would be used are the default
618ones, in case fg and bg are nil."
619 (when (or fg bg)
620 (let ((fixed (cond ((null fg) 'fg)
621 ((null bg) 'bg))))
622 ;; Convert colors to hexadecimal, or set them to default.
623 (let ((fg (or (shr-color->hexadecimal fg)
624 (frame-parameter nil 'foreground-color)))
625 (bg (or (shr-color->hexadecimal bg)
626 (frame-parameter nil 'background-color))))
627 (cond ((eq fixed 'bg)
628 ;; Only return the new fg
629 (list nil (cadr (shr-color-visible bg fg t))))
630 ((eq fixed 'fg)
631 ;; Invert args and results and return only the new bg
632 (list (cadr (shr-color-visible fg bg t)) nil))
633 (t
634 (shr-color-visible bg fg)))))))
635
04db63bc 636(defun shr-colorize-region (start end fg &optional bg)
b31b26b4 637 (when (or fg bg)
04db63bc 638 (let ((new-colors (shr-color-check fg bg)))
144b7b5c 639 (when new-colors
60568d74
LMI
640 (when fg
641 (shr-put-color start end :foreground (cadr new-colors)))
04db63bc 642 (when bg
ec72bf63
G
643 (shr-put-color start end :background (car new-colors))))
644 new-colors)))
04db63bc 645
9b053e76 646;; Put a color in the region, but avoid putting colors on blank
04db63bc
G
647;; text at the start of the line, and the newline at the end, to avoid
648;; ugliness. Also, don't overwrite any existing color information,
649;; since this can be called recursively, and we want the "inner" color
650;; to win.
651(defun shr-put-color (start end type color)
652 (save-excursion
653 (goto-char start)
654 (while (< (point) end)
3f39b526
LI
655 (when (and (bolp)
656 (not (eq type :background)))
04db63bc
G
657 (skip-chars-forward " "))
658 (when (> (line-end-position) (point))
659 (shr-put-color-1 (point) (min (line-end-position) end) type color))
660 (if (< (line-end-position) end)
661 (forward-line 1)
c5ecc769 662 (goto-char end)))
647559c2
LI
663 (when (and (eq type :background)
664 (= shr-table-depth 0))
c5ecc769
G
665 (shr-expand-newlines start end color))))
666
667(defun shr-expand-newlines (start end color)
668 (save-restriction
d709b79a
LI
669 ;; Skip past all white space at the start and ends.
670 (goto-char start)
671 (skip-chars-forward " \t\n")
672 (beginning-of-line)
673 (setq start (point))
674 (goto-char end)
675 (skip-chars-backward " \t\n")
676 (forward-line 1)
677 (setq end (point))
c5ecc769
G
678 (narrow-to-region start end)
679 (let ((width (shr-natural-width))
680 column)
681 (goto-char (point-min))
682 (while (not (eobp))
683 (end-of-line)
19e0dbe0
KY
684 (when (and (< (setq column (current-column)) width)
685 (< (setq column (shr-previous-newline-padding-width column))
d709b79a 686 width))
c5ecc769
G
687 (let ((overlay (make-overlay (point) (1+ (point)))))
688 (overlay-put overlay 'before-string
d709b79a
LI
689 (concat
690 (mapconcat
691 (lambda (overlay)
14596870
KY
692 (let ((string (plist-get
693 (overlay-properties overlay)
694 'before-string)))
d709b79a
LI
695 (if (not string)
696 ""
697 (overlay-put overlay 'before-string "")
698 string)))
699 (overlays-at (point))
700 "")
19e0dbe0 701 (propertize (make-string (- width column) ? )
d709b79a 702 'face (list :background color))))))
c5ecc769 703 (forward-line 1)))))
04db63bc 704
d709b79a
LI
705(defun shr-previous-newline-padding-width (width)
706 (let ((overlays (overlays-at (point)))
707 (previous-width 0))
708 (if (null overlays)
709 width
710 (dolist (overlay overlays)
711 (setq previous-width
712 (+ previous-width
14596870
KY
713 (length (plist-get (overlay-properties overlay)
714 'before-string)))))
d709b79a
LI
715 (+ width previous-width))))
716
04db63bc
G
717(defun shr-put-color-1 (start end type color)
718 (let* ((old-props (get-text-property start 'face))
296d197b
JD
719 (do-put (and (listp old-props)
720 (not (memq type old-props))))
04db63bc
G
721 change)
722 (while (< start end)
723 (setq change (next-single-property-change start 'face nil end))
724 (when do-put
725 (put-text-property start change 'face
726 (nconc (list type color) old-props)))
727 (setq old-props (get-text-property change 'face))
296d197b
JD
728 (setq do-put (and (listp old-props)
729 (not (memq type old-props))))
04db63bc
G
730 (setq start change))
731 (when (and do-put
732 (> end start))
733 (put-text-property start end 'face
734 (nconc (list type color old-props))))))
ebe79557 735
66627fa9
G
736;;; Tag-specific rendering rules.
737
144b7b5c 738(defun shr-tag-body (cont)
04db63bc 739 (let* ((start (point))
af4e5f4c
G
740 (fgcolor (cdr (or (assq :fgcolor cont)
741 (assq :text cont))))
04db63bc 742 (bgcolor (cdr (assq :bgcolor cont)))
b31b26b4
G
743 (shr-stylesheet (list (cons 'color fgcolor)
744 (cons 'background-color bgcolor))))
144b7b5c 745 (shr-generic cont)
04db63bc 746 (shr-colorize-region start (point) fgcolor bgcolor)))
144b7b5c 747
b31b26b4
G
748(defun shr-tag-style (cont)
749 )
750
f73341e2
LMI
751(defun shr-tag-script (cont)
752 )
753
a3af2929
LMI
754(defun shr-tag-sup (cont)
755 (let ((start (point)))
756 (shr-generic cont)
757 (put-text-property start (point) 'display '(raise 0.5))))
758
759(defun shr-tag-sub (cont)
760 (let ((start (point)))
761 (shr-generic cont)
762 (put-text-property start (point) 'display '(raise -0.5))))
763
7bafe9bc
LMI
764(defun shr-tag-label (cont)
765 (shr-generic cont)
766 (shr-ensure-paragraph))
767
66627fa9
G
768(defun shr-tag-p (cont)
769 (shr-ensure-paragraph)
f7aa248a 770 (shr-indent)
66627fa9
G
771 (shr-generic cont)
772 (shr-ensure-paragraph))
773
036d93bc
KY
774(defun shr-tag-div (cont)
775 (shr-ensure-newline)
776 (shr-indent)
777 (shr-generic cont)
778 (shr-ensure-newline))
779
6eee2678
LMI
780(defun shr-tag-s (cont)
781 (shr-fontize-cont cont 'shr-strike-through))
782
55385ebc
JD
783(defun shr-tag-del (cont)
784 (shr-fontize-cont cont 'shr-strike-through))
785
66627fa9
G
786(defun shr-tag-b (cont)
787 (shr-fontize-cont cont 'bold))
788
789(defun shr-tag-i (cont)
790 (shr-fontize-cont cont 'italic))
791
792(defun shr-tag-em (cont)
793 (shr-fontize-cont cont 'bold))
794
530f7b67
LMI
795(defun shr-tag-strong (cont)
796 (shr-fontize-cont cont 'bold))
797
66627fa9
G
798(defun shr-tag-u (cont)
799 (shr-fontize-cont cont 'underline))
800
2e76c12c
LMI
801(defun shr-parse-style (style)
802 (when style
a2994808
JD
803 (save-match-data
804 (when (string-match "\n" style)
805 (setq style (replace-match " " t t style))))
2e76c12c
LMI
806 (let ((plist nil))
807 (dolist (elem (split-string style ";"))
808 (when elem
809 (setq elem (split-string elem ":"))
810 (when (and (car elem)
811 (cadr elem))
812 (let ((name (replace-regexp-in-string "^ +\\| +$" "" (car elem)))
813 (value (replace-regexp-in-string "^ +\\| +$" "" (cadr elem))))
144b7b5c
G
814 (when (string-match " *!important\\'" value)
815 (setq value (substring value 0 (match-beginning 0))))
2e76c12c
LMI
816 (push (cons (intern name obarray)
817 value)
818 plist)))))
819 plist)))
820
dbd5ffad
LMI
821(defun shr-tag-base (cont)
822 (setq shr-base (cdr (assq :href cont))))
823
66627fa9
G
824(defun shr-tag-a (cont)
825 (let ((url (cdr (assq :href cont)))
04db63bc 826 (title (cdr (assq :title cont)))
66627fa9
G
827 (start (point))
828 shr-start)
829 (shr-generic cont)
dbd5ffad 830 (shr-urlify (or shr-start start) (shr-expand-url url) title)))
de635afe
G
831
832(defun shr-tag-object (cont)
99e65b2d
G
833 (let ((start (point))
834 url)
835 (dolist (elem cont)
836 (when (eq (car elem) 'embed)
837 (setq url (or url (cdr (assq :src (cdr elem))))))
838 (when (and (eq (car elem) 'param)
839 (equal (cdr (assq :name (cdr elem))) "movie"))
840 (setq url (or url (cdr (assq :value (cdr elem)))))))
de635afe
G
841 (when url
842 (shr-insert " [multimedia] ")
dbd5ffad 843 (shr-urlify start (shr-expand-url url)))
99e65b2d
G
844 (shr-generic cont)))
845
846(defun shr-tag-video (cont)
847 (let ((image (cdr (assq :poster cont)))
848 (url (cdr (assq :src cont)))
849 (start (point)))
850 (shr-tag-img nil image)
dbd5ffad 851 (shr-urlify start (shr-expand-url url))))
ab67634f 852
99e65b2d
G
853(defun shr-tag-img (cont &optional url)
854 (when (or url
855 (and cont
856 (cdr (assq :src cont))))
68f6bd17
KY
857 (when (and (> (current-column) 0)
858 (not (eq shr-state 'image)))
859 (insert "\n"))
860 (let ((alt (cdr (assq :alt cont)))
dbd5ffad 861 (url (shr-expand-url (or url (cdr (assq :src cont))))))
68f6bd17
KY
862 (let ((start (point-marker)))
863 (when (zerop (length alt))
953d41c4 864 (setq alt "*"))
68f6bd17 865 (cond
99e65b2d
G
866 ((or (member (cdr (assq :height cont)) '("0" "1"))
867 (member (cdr (assq :width cont)) '("0" "1")))
868 ;; Ignore zero-sized or single-pixel images.
869 )
68f6bd17
KY
870 ((and (not shr-inhibit-images)
871 (string-match "\\`cid:" url))
872 (let ((url (substring url (match-end 0)))
873 image)
874 (if (or (not shr-content-function)
875 (not (setq image (funcall shr-content-function url))))
876 (insert alt)
b9bdaf74 877 (funcall shr-put-image-function image alt))))
68f6bd17
KY
878 ((or shr-inhibit-images
879 (and shr-blocked-images
880 (string-match shr-blocked-images url)))
881 (setq shr-start (point))
882 (let ((shr-state 'space))
b354bc53
KY
883 (if (> (string-width alt) 8)
884 (shr-insert (truncate-string-to-width alt 8))
68f6bd17
KY
885 (shr-insert alt))))
886 ((url-is-cached (shr-encode-url url))
b9bdaf74 887 (funcall shr-put-image-function (shr-get-image-data url) alt))
68f6bd17
KY
888 (t
889 (insert alt)
f3b146e9
LMI
890 (funcall
891 (if (fboundp 'url-queue-retrieve)
892 'url-queue-retrieve
893 'url-retrieve)
894 (shr-encode-url url) 'shr-image-fetched
895 (list (current-buffer) start (point-marker))
896 t)))
68f6bd17
KY
897 (put-text-property start (point) 'keymap shr-map)
898 (put-text-property start (point) 'shr-alt alt)
8b6f6573
LMI
899 (put-text-property start (point) 'image-url url)
900 (put-text-property start (point) 'image-displayer
40de2c6d 901 (shr-image-displayer shr-content-function))
99e65b2d 902 (put-text-property start (point) 'help-echo alt)
68f6bd17 903 (setq shr-state 'image)))))
66627fa9
G
904
905(defun shr-tag-pre (cont)
906 (let ((shr-folding-mode 'none))
907 (shr-ensure-newline)
f7aa248a 908 (shr-indent)
66627fa9
G
909 (shr-generic cont)
910 (shr-ensure-newline)))
911
912(defun shr-tag-blockquote (cont)
913 (shr-ensure-paragraph)
f7aa248a 914 (shr-indent)
66627fa9
G
915 (let ((shr-indentation (+ shr-indentation 4)))
916 (shr-generic cont))
917 (shr-ensure-paragraph))
a41c2e6d
G
918
919(defun shr-tag-ul (cont)
920 (shr-ensure-paragraph)
921 (let ((shr-list-mode 'ul))
3d319c8f
LMI
922 (shr-generic cont))
923 (shr-ensure-paragraph))
a41c2e6d
G
924
925(defun shr-tag-ol (cont)
3d319c8f 926 (shr-ensure-paragraph)
a41c2e6d 927 (let ((shr-list-mode 1))
3d319c8f
LMI
928 (shr-generic cont))
929 (shr-ensure-paragraph))
a41c2e6d
G
930
931(defun shr-tag-li (cont)
f7aa248a
G
932 (shr-ensure-paragraph)
933 (shr-indent)
8028ed5c
LMI
934 (let* ((bullet
935 (if (numberp shr-list-mode)
936 (prog1
937 (format "%d " shr-list-mode)
938 (setq shr-list-mode (1+ shr-list-mode)))
939 "* "))
940 (shr-indentation (+ shr-indentation (length bullet))))
941 (insert bullet)
942 (shr-generic cont)))
a41c2e6d
G
943
944(defun shr-tag-br (cont)
1e463294 945 (unless (bobp)
f7aa248a
G
946 (insert "\n")
947 (shr-indent))
a41c2e6d
G
948 (shr-generic cont))
949
950(defun shr-tag-h1 (cont)
e4dbdb09 951 (shr-heading cont 'bold 'underline))
a41c2e6d
G
952
953(defun shr-tag-h2 (cont)
e4dbdb09 954 (shr-heading cont 'bold))
a41c2e6d
G
955
956(defun shr-tag-h3 (cont)
e4dbdb09 957 (shr-heading cont 'italic))
a41c2e6d
G
958
959(defun shr-tag-h4 (cont)
e4dbdb09 960 (shr-heading cont))
a41c2e6d
G
961
962(defun shr-tag-h5 (cont)
e4dbdb09 963 (shr-heading cont))
a41c2e6d
G
964
965(defun shr-tag-h6 (cont)
e4dbdb09 966 (shr-heading cont))
a41c2e6d 967
3d319c8f
LMI
968(defun shr-tag-hr (cont)
969 (shr-ensure-newline)
6b7df8d3 970 (insert (make-string shr-width shr-hr-line) "\n"))
3d319c8f 971
144b7b5c
G
972(defun shr-tag-title (cont)
973 (shr-heading cont 'bold 'underline))
974
1110d53b 975(defun shr-tag-font (cont)
b31b26b4
G
976 (let* ((start (point))
977 (color (cdr (assq :color cont)))
978 (shr-stylesheet (nconc (list (cons 'color color))
979 shr-stylesheet)))
1110d53b 980 (shr-generic cont)
b31b26b4
G
981 (when color
982 (shr-colorize-region start (point) color
983 (cdr (assq 'background-color shr-stylesheet))))))
1110d53b 984
66627fa9 985;;; Table rendering algorithm.
a41c2e6d 986
a0ec382a
LMI
987;; Table rendering is the only complicated thing here. We do this by
988;; first counting how many TDs there are in each TR, and registering
989;; how wide they think they should be ("width=45%", etc). Then we
990;; render each TD separately (this is done in temporary buffers, so
991;; that we can use all the rendering machinery as if we were in the
992;; main buffer). Now we know how much space each TD really takes, so
993;; we then render everything again with the new widths, and finally
994;; insert all these boxes into the main buffer.
6c769311 995(defun shr-tag-table-1 (cont)
71e691a5
G
996 (setq cont (or (cdr (assq 'tbody cont))
997 cont))
130e977f 998 (let* ((shr-inhibit-images t)
99e65b2d 999 (shr-table-depth (1+ shr-table-depth))
83ffd571 1000 (shr-kinsoku-shorten t)
a0ec382a 1001 ;; Find all suggested widths.
130e977f 1002 (columns (shr-column-specs cont))
a0ec382a 1003 ;; Compute how many characters wide each TD should be.
71e691a5 1004 (suggested-widths (shr-pro-rate-columns columns))
a0ec382a
LMI
1005 ;; Do a "test rendering" to see how big each TD is (this can
1006 ;; be smaller (if there's little text) or bigger (if there's
1007 ;; unbreakable text).
71e691a5 1008 (sketch (shr-make-table cont suggested-widths))
a7dcc87b 1009 (sketch-widths (shr-table-widths sketch suggested-widths)))
030158f3 1010 ;; This probably won't work very well.
83ffd571
KY
1011 (when (> (+ (loop for width across sketch-widths
1012 summing (1+ width))
1013 shr-indentation 1)
030158f3
G
1014 (frame-width))
1015 (setq truncate-lines t))
a0ec382a 1016 ;; Then render the table again with these new "hard" widths.
130e977f 1017 (shr-insert-table (shr-make-table cont sketch-widths t) sketch-widths))
a0ec382a
LMI
1018 ;; Finally, insert all the images after the table. The Emacs buffer
1019 ;; model isn't strong enough to allow us to put the images actually
1020 ;; into the tables.
99e65b2d
G
1021 (when (zerop shr-table-depth)
1022 (dolist (elem (shr-find-elements cont 'img))
1023 (shr-tag-img (cdr elem)))))
130e977f 1024
6c769311
KY
1025(defun shr-tag-table (cont)
1026 (shr-ensure-paragraph)
1027 (let* ((caption (cdr (assq 'caption cont)))
1028 (header (cdr (assq 'thead cont)))
1029 (body (or (cdr (assq 'tbody cont)) cont))
1030 (footer (cdr (assq 'tfoot cont)))
144b7b5c 1031 (bgcolor (cdr (assq :bgcolor cont)))
60568d74
LMI
1032 (start (point))
1033 (shr-stylesheet (nconc (list (cons 'background-color bgcolor))
1034 shr-stylesheet))
6c769311
KY
1035 (nheader (if header (shr-max-columns header)))
1036 (nbody (if body (shr-max-columns body)))
1037 (nfooter (if footer (shr-max-columns footer))))
1038 (shr-tag-table-1
3c066373
KY
1039 (nconc
1040 (if caption `((tr (td ,@caption))))
1041 (if header
1042 (if footer
1043 ;; hader + body + footer
1044 (if (= nheader nbody)
1045 (if (= nbody nfooter)
1046 `((tr (td (table (tbody ,@header ,@body ,@footer)))))
1047 (nconc `((tr (td (table (tbody ,@header ,@body)))))
1048 (if (= nfooter 1)
1049 footer
1050 `((tr (td (table (tbody ,@footer))))))))
1051 (nconc `((tr (td (table (tbody ,@header)))))
1052 (if (= nbody nfooter)
1053 `((tr (td (table (tbody ,@body ,@footer)))))
1054 (nconc `((tr (td (table (tbody ,@body)))))
1055 (if (= nfooter 1)
1056 footer
1057 `((tr (td (table (tbody ,@footer))))))))))
1058 ;; header + body
1059 (if (= nheader nbody)
1060 `((tr (td (table (tbody ,@header ,@body)))))
1061 (if (= nheader 1)
1062 `(,@header (tr (td (table (tbody ,@body)))))
1063 `((tr (td (table (tbody ,@header))))
1064 (tr (td (table (tbody ,@body))))))))
1065 (if footer
1066 ;; body + footer
1067 (if (= nbody nfooter)
1068 `((tr (td (table (tbody ,@body ,@footer)))))
1069 (nconc `((tr (td (table (tbody ,@body)))))
2526f423 1070 (if (= nfooter 1)
3c066373
KY
1071 footer
1072 `((tr (td (table (tbody ,@footer))))))))
1073 (if caption
1074 `((tr (td (table (tbody ,@body)))))
60568d74
LMI
1075 body)))))
1076 (when bgcolor
1077 (shr-colorize-region start (point) (cdr (assq 'color shr-stylesheet))
1078 bgcolor))))
6c769311 1079
130e977f
LMI
1080(defun shr-find-elements (cont type)
1081 (let (result)
1082 (dolist (elem cont)
1083 (cond ((eq (car elem) type)
1084 (push elem result))
1085 ((consp (cdr elem))
1086 (setq result (nconc (shr-find-elements (cdr elem) type) result)))))
1087 (nreverse result)))
71e691a5
G
1088
1089(defun shr-insert-table (table widths)
1090 (shr-insert-table-ruler widths)
1091 (dolist (row table)
1092 (let ((start (point))
1093 (height (let ((max 0))
1094 (dolist (column row)
1095 (setq max (max max (cadr column))))
1096 max)))
1097 (dotimes (i height)
1098 (shr-indent)
d3098750 1099 (insert shr-table-vertical-line "\n"))
71e691a5
G
1100 (dolist (column row)
1101 (goto-char start)
a7dcc87b 1102 (let ((lines (nth 2 column))
130e977f
LMI
1103 (overlay-lines (nth 3 column))
1104 overlay overlay-line)
71e691a5 1105 (dolist (line lines)
130e977f 1106 (setq overlay-line (pop overlay-lines))
3d319c8f 1107 (end-of-line)
d3098750 1108 (insert line shr-table-vertical-line)
3d319c8f
LMI
1109 (dolist (overlay overlay-line)
1110 (let ((o (make-overlay (- (point) (nth 0 overlay) 1)
1111 (- (point) (nth 1 overlay) 1)))
1112 (properties (nth 2 overlay)))
1113 (while properties
1114 (overlay-put o (pop properties) (pop properties)))))
1115 (forward-line 1))
71e691a5
G
1116 ;; Add blank lines at padding at the bottom of the TD,
1117 ;; possibly.
1118 (dotimes (i (- height (length lines)))
1119 (end-of-line)
abb97fbb
LI
1120 (let ((start (point)))
1121 (insert (make-string (string-width (car lines)) ? )
1122 shr-table-vertical-line)
1123 (when (nth 4 column)
1124 (shr-put-color start (1- (point)) :background (nth 4 column))))
71e691a5
G
1125 (forward-line 1)))))
1126 (shr-insert-table-ruler widths)))
1127
1128(defun shr-insert-table-ruler (widths)
83ffd571
KY
1129 (when (and (bolp)
1130 (> shr-indentation 0))
1131 (shr-indent))
afba0c4b 1132 (insert shr-table-corner)
71e691a5 1133 (dotimes (i (length widths))
d3098750
LMI
1134 (insert (make-string (aref widths i) shr-table-horizontal-line)
1135 shr-table-corner))
71e691a5
G
1136 (insert "\n"))
1137
a7dcc87b
G
1138(defun shr-table-widths (table suggested-widths)
1139 (let* ((length (length suggested-widths))
1140 (widths (make-vector length 0))
1141 (natural-widths (make-vector length 0)))
71e691a5
G
1142 (dolist (row table)
1143 (let ((i 0))
1144 (dolist (column row)
1145 (aset widths i (max (aref widths i)
1146 (car column)))
a7dcc87b
G
1147 (aset natural-widths i (max (aref natural-widths i)
1148 (cadr column)))
1149 (setq i (1+ i)))))
863b61d6
KY
1150 (let ((extra (- (apply '+ (append suggested-widths nil))
1151 (apply '+ (append widths nil))))
a7dcc87b
G
1152 (expanded-columns 0))
1153 (when (> extra 0)
1154 (dotimes (i length)
1155 ;; If the natural width is wider than the rendered width, we
1156 ;; want to allow the column to expand.
1157 (when (> (aref natural-widths i) (aref widths i))
1158 (setq expanded-columns (1+ expanded-columns))))
1159 (dotimes (i length)
1160 (when (> (aref natural-widths i) (aref widths i))
1161 (aset widths i (min
1162 (1+ (aref natural-widths i))
1163 (+ (/ extra expanded-columns)
1164 (aref widths i))))))))
71e691a5
G
1165 widths))
1166
1167(defun shr-make-table (cont widths &optional fill)
1168 (let ((trs nil))
1169 (dolist (row cont)
1170 (when (eq (car row) 'tr)
a0ec382a
LMI
1171 (let ((tds nil)
1172 (columns (cdr row))
1173 (i 0)
1174 column)
1175 (while (< i (length widths))
1176 (setq column (pop columns))
1177 (when (or (memq (car column) '(td th))
1178 (null column))
71e691a5
G
1179 (push (shr-render-td (cdr column) (aref widths i) fill)
1180 tds)
1181 (setq i (1+ i))))
1182 (push (nreverse tds) trs))))
1183 (nreverse trs)))
1184
1185(defun shr-render-td (cont width fill)
04db63bc 1186 (with-temp-buffer
60568d74
LMI
1187 (let ((bgcolor (cdr (assq :bgcolor cont)))
1188 (fgcolor (cdr (assq :fgcolor cont)))
1189 (style (cdr (assq :style cont)))
1190 (shr-stylesheet shr-stylesheet)
ec72bf63 1191 overlays actual-colors)
60568d74
LMI
1192 (when style
1193 (setq style (and (string-match "color" style)
1194 (shr-parse-style style))))
1195 (when bgcolor
1196 (setq style (nconc (list (cons 'background-color bgcolor)) style)))
1197 (when fgcolor
1198 (setq style (nconc (list (cons 'color fgcolor)) style)))
1199 (when style
1200 (setq shr-stylesheet (append style shr-stylesheet)))
1201 (let ((cache (cdr (assoc (cons width cont) shr-content-cache))))
1202 (if cache
1203 (progn
1204 (insert (car cache))
1205 (let ((end (length (car cache))))
1206 (dolist (overlay (cadr cache))
1207 (let ((new-overlay
1208 (make-overlay (1+ (- end (nth 0 overlay)))
1209 (1+ (- end (nth 1 overlay)))))
1210 (properties (nth 2 overlay)))
1211 (while properties
1212 (overlay-put new-overlay
1213 (pop properties) (pop properties)))))))
1214 (let ((shr-width width)
1215 (shr-indentation 0))
1216 (shr-descend (cons 'td cont)))
1217 (delete-region
1218 (point)
1219 (+ (point)
1220 (skip-chars-backward " \t\n")))
1221 (push (list (cons width cont) (buffer-string)
1222 (shr-overlays-in-region (point-min) (point-max)))
1223 shr-content-cache)))
1224 (goto-char (point-min))
1225 (let ((max 0))
1226 (while (not (eobp))
1227 (end-of-line)
1228 (setq max (max max (current-column)))
1229 (forward-line 1))
1230 (when fill
1231 (goto-char (point-min))
1232 ;; If the buffer is totally empty, then put a single blank
1233 ;; line here.
1234 (if (zerop (buffer-size))
1235 (insert (make-string width ? ))
1236 ;; Otherwise, fill the buffer.
1237 (while (not (eobp))
1238 (end-of-line)
1239 (when (> (- width (current-column)) 0)
1240 (insert (make-string (- width (current-column)) ? )))
abb97fbb
LI
1241 (forward-line 1)))
1242 (when style
ec72bf63
G
1243 (setq actual-colors
1244 (shr-colorize-region
1245 (point-min) (point-max)
1246 (cdr (assq 'color shr-stylesheet))
1247 (cdr (assq 'background-color shr-stylesheet))))))
60568d74
LMI
1248 (if fill
1249 (list max
1250 (count-lines (point-min) (point-max))
1251 (split-string (buffer-string) "\n")
abb97fbb 1252 (shr-collect-overlays)
ec72bf63 1253 (car actual-colors))
04db63bc 1254 (list max
60568d74 1255 (shr-natural-width)))))))
a7dcc87b
G
1256
1257(defun shr-natural-width ()
1258 (goto-char (point-min))
1259 (let ((current 0)
1260 (max 0))
1261 (while (not (eobp))
1262 (end-of-line)
1263 (setq current (+ current (current-column)))
1264 (unless (get-text-property (point) 'shr-break)
1265 (setq max (max max current)
1266 current 0))
1267 (forward-line 1))
1268 max))
130e977f
LMI
1269
1270(defun shr-collect-overlays ()
1271 (save-excursion
1272 (goto-char (point-min))
1273 (let ((overlays nil))
1274 (while (not (eobp))
1275 (push (shr-overlays-in-region (point) (line-end-position))
1276 overlays)
1277 (forward-line 1))
1278 (nreverse overlays))))
1279
1280(defun shr-overlays-in-region (start end)
1281 (let (result)
1282 (dolist (overlay (overlays-in start end))
1283 (push (list (if (> start (overlay-start overlay))
1284 (- end start)
1285 (- end (overlay-start overlay)))
1286 (if (< end (overlay-end overlay))
1287 0
1288 (- end (overlay-end overlay)))
1289 (overlay-properties overlay))
1290 result))
1291 (nreverse result)))
71e691a5
G
1292
1293(defun shr-pro-rate-columns (columns)
1294 (let ((total-percentage 0)
1295 (widths (make-vector (length columns) 0)))
1296 (dotimes (i (length columns))
a7dcc87b 1297 (setq total-percentage (+ total-percentage (aref columns i))))
71e691a5
G
1298 (setq total-percentage (/ 1.0 total-percentage))
1299 (dotimes (i (length columns))
1300 (aset widths i (max (truncate (* (aref columns i)
1301 total-percentage
a7dcc87b 1302 (- shr-width (1+ (length columns)))))
71e691a5
G
1303 10)))
1304 widths))
1305
1306;; Return a summary of the number and shape of the TDs in the table.
1307(defun shr-column-specs (cont)
1308 (let ((columns (make-vector (shr-max-columns cont) 1)))
1309 (dolist (row cont)
1310 (when (eq (car row) 'tr)
1311 (let ((i 0))
1312 (dolist (column (cdr row))
1313 (when (memq (car column) '(td th))
1314 (let ((width (cdr (assq :width (cdr column)))))
1315 (when (and width
1316 (string-match "\\([0-9]+\\)%" width))
1317 (aset columns i
1318 (/ (string-to-number (match-string 1 width))
130e977f
LMI
1319 100.0))))
1320 (setq i (1+ i)))))))
71e691a5
G
1321 columns))
1322
1323(defun shr-count (cont elem)
1324 (let ((i 0))
1325 (dolist (sub cont)
1326 (when (eq (car sub) elem)
1327 (setq i (1+ i))))
1328 i))
1329
1330(defun shr-max-columns (cont)
1331 (let ((max 0))
1332 (dolist (row cont)
1333 (when (eq (car row) 'tr)
130e977f
LMI
1334 (setq max (max max (+ (shr-count (cdr row) 'td)
1335 (shr-count (cdr row) 'th))))))
71e691a5
G
1336 max))
1337
f3fd95db 1338(provide 'shr)
367f7f81
LMI
1339
1340;;; shr.el ends here