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