Spelling fixes (mostly "inactive" vs "deactivate").
[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
e37df674 56(defcustom shr-table-horizontal-line ?\s
d3098750
LMI
57 "Character used to draw horizontal table lines."
58 :group 'shr
59 :type 'character)
60
e37df674 61(defcustom shr-table-vertical-line ?\s
d3098750 62 "Character used to draw vertical table lines."
afba0c4b 63 :group 'shr
030158f3 64 :type 'character)
afba0c4b 65
e37df674 66(defcustom shr-table-corner ?\s
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,
53964682 586START, and END. Note that START and END should be markers."
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 903 (t
64522086 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
64522086 915 (list (current-buffer) start (set-marker (make-marker) (1- (point))))
f3b146e9 916 t)))
a959fc40
KY
917 (when (zerop shr-table-depth) ;; We are not in a table.
918 (put-text-property start (point) 'keymap shr-map)
919 (put-text-property start (point) 'shr-alt alt)
920 (put-text-property start (point) 'image-url url)
921 (put-text-property start (point) 'image-displayer
922 (shr-image-displayer shr-content-function))
923 (put-text-property start (point) 'help-echo alt))
68f6bd17 924 (setq shr-state 'image)))))
66627fa9
G
925
926(defun shr-tag-pre (cont)
927 (let ((shr-folding-mode 'none))
928 (shr-ensure-newline)
f7aa248a 929 (shr-indent)
66627fa9
G
930 (shr-generic cont)
931 (shr-ensure-newline)))
932
933(defun shr-tag-blockquote (cont)
934 (shr-ensure-paragraph)
f7aa248a 935 (shr-indent)
66627fa9
G
936 (let ((shr-indentation (+ shr-indentation 4)))
937 (shr-generic cont))
938 (shr-ensure-paragraph))
a41c2e6d
G
939
940(defun shr-tag-ul (cont)
941 (shr-ensure-paragraph)
942 (let ((shr-list-mode 'ul))
3d319c8f
LMI
943 (shr-generic cont))
944 (shr-ensure-paragraph))
a41c2e6d
G
945
946(defun shr-tag-ol (cont)
3d319c8f 947 (shr-ensure-paragraph)
a41c2e6d 948 (let ((shr-list-mode 1))
3d319c8f
LMI
949 (shr-generic cont))
950 (shr-ensure-paragraph))
a41c2e6d
G
951
952(defun shr-tag-li (cont)
f7aa248a
G
953 (shr-ensure-paragraph)
954 (shr-indent)
8028ed5c
LMI
955 (let* ((bullet
956 (if (numberp shr-list-mode)
957 (prog1
958 (format "%d " shr-list-mode)
959 (setq shr-list-mode (1+ shr-list-mode)))
960 "* "))
961 (shr-indentation (+ shr-indentation (length bullet))))
962 (insert bullet)
963 (shr-generic cont)))
a41c2e6d
G
964
965(defun shr-tag-br (cont)
1e463294 966 (unless (bobp)
f7aa248a
G
967 (insert "\n")
968 (shr-indent))
a41c2e6d
G
969 (shr-generic cont))
970
971(defun shr-tag-h1 (cont)
e4dbdb09 972 (shr-heading cont 'bold 'underline))
a41c2e6d
G
973
974(defun shr-tag-h2 (cont)
e4dbdb09 975 (shr-heading cont 'bold))
a41c2e6d
G
976
977(defun shr-tag-h3 (cont)
e4dbdb09 978 (shr-heading cont 'italic))
a41c2e6d
G
979
980(defun shr-tag-h4 (cont)
e4dbdb09 981 (shr-heading cont))
a41c2e6d
G
982
983(defun shr-tag-h5 (cont)
e4dbdb09 984 (shr-heading cont))
a41c2e6d
G
985
986(defun shr-tag-h6 (cont)
e4dbdb09 987 (shr-heading cont))
a41c2e6d 988
3d319c8f
LMI
989(defun shr-tag-hr (cont)
990 (shr-ensure-newline)
6b7df8d3 991 (insert (make-string shr-width shr-hr-line) "\n"))
3d319c8f 992
144b7b5c
G
993(defun shr-tag-title (cont)
994 (shr-heading cont 'bold 'underline))
995
1110d53b 996(defun shr-tag-font (cont)
b31b26b4
G
997 (let* ((start (point))
998 (color (cdr (assq :color cont)))
999 (shr-stylesheet (nconc (list (cons 'color color))
1000 shr-stylesheet)))
1110d53b 1001 (shr-generic cont)
b31b26b4
G
1002 (when color
1003 (shr-colorize-region start (point) color
1004 (cdr (assq 'background-color shr-stylesheet))))))
1110d53b 1005
66627fa9 1006;;; Table rendering algorithm.
a41c2e6d 1007
a0ec382a
LMI
1008;; Table rendering is the only complicated thing here. We do this by
1009;; first counting how many TDs there are in each TR, and registering
1010;; how wide they think they should be ("width=45%", etc). Then we
1011;; render each TD separately (this is done in temporary buffers, so
1012;; that we can use all the rendering machinery as if we were in the
1013;; main buffer). Now we know how much space each TD really takes, so
1014;; we then render everything again with the new widths, and finally
1015;; insert all these boxes into the main buffer.
6c769311 1016(defun shr-tag-table-1 (cont)
71e691a5
G
1017 (setq cont (or (cdr (assq 'tbody cont))
1018 cont))
130e977f 1019 (let* ((shr-inhibit-images t)
99e65b2d 1020 (shr-table-depth (1+ shr-table-depth))
83ffd571 1021 (shr-kinsoku-shorten t)
a0ec382a 1022 ;; Find all suggested widths.
130e977f 1023 (columns (shr-column-specs cont))
a0ec382a 1024 ;; Compute how many characters wide each TD should be.
71e691a5 1025 (suggested-widths (shr-pro-rate-columns columns))
a0ec382a
LMI
1026 ;; Do a "test rendering" to see how big each TD is (this can
1027 ;; be smaller (if there's little text) or bigger (if there's
1028 ;; unbreakable text).
71e691a5 1029 (sketch (shr-make-table cont suggested-widths))
a7dcc87b 1030 (sketch-widths (shr-table-widths sketch suggested-widths)))
030158f3 1031 ;; This probably won't work very well.
83ffd571
KY
1032 (when (> (+ (loop for width across sketch-widths
1033 summing (1+ width))
1034 shr-indentation 1)
030158f3
G
1035 (frame-width))
1036 (setq truncate-lines t))
a0ec382a 1037 ;; Then render the table again with these new "hard" widths.
130e977f 1038 (shr-insert-table (shr-make-table cont sketch-widths t) sketch-widths))
a0ec382a
LMI
1039 ;; Finally, insert all the images after the table. The Emacs buffer
1040 ;; model isn't strong enough to allow us to put the images actually
1041 ;; into the tables.
99e65b2d
G
1042 (when (zerop shr-table-depth)
1043 (dolist (elem (shr-find-elements cont 'img))
1044 (shr-tag-img (cdr elem)))))
130e977f 1045
6c769311
KY
1046(defun shr-tag-table (cont)
1047 (shr-ensure-paragraph)
1048 (let* ((caption (cdr (assq 'caption cont)))
1049 (header (cdr (assq 'thead cont)))
1050 (body (or (cdr (assq 'tbody cont)) cont))
1051 (footer (cdr (assq 'tfoot cont)))
144b7b5c 1052 (bgcolor (cdr (assq :bgcolor cont)))
60568d74
LMI
1053 (start (point))
1054 (shr-stylesheet (nconc (list (cons 'background-color bgcolor))
1055 shr-stylesheet))
6c769311
KY
1056 (nheader (if header (shr-max-columns header)))
1057 (nbody (if body (shr-max-columns body)))
1058 (nfooter (if footer (shr-max-columns footer))))
2146e256
LMI
1059 (if (and (not caption)
1060 (not header)
1061 (not (cdr (assq 'tbody cont)))
1062 (not (cdr (assq 'tr cont)))
1063 (not footer))
1064 ;; The table is totally invalid and just contains random junk.
1065 ;; Try to output it anyway.
1066 (shr-generic cont)
1067 ;; It's a real table, so render it.
1068 (shr-tag-table-1
1069 (nconc
1070 (if caption `((tr (td ,@caption))))
1071 (if header
1072 (if footer
1073 ;; hader + body + footer
1074 (if (= nheader nbody)
1075 (if (= nbody nfooter)
1076 `((tr (td (table (tbody ,@header ,@body ,@footer)))))
1077 (nconc `((tr (td (table (tbody ,@header ,@body)))))
1078 (if (= nfooter 1)
1079 footer
1080 `((tr (td (table (tbody ,@footer))))))))
1081 (nconc `((tr (td (table (tbody ,@header)))))
1082 (if (= nbody nfooter)
1083 `((tr (td (table (tbody ,@body ,@footer)))))
1084 (nconc `((tr (td (table (tbody ,@body)))))
1085 (if (= nfooter 1)
1086 footer
1087 `((tr (td (table (tbody ,@footer))))))))))
1088 ;; header + body
3c066373 1089 (if (= nheader nbody)
2146e256
LMI
1090 `((tr (td (table (tbody ,@header ,@body)))))
1091 (if (= nheader 1)
1092 `(,@header (tr (td (table (tbody ,@body)))))
1093 `((tr (td (table (tbody ,@header))))
1094 (tr (td (table (tbody ,@body))))))))
1095 (if footer
1096 ;; body + footer
1097 (if (= nbody nfooter)
1098 `((tr (td (table (tbody ,@body ,@footer)))))
1099 (nconc `((tr (td (table (tbody ,@body)))))
1100 (if (= nfooter 1)
1101 footer
1102 `((tr (td (table (tbody ,@footer))))))))
1103 (if caption
1104 `((tr (td (table (tbody ,@body)))))
1105 body))))))
60568d74
LMI
1106 (when bgcolor
1107 (shr-colorize-region start (point) (cdr (assq 'color shr-stylesheet))
1108 bgcolor))))
6c769311 1109
130e977f
LMI
1110(defun shr-find-elements (cont type)
1111 (let (result)
1112 (dolist (elem cont)
1113 (cond ((eq (car elem) type)
1114 (push elem result))
1115 ((consp (cdr elem))
1116 (setq result (nconc (shr-find-elements (cdr elem) type) result)))))
1117 (nreverse result)))
71e691a5
G
1118
1119(defun shr-insert-table (table widths)
1120 (shr-insert-table-ruler widths)
1121 (dolist (row table)
1122 (let ((start (point))
1123 (height (let ((max 0))
1124 (dolist (column row)
1125 (setq max (max max (cadr column))))
1126 max)))
1127 (dotimes (i height)
1128 (shr-indent)
d3098750 1129 (insert shr-table-vertical-line "\n"))
71e691a5
G
1130 (dolist (column row)
1131 (goto-char start)
a7dcc87b 1132 (let ((lines (nth 2 column))
130e977f
LMI
1133 (overlay-lines (nth 3 column))
1134 overlay overlay-line)
71e691a5 1135 (dolist (line lines)
130e977f 1136 (setq overlay-line (pop overlay-lines))
3d319c8f 1137 (end-of-line)
d3098750 1138 (insert line shr-table-vertical-line)
3d319c8f
LMI
1139 (dolist (overlay overlay-line)
1140 (let ((o (make-overlay (- (point) (nth 0 overlay) 1)
1141 (- (point) (nth 1 overlay) 1)))
1142 (properties (nth 2 overlay)))
1143 (while properties
1144 (overlay-put o (pop properties) (pop properties)))))
1145 (forward-line 1))
71e691a5
G
1146 ;; Add blank lines at padding at the bottom of the TD,
1147 ;; possibly.
1148 (dotimes (i (- height (length lines)))
1149 (end-of-line)
abb97fbb
LI
1150 (let ((start (point)))
1151 (insert (make-string (string-width (car lines)) ? )
1152 shr-table-vertical-line)
1153 (when (nth 4 column)
1154 (shr-put-color start (1- (point)) :background (nth 4 column))))
71e691a5
G
1155 (forward-line 1)))))
1156 (shr-insert-table-ruler widths)))
1157
1158(defun shr-insert-table-ruler (widths)
83ffd571
KY
1159 (when (and (bolp)
1160 (> shr-indentation 0))
1161 (shr-indent))
afba0c4b 1162 (insert shr-table-corner)
71e691a5 1163 (dotimes (i (length widths))
d3098750
LMI
1164 (insert (make-string (aref widths i) shr-table-horizontal-line)
1165 shr-table-corner))
71e691a5
G
1166 (insert "\n"))
1167
a7dcc87b
G
1168(defun shr-table-widths (table suggested-widths)
1169 (let* ((length (length suggested-widths))
1170 (widths (make-vector length 0))
1171 (natural-widths (make-vector length 0)))
71e691a5
G
1172 (dolist (row table)
1173 (let ((i 0))
1174 (dolist (column row)
1175 (aset widths i (max (aref widths i)
1176 (car column)))
a7dcc87b
G
1177 (aset natural-widths i (max (aref natural-widths i)
1178 (cadr column)))
1179 (setq i (1+ i)))))
863b61d6
KY
1180 (let ((extra (- (apply '+ (append suggested-widths nil))
1181 (apply '+ (append widths nil))))
a7dcc87b
G
1182 (expanded-columns 0))
1183 (when (> extra 0)
1184 (dotimes (i length)
1185 ;; If the natural width is wider than the rendered width, we
1186 ;; want to allow the column to expand.
1187 (when (> (aref natural-widths i) (aref widths i))
1188 (setq expanded-columns (1+ expanded-columns))))
1189 (dotimes (i length)
1190 (when (> (aref natural-widths i) (aref widths i))
1191 (aset widths i (min
1192 (1+ (aref natural-widths i))
1193 (+ (/ extra expanded-columns)
1194 (aref widths i))))))))
71e691a5
G
1195 widths))
1196
1197(defun shr-make-table (cont widths &optional fill)
1198 (let ((trs nil))
1199 (dolist (row cont)
1200 (when (eq (car row) 'tr)
a0ec382a
LMI
1201 (let ((tds nil)
1202 (columns (cdr row))
1203 (i 0)
1204 column)
1205 (while (< i (length widths))
1206 (setq column (pop columns))
1207 (when (or (memq (car column) '(td th))
1208 (null column))
71e691a5
G
1209 (push (shr-render-td (cdr column) (aref widths i) fill)
1210 tds)
1211 (setq i (1+ i))))
1212 (push (nreverse tds) trs))))
1213 (nreverse trs)))
1214
1215(defun shr-render-td (cont width fill)
04db63bc 1216 (with-temp-buffer
60568d74
LMI
1217 (let ((bgcolor (cdr (assq :bgcolor cont)))
1218 (fgcolor (cdr (assq :fgcolor cont)))
1219 (style (cdr (assq :style cont)))
1220 (shr-stylesheet shr-stylesheet)
ec72bf63 1221 overlays actual-colors)
60568d74
LMI
1222 (when style
1223 (setq style (and (string-match "color" style)
1224 (shr-parse-style style))))
1225 (when bgcolor
1226 (setq style (nconc (list (cons 'background-color bgcolor)) style)))
1227 (when fgcolor
1228 (setq style (nconc (list (cons 'color fgcolor)) style)))
1229 (when style
1230 (setq shr-stylesheet (append style shr-stylesheet)))
1231 (let ((cache (cdr (assoc (cons width cont) shr-content-cache))))
1232 (if cache
1233 (progn
1234 (insert (car cache))
1235 (let ((end (length (car cache))))
1236 (dolist (overlay (cadr cache))
1237 (let ((new-overlay
1238 (make-overlay (1+ (- end (nth 0 overlay)))
1239 (1+ (- end (nth 1 overlay)))))
1240 (properties (nth 2 overlay)))
1241 (while properties
1242 (overlay-put new-overlay
1243 (pop properties) (pop properties)))))))
1244 (let ((shr-width width)
1245 (shr-indentation 0))
1246 (shr-descend (cons 'td cont)))
1247 (delete-region
1248 (point)
1249 (+ (point)
1250 (skip-chars-backward " \t\n")))
1251 (push (list (cons width cont) (buffer-string)
1252 (shr-overlays-in-region (point-min) (point-max)))
1253 shr-content-cache)))
1254 (goto-char (point-min))
1255 (let ((max 0))
1256 (while (not (eobp))
1257 (end-of-line)
1258 (setq max (max max (current-column)))
1259 (forward-line 1))
1260 (when fill
1261 (goto-char (point-min))
1262 ;; If the buffer is totally empty, then put a single blank
1263 ;; line here.
1264 (if (zerop (buffer-size))
1265 (insert (make-string width ? ))
1266 ;; Otherwise, fill the buffer.
1267 (while (not (eobp))
1268 (end-of-line)
1269 (when (> (- width (current-column)) 0)
1270 (insert (make-string (- width (current-column)) ? )))
abb97fbb
LI
1271 (forward-line 1)))
1272 (when style
ec72bf63
G
1273 (setq actual-colors
1274 (shr-colorize-region
1275 (point-min) (point-max)
1276 (cdr (assq 'color shr-stylesheet))
1277 (cdr (assq 'background-color shr-stylesheet))))))
60568d74
LMI
1278 (if fill
1279 (list max
1280 (count-lines (point-min) (point-max))
1281 (split-string (buffer-string) "\n")
abb97fbb 1282 (shr-collect-overlays)
ec72bf63 1283 (car actual-colors))
04db63bc 1284 (list max
60568d74 1285 (shr-natural-width)))))))
a7dcc87b
G
1286
1287(defun shr-natural-width ()
1288 (goto-char (point-min))
1289 (let ((current 0)
1290 (max 0))
1291 (while (not (eobp))
1292 (end-of-line)
1293 (setq current (+ current (current-column)))
1294 (unless (get-text-property (point) 'shr-break)
1295 (setq max (max max current)
1296 current 0))
1297 (forward-line 1))
1298 max))
130e977f
LMI
1299
1300(defun shr-collect-overlays ()
1301 (save-excursion
1302 (goto-char (point-min))
1303 (let ((overlays nil))
1304 (while (not (eobp))
1305 (push (shr-overlays-in-region (point) (line-end-position))
1306 overlays)
1307 (forward-line 1))
1308 (nreverse overlays))))
1309
1310(defun shr-overlays-in-region (start end)
1311 (let (result)
1312 (dolist (overlay (overlays-in start end))
1313 (push (list (if (> start (overlay-start overlay))
1314 (- end start)
1315 (- end (overlay-start overlay)))
1316 (if (< end (overlay-end overlay))
1317 0
1318 (- end (overlay-end overlay)))
1319 (overlay-properties overlay))
1320 result))
1321 (nreverse result)))
71e691a5
G
1322
1323(defun shr-pro-rate-columns (columns)
1324 (let ((total-percentage 0)
1325 (widths (make-vector (length columns) 0)))
1326 (dotimes (i (length columns))
a7dcc87b 1327 (setq total-percentage (+ total-percentage (aref columns i))))
71e691a5
G
1328 (setq total-percentage (/ 1.0 total-percentage))
1329 (dotimes (i (length columns))
1330 (aset widths i (max (truncate (* (aref columns i)
1331 total-percentage
a7dcc87b 1332 (- shr-width (1+ (length columns)))))
71e691a5
G
1333 10)))
1334 widths))
1335
1336;; Return a summary of the number and shape of the TDs in the table.
1337(defun shr-column-specs (cont)
1338 (let ((columns (make-vector (shr-max-columns cont) 1)))
1339 (dolist (row cont)
1340 (when (eq (car row) 'tr)
1341 (let ((i 0))
1342 (dolist (column (cdr row))
1343 (when (memq (car column) '(td th))
1344 (let ((width (cdr (assq :width (cdr column)))))
1345 (when (and width
1346 (string-match "\\([0-9]+\\)%" width))
1347 (aset columns i
1348 (/ (string-to-number (match-string 1 width))
130e977f
LMI
1349 100.0))))
1350 (setq i (1+ i)))))))
71e691a5
G
1351 columns))
1352
1353(defun shr-count (cont elem)
1354 (let ((i 0))
1355 (dolist (sub cont)
1356 (when (eq (car sub) elem)
1357 (setq i (1+ i))))
1358 i))
1359
1360(defun shr-max-columns (cont)
1361 (let ((max 0))
1362 (dolist (row cont)
1363 (when (eq (car row) 'tr)
130e977f
LMI
1364 (setq max (max max (+ (shr-count (cdr row) 'td)
1365 (shr-count (cdr row) 'th))))))
71e691a5
G
1366 max))
1367
f3fd95db 1368(provide 'shr)
367f7f81
LMI
1369
1370;;; shr.el ends here