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