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