(defgroup shr nil
"Simple HTML Renderer"
+ :version "24.1"
:group 'mail)
(defcustom shr-max-image-proportion 0.9
;; Public functions and commands.
-(defun shr-visit-file (file)
- "Parse FILE as an HTML document, and render it in a new buffer."
- (interactive "fHTML file name: ")
+(defun shr-render-buffer (buffer)
+ "Display the HTML rendering of the current buffer."
+ (interactive (list (current-buffer)))
(pop-to-buffer "*html*")
(erase-buffer)
(shr-insert-document
- (with-temp-buffer
- (insert-file-contents file)
+ (with-current-buffer buffer
(libxml-parse-html-region (point-min) (point-max))))
(goto-char (point-min)))
+(defun shr-visit-file (file)
+ "Parse FILE as an HTML document, and render it in a new buffer."
+ (interactive "fHTML file name: ")
+ (with-temp-buffer
+ (insert-file-contents file)
+ (shr-render-buffer (current-buffer))))
+
;;;###autoload
(defun shr-insert-document (dom)
"Render the parsed document DOM into the current buffer.
(goto-char start)
(while (not (eobp))
(end-of-line)
- (when (> (current-column) width)
+ (when (> (shr-previous-newline-padding-width (current-column)) width)
(dolist (overlay (overlays-at (point)))
(when (overlay-get overlay 'before-string)
(overlay-put overlay 'before-string nil))))
(defun shr-insert (text)
(when (and (eq shr-state 'image)
+ (not (bolp))
(not (string-match "\\`[ \t\n]+\\'" text)))
(insert "\n")
(setq shr-state nil))
((eq shr-folding-mode 'none)
(insert text))
(t
- (when (and (string-match "\\`[ \t\n]" text)
+ (when (and (string-match "\\`[ \t\n ]" text)
(not (bolp))
(not (eq (char-after (1- (point))) ? )))
(insert " "))
- (dolist (elem (split-string text))
+ (dolist (elem (split-string text "[ \f\t\n\r\v ]+" t))
(when (and (bolp)
(> shr-indentation 0))
(shr-indent))
(unless shr-start
(setq shr-start (point)))
(insert elem)
+ (setq shr-state nil)
(let (found)
(while (and (> (current-column) shr-width)
(progn
(delete-char -1))
(insert "\n")
(unless found
- (put-text-property (1- (point)) (point) 'shr-break t)
;; No space is needed at the beginning of a line.
(when (eq (following-char) ? )
(delete-char 1)))
(shr-indent))
(end-of-line))
(insert " ")))
- (unless (string-match "[ \t\n]\\'" text)
+ (unless (string-match "[ \t\n ]\\'" text)
(delete-char -1)))))
(defun shr-find-fill-point ()
(shr-char-kinsoku-eol-p (following-char)))))
(goto-char bp)))
((shr-char-kinsoku-eol-p (preceding-char))
- (if (shr-char-kinsoku-eol-p (following-char))
- ;; There are consecutive kinsoku-eol characters.
- (setq failed t)
- (let ((count 4))
- (while
- (progn
- (backward-char 1)
- (and (> (setq count (1- count)) 0)
- (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
- (or (shr-char-kinsoku-eol-p (preceding-char))
- (shr-char-kinsoku-bol-p (following-char)))))))
- (if (setq failed (= (current-column) shr-indentation))
- ;; There's no breakable point that doesn't violate kinsoku,
- ;; so we go to the second best position.
- (if (looking-at "\\(\\c<+\\)\\c<")
- (goto-char (match-end 1))
- (forward-char 1)))))
- (t
- (if (shr-char-kinsoku-bol-p (preceding-char))
- ;; There are consecutive kinsoku-bol characters.
- (setq failed t)
- (let ((count 4))
- (while (and (>= (setq count (1- count)) 0)
+ ;; Find backward the point where kinsoku-eol characters begin.
+ (let ((count 4))
+ (while
+ (progn
+ (backward-char 1)
+ (and (> (setq count (1- count)) 0)
+ (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
+ (or (shr-char-kinsoku-eol-p (preceding-char))
+ (shr-char-kinsoku-bol-p (following-char)))))))
+ (if (setq failed (= (current-column) shr-indentation))
+ ;; There's no breakable point that doesn't violate kinsoku,
+ ;; so we go to the second best position.
+ (if (looking-at "\\(\\c<+\\)\\c<")
+ (goto-char (match-end 1))
+ (forward-char 1))))
+ ((shr-char-kinsoku-bol-p (following-char))
+ ;; Find forward the point where kinsoku-bol characters end.
+ (let ((count 4))
+ (while (progn
+ (forward-char 1)
+ (and (>= (setq count (1- count)) 0)
(shr-char-kinsoku-bol-p (following-char))
- (shr-char-breakable-p (following-char)))
- (forward-char 1))))))
+ (shr-char-breakable-p (following-char))))))))
(when (eq (following-char) ? )
(forward-char 1))))
(not failed)))
(string-match "\\`[a-z]*:" url)
(not shr-base))
url)
+ ((and (string-match "\\`//" url)
+ (string-match "\\`[a-z]*:" shr-base))
+ (concat (match-string 0 shr-base) url))
((and (not (string-match "/\\'" shr-base))
(not (string-match "\\`/" url)))
(concat shr-base "/" url))
(if (save-excursion
(beginning-of-line)
(looking-at " *$"))
- (insert "\n")
+ (delete-region (match-beginning 0) (match-end 0))
(insert "\n\n")))))
(defun shr-indent ()
directory)))))
(defun shr-image-fetched (status buffer start end &optional flags)
- (when (and (buffer-name buffer)
- (not (plist-get status :error)))
- (url-store-in-cache (current-buffer))
- (when (or (search-forward "\n\n" nil t)
- (search-forward "\r\n\r\n" nil t))
- (let ((data (buffer-substring (point) (point-max))))
- (with-current-buffer buffer
- (save-excursion
- (let ((alt (buffer-substring start end))
- (properties (text-properties-at start))
- (inhibit-read-only t))
- (delete-region start end)
- (goto-char start)
- (funcall shr-put-image-function data alt flags)
- (while properties
- (let ((type (pop properties))
- (value (pop properties)))
- (unless (memq type '(display image-size))
- (put-text-property start (point) type value))))))))))
- (kill-buffer (current-buffer)))
+ (let ((image-buffer (current-buffer)))
+ (when (and (buffer-name buffer)
+ (not (plist-get status :error)))
+ (url-store-in-cache image-buffer)
+ (when (or (search-forward "\n\n" nil t)
+ (search-forward "\r\n\r\n" nil t))
+ (let ((data (buffer-substring (point) (point-max))))
+ (with-current-buffer buffer
+ (save-excursion
+ (let ((alt (buffer-substring start end))
+ (properties (text-properties-at start))
+ (inhibit-read-only t))
+ (delete-region start end)
+ (goto-char start)
+ (funcall shr-put-image-function data alt flags)
+ (while properties
+ (let ((type (pop properties))
+ (value (pop properties)))
+ (unless (memq type '(display image-size))
+ (put-text-property start (point) type value))))))))))
+ (kill-buffer image-buffer)))
(defun shr-put-image (data alt &optional flags)
"Put image DATA with a string ALT. Return image."
(forward-line 1)
(setq end (point))
(narrow-to-region start end)
- (let ((width (shr-natural-width))
+ (let ((width (shr-buffer-width))
column)
(goto-char (point-min))
(while (not (eobp))
(shr-generic cont)))
(defun shr-tag-br (cont)
- (unless (bobp)
+ (when (and (not (bobp))
+ ;; Only add a newline if we break the current line, or
+ ;; the previous line isn't a blank line.
+ (or (not (bolp))
+ (and (> (- (point) 2) (point-min))
+ (not (= (char-after (- (point) 2)) ?\n)))))
(insert "\n")
(shr-indent))
(shr-generic cont))
;; be smaller (if there's little text) or bigger (if there's
;; unbreakable text).
(sketch (shr-make-table cont suggested-widths))
- (sketch-widths (shr-table-widths sketch suggested-widths)))
+ ;; Compute the "natural" width by setting each column to 500
+ ;; characters and see how wide they really render.
+ (natural (shr-make-table cont (make-vector (length columns) 500)))
+ (sketch-widths (shr-table-widths sketch natural suggested-widths)))
;; This probably won't work very well.
(when (> (+ (loop for width across sketch-widths
summing (1+ width))
shr-table-corner))
(insert "\n"))
-(defun shr-table-widths (table suggested-widths)
+(defun shr-table-widths (table natural-table suggested-widths)
(let* ((length (length suggested-widths))
(widths (make-vector length 0))
(natural-widths (make-vector length 0)))
(dolist (row table)
(let ((i 0))
(dolist (column row)
- (aset widths i (max (aref widths i)
- (car column)))
- (aset natural-widths i (max (aref natural-widths i)
- (cadr column)))
+ (aset widths i (max (aref widths i) column))
+ (setq i (1+ i)))))
+ (dolist (row natural-table)
+ (let ((i 0))
+ (dolist (column row)
+ (aset natural-widths i (max (aref natural-widths i) column))
(setq i (1+ i)))))
(let ((extra (- (apply '+ (append suggested-widths nil))
(apply '+ (append widths nil))))
(expanded-columns 0))
+ ;; We have extra, unused space, so divide this space amongst the
+ ;; columns.
(when (> extra 0)
+ ;; If the natural width is wider than the rendered width, we
+ ;; want to allow the column to expand.
(dotimes (i length)
- ;; If the natural width is wider than the rendered width, we
- ;; want to allow the column to expand.
(when (> (aref natural-widths i) (aref widths i))
(setq expanded-columns (1+ expanded-columns))))
(dotimes (i length)
(when (> (aref natural-widths i) (aref widths i))
(aset widths i (min
- (1+ (aref natural-widths i))
+ (aref natural-widths i)
(+ (/ extra expanded-columns)
(aref widths i))))))))
widths))
(let ((shr-width width)
(shr-indentation 0))
(shr-descend (cons 'td cont)))
+ ;; Delete padding at the bottom of the TDs.
(delete-region
(point)
- (+ (point)
- (skip-chars-backward " \t\n")))
+ (progn
+ (skip-chars-backward " \t\n")
+ (end-of-line)
+ (point)))
(push (list (cons width cont) (buffer-string)
(shr-overlays-in-region (point-min) (point-max)))
shr-content-cache)))
(split-string (buffer-string) "\n")
(shr-collect-overlays)
(car actual-colors))
- (list max
- (shr-natural-width)))))))
+ max)))))
-(defun shr-natural-width ()
+(defun shr-buffer-width ()
(goto-char (point-min))
- (let ((current 0)
- (max 0))
+ (let ((max 0))
(while (not (eobp))
(end-of-line)
- (setq current (+ current (current-column)))
- (unless (get-text-property (point) 'shr-break)
- (setq max (max max current)
- current 0))
+ (setq max (max max (current-column)))
(forward-line 1))
max))
(when (memq (car column) '(td th))
(let ((width (cdr (assq :width (cdr column)))))
(when (and width
- (string-match "\\([0-9]+\\)%" width))
- (aset columns i
- (/ (string-to-number (match-string 1 width))
- 100.0))))
+ (string-match "\\([0-9]+\\)%" width)
+ (not (zerop (setq width (string-to-number
+ (match-string 1 width))))))
+ (aset columns i (/ width 100.0))))
(setq i (1+ i)))))))
columns))
(provide 'shr)
+;; Local Variables:
+;; coding: iso-8859-1
+;; End:
+
;;; shr.el ends here