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