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