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