;;; Code:
+(require 'browse-url)
+
(defgroup shr nil
"Simple HTML Renderer"
:group 'mail)
(shr-descend sub)))))
(defun shr-tag-p (cont)
- (unless (bobp)
- (shr-ensure-newline)
- (insert "\n"))
+ (shr-ensure-paragraph)
(shr-generic cont)
- (insert "\n"))
+ (shr-ensure-paragraph))
+
+(defun shr-ensure-paragraph ()
+ (unless (bobp)
+ (if (bolp)
+ (unless (eql (char-after (- (point) 2)) ?\n)
+ (insert "\n"))
+ (if (save-excursion
+ (beginning-of-line)
+ (looking-at " *$"))
+ (insert "\n")
+ (insert "\n\n")))))
(defun shr-tag-b (cont)
(shr-fontize-cont cont 'bold))
(defun shr-tag-u (cont)
(shr-fontize-cont cont 'underline))
-(defun shr-s (cont)
- (shr-fontize-cont cont 'strikethru))
+(defun shr-tag-s (cont)
+ (shr-fontize-cont cont 'strike-through))
-(defun shr-fontize-cont (cont type)
+(defun shr-fontize-cont (cont &rest types)
(let (shr-start)
(shr-generic cont)
- (shr-add-font (or shr-start (point)) (point) type)))
+ (dolist (type types)
+ (shr-add-font (or shr-start (point)) (point) type))))
(defun shr-add-font (start end type)
(let ((overlay (make-overlay start end)))
(browse-url (widget-get widget :url)))
(defun shr-tag-img (cont)
- (when (and (plusp (current-column))
+ (when (and (> (current-column) 0)
(not (eq shr-state 'image)))
(insert "\n"))
(let ((start (point-marker)))
image)))
(defun shr-tag-pre (cont)
- (let ((shr-folding-mode nil))
+ (let ((shr-folding-mode 'none))
(shr-ensure-newline)
(shr-generic cont)
(shr-ensure-newline)))
(defun shr-tag-blockquote (cont)
+ (shr-ensure-paragraph)
(let ((shr-indentation (+ shr-indentation 4)))
- (shr-tag-pre cont)))
+ (shr-generic cont)))
(defun shr-ensure-newline ()
(unless (zerop (current-column))
(setq shr-state nil))
(cond
((eq shr-folding-mode 'none)
- (insert t))
+ (insert text))
(t
- (let (column)
+ (let ((first t)
+ column)
+ (when (and (string-match "\\`[ \t\n]" text)
+ (not (bolp)))
+ (insert " "))
(dolist (elem (split-string text))
(setq column (current-column))
- (when (plusp column)
- (if (> (+ column (length elem) 1) shr-width)
- (insert "\n")
- (insert " ")))
+ (when (> column 0)
+ (cond
+ ((and (or (not first)
+ (eq shr-state 'space))
+ (> (+ column (length elem) 1) shr-width))
+ (insert "\n"))
+ ((not first)
+ (insert " "))))
+ (setq first nil)
(when (and (bolp)
- (plusp shr-indentation))
- (insert (make-string shr-indentation ? )))
+ (> shr-indentation 0))
+ (shr-indent))
;; The shr-start is a special variable that is used to pass
;; upwards the first point in the buffer where the text really
;; starts.
(unless shr-start
(setq shr-start (point)))
- (insert elem))))))
+ (insert elem))
+ (setq shr-state nil)
+ (when (and (string-match "[ \t\n]\\'" text)
+ (not (bolp)))
+ (insert " ")
+ (setq shr-state 'space))))))
+
+(defun shr-indent ()
+ (insert (make-string shr-indentation ? )))
(defun shr-get-image-data (url)
"Get image data for URL.
(defvar shr-list-mode nil)
(defun shr-tag-ul (cont)
+ (shr-ensure-paragraph)
(let ((shr-list-mode 'ul))
(shr-generic cont)))
(defun shr-tag-li (cont)
(shr-ensure-newline)
- (if (numberp shr-list-mode)
- (progn
- (insert (format "%d " shr-list-mode))
- (setq shr-list-mode (1+ shr-list-mode)))
- (insert "* "))
- (shr-generic cont))
+ (let* ((bullet
+ (if (numberp shr-list-mode)
+ (prog1
+ (format "%d " shr-list-mode)
+ (setq shr-list-mode (1+ shr-list-mode)))
+ "* "))
+ (shr-indentation (+ shr-indentation (length bullet))))
+ (insert bullet)
+ (shr-generic cont)))
(defun shr-tag-br (cont)
- (shr-ensure-newline)
+ (unless (bobp)
+ (insert "\n"))
(shr-generic cont))
+(defun shr-tag-h1 (cont)
+ (shr-heading cont 'bold 'underline))
+
+(defun shr-tag-h2 (cont)
+ (shr-heading cont 'bold))
+
+(defun shr-tag-h3 (cont)
+ (shr-heading cont 'italic))
+
+(defun shr-tag-h4 (cont)
+ (shr-heading cont))
+
+(defun shr-tag-h5 (cont)
+ (shr-heading cont))
+
+(defun shr-tag-h6 (cont)
+ (shr-heading cont))
+
+(defun shr-heading (cont &rest types)
+ (shr-ensure-paragraph)
+ (apply #'shr-fontize-cont cont types)
+ (shr-ensure-paragraph))
+
+(defun shr-tag-table (cont)
+ (shr-ensure-paragraph)
+ (setq cont (or (cdr (assq 'tbody cont))
+ cont))
+ (let* ((columns (shr-column-specs cont))
+ (suggested-widths (shr-pro-rate-columns columns))
+ (sketch (shr-make-table cont suggested-widths))
+ (sketch-widths (shr-table-widths sketch (length suggested-widths))))
+ (shr-insert-table (shr-make-table cont sketch-widths t) sketch-widths)))
+
+(defun shr-insert-table (table widths)
+ (shr-insert-table-ruler widths)
+ (dolist (row table)
+ (let ((start (point))
+ (height (let ((max 0))
+ (dolist (column row)
+ (setq max (max max (cadr column))))
+ max)))
+ (dotimes (i height)
+ (shr-indent)
+ (insert "|\n"))
+ (dolist (column row)
+ (goto-char start)
+ (let ((lines (split-string (nth 2 column) "\n")))
+ (dolist (line lines)
+ (when (> (length line) 0)
+ (end-of-line)
+ (insert line "|")
+ (forward-line 1)))
+ ;; Add blank lines at padding at the bottom of the TD,
+ ;; possibly.
+ (dotimes (i (- height (length lines)))
+ (end-of-line)
+ (insert (make-string (length (car lines)) ? ) "|")
+ (forward-line 1)))))
+ (shr-insert-table-ruler widths)))
+
+(defun shr-insert-table-ruler (widths)
+ (shr-indent)
+ (insert "+")
+ (dotimes (i (length widths))
+ (insert (make-string (aref widths i) ?-) ?+))
+ (insert "\n"))
+
+(defun shr-table-widths (table length)
+ (let ((widths (make-vector length 0)))
+ (dolist (row table)
+ (let ((i 0))
+ (dolist (column row)
+ (aset widths i (max (aref widths i)
+ (car column)))
+ (incf i))))
+ widths))
+
+(defun shr-make-table (cont widths &optional fill)
+ (let ((trs nil))
+ (dolist (row cont)
+ (when (eq (car row) 'tr)
+ (let ((i 0)
+ (tds nil))
+ (dolist (column (cdr row))
+ (when (memq (car column) '(td th))
+ (push (shr-render-td (cdr column) (aref widths i) fill)
+ tds)
+ (setq i (1+ i))))
+ (push (nreverse tds) trs))))
+ (nreverse trs)))
+
+(defun shr-render-td (cont width fill)
+ (with-temp-buffer
+ (let ((shr-width width))
+ (shr-generic cont))
+ (while (re-search-backward "\n *$" nil t)
+ (delete-region (match-beginning 0) (match-end 0)))
+ (goto-char (point-min))
+ (let ((max 0))
+ (while (not (eobp))
+ (end-of-line)
+ (setq max (max max (current-column)))
+ (forward-line 1))
+ (when fill
+ (goto-char (point-min))
+ (while (not (eobp))
+ (end-of-line)
+ (insert (make-string (- width (current-column)) ? ))
+ (forward-line 1)))
+ (list max (count-lines (point-min) (point-max)) (buffer-string)))))
+
+(defun shr-pro-rate-columns (columns)
+ (let ((total-percentage 0)
+ (widths (make-vector (length columns) 0)))
+ (dotimes (i (length columns))
+ (incf total-percentage (aref columns i)))
+ (setq total-percentage (/ 1.0 total-percentage))
+ (dotimes (i (length columns))
+ (aset widths i (max (truncate (* (aref columns i)
+ total-percentage
+ shr-width))
+ 10)))
+ widths))
+
+;; Return a summary of the number and shape of the TDs in the table.
+(defun shr-column-specs (cont)
+ (let ((columns (make-vector (shr-max-columns cont) 1)))
+ (dolist (row cont)
+ (when (eq (car row) 'tr)
+ (let ((i 0))
+ (dolist (column (cdr row))
+ (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)))))
+ (setq i (1+ i))))))
+ columns))
+
+(defun shr-count (cont elem)
+ (let ((i 0))
+ (dolist (sub cont)
+ (when (eq (car sub) elem)
+ (setq i (1+ i))))
+ i))
+
+(defun shr-max-columns (cont)
+ (let ((max 0))
+ (dolist (row cont)
+ (when (eq (car row) 'tr)
+ (setq max (max max (shr-count (cdr row) 'td)))))
+ max))
+
(provide 'shr)
;;; shr.el ends here