shr.el 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411
  1. ;;; shr.el --- Simple HTML Renderer -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
  3. ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
  4. ;; Keywords: html
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This package takes a HTML parse tree (as provided by
  18. ;; libxml-parse-html-region) and renders it in the current buffer. It
  19. ;; does not do CSS, JavaScript or anything advanced: It's geared
  20. ;; towards rendering typical short snippets of HTML, like what you'd
  21. ;; find in HTML email and the like.
  22. ;;; Code:
  23. (eval-when-compile (require 'cl-lib))
  24. (eval-when-compile (require 'url)) ;For url-filename's setf handler.
  25. (require 'browse-url)
  26. (eval-when-compile (require 'subr-x))
  27. (require 'dom)
  28. (require 'seq)
  29. (require 'svg)
  30. (require 'image)
  31. (defgroup shr nil
  32. "Simple HTML Renderer"
  33. :version "25.1"
  34. :group 'web)
  35. (defcustom shr-max-image-proportion 0.9
  36. "How big pictures displayed are in relation to the window they're in.
  37. A value of 0.7 means that they are allowed to take up 70% of the
  38. width and height of the window. If they are larger than this,
  39. and Emacs supports it, then the images will be rescaled down to
  40. fit these criteria."
  41. :version "24.1"
  42. :group 'shr
  43. :type 'float)
  44. (defcustom shr-blocked-images nil
  45. "Images that have URLs matching this regexp will be blocked."
  46. :version "24.1"
  47. :group 'shr
  48. :type '(choice (const nil) regexp))
  49. (defcustom shr-use-fonts t
  50. "If non-nil, use proportional fonts for text."
  51. :version "25.1"
  52. :group 'shr
  53. :type 'boolean)
  54. (defcustom shr-use-colors t
  55. "If non-nil, respect color specifications in the HTML."
  56. :version "26.1"
  57. :group 'shr
  58. :type 'boolean)
  59. (defcustom shr-table-horizontal-line nil
  60. "Character used to draw horizontal table lines.
  61. If nil, don't draw horizontal table lines."
  62. :group 'shr
  63. :type '(choice (const nil) character))
  64. (defcustom shr-table-vertical-line ?\s
  65. "Character used to draw vertical table lines."
  66. :group 'shr
  67. :type 'character)
  68. (defcustom shr-table-corner ?\s
  69. "Character used to draw table corners."
  70. :group 'shr
  71. :type 'character)
  72. (defcustom shr-hr-line ?-
  73. "Character used to draw hr lines."
  74. :group 'shr
  75. :type 'character)
  76. (defcustom shr-width nil
  77. "Frame width to use for rendering.
  78. May either be an integer specifying a fixed width in characters,
  79. or nil, meaning that the full width of the window should be used.
  80. If `shr-use-fonts' is set, the mean character width is used to
  81. compute the pixel width, which is used instead."
  82. :version "25.1"
  83. :type '(choice (integer :tag "Fixed width in characters")
  84. (const :tag "Use the width of the window" nil))
  85. :group 'shr)
  86. (defcustom shr-bullet "* "
  87. "Bullet used for unordered lists.
  88. Alternative suggestions are:
  89. - \" \"
  90. - \" \""
  91. :version "24.4"
  92. :type 'string
  93. :group 'shr)
  94. (defcustom shr-external-browser 'browse-url-default-browser
  95. "Function used to launch an external browser."
  96. :version "24.4"
  97. :group 'shr
  98. :type 'function)
  99. (defcustom shr-image-animate t
  100. "Non nil means that images that can be animated will be."
  101. :version "24.4"
  102. :group 'shr
  103. :type 'boolean)
  104. (defvar shr-content-function nil
  105. "If bound, this should be a function that will return the content.
  106. This is used for cid: URLs, and the function is called with the
  107. cid: URL as the argument.")
  108. (defvar shr-put-image-function 'shr-put-image
  109. "Function called to put image and alt string.")
  110. (defface shr-strike-through '((t (:strike-through t)))
  111. "Font for <s> elements."
  112. :group 'shr)
  113. (defface shr-link
  114. '((t (:inherit link)))
  115. "Font for link elements."
  116. :group 'shr)
  117. (defvar shr-inhibit-images nil
  118. "If non-nil, inhibit loading images.")
  119. (defvar shr-external-rendering-functions nil
  120. "Alist of tag/function pairs used to alter how shr renders certain tags.
  121. For instance, eww uses this to alter rendering of title, forms
  122. and other things:
  123. ((title . eww-tag-title)
  124. (form . eww-tag-form)
  125. ...)")
  126. ;;; Internal variables.
  127. (defvar shr-folding-mode nil)
  128. (defvar shr-start nil)
  129. (defvar shr-indentation 0)
  130. (defvar shr-internal-width nil)
  131. (defvar shr-list-mode nil)
  132. (defvar shr-content-cache nil)
  133. (defvar shr-kinsoku-shorten nil)
  134. (defvar shr-table-depth 0)
  135. (defvar shr-stylesheet nil)
  136. (defvar shr-base nil)
  137. (defvar shr-depth 0)
  138. (defvar shr-warning nil)
  139. (defvar shr-ignore-cache nil)
  140. (defvar shr-target-id nil)
  141. (defvar shr-table-separator-length 1)
  142. (defvar shr-table-separator-pixel-width 0)
  143. (defvar shr-table-id nil)
  144. (defvar shr-current-font nil)
  145. (defvar shr-internal-bullet nil)
  146. (defvar shr-map
  147. (let ((map (make-sparse-keymap)))
  148. (define-key map "a" 'shr-show-alt-text)
  149. (define-key map "i" 'shr-browse-image)
  150. (define-key map "z" 'shr-zoom-image)
  151. (define-key map [?\t] 'shr-next-link)
  152. (define-key map [?\M-\t] 'shr-previous-link)
  153. (define-key map [follow-link] 'mouse-face)
  154. (define-key map [mouse-2] 'shr-browse-url)
  155. (define-key map "I" 'shr-insert-image)
  156. (define-key map "w" 'shr-maybe-probe-and-copy-url)
  157. (define-key map "u" 'shr-maybe-probe-and-copy-url)
  158. (define-key map "v" 'shr-browse-url)
  159. (define-key map "O" 'shr-save-contents)
  160. (define-key map "\r" 'shr-browse-url)
  161. map))
  162. (defvar shr-image-map
  163. (let ((map (copy-keymap shr-map)))
  164. (when (boundp 'image-map)
  165. (set-keymap-parent map image-map))
  166. map))
  167. ;; Public functions and commands.
  168. (declare-function libxml-parse-html-region "xml.c"
  169. (start end &optional base-url discard-comments))
  170. (defun shr-render-buffer (buffer)
  171. "Display the HTML rendering of the current buffer."
  172. (interactive (list (current-buffer)))
  173. (or (fboundp 'libxml-parse-html-region)
  174. (error "This function requires Emacs to be compiled with libxml2"))
  175. (pop-to-buffer "*html*")
  176. (erase-buffer)
  177. (shr-insert-document
  178. (with-current-buffer buffer
  179. (libxml-parse-html-region (point-min) (point-max))))
  180. (goto-char (point-min)))
  181. ;;;###autoload
  182. (defun shr-render-region (begin end &optional buffer)
  183. "Display the HTML rendering of the region between BEGIN and END."
  184. (interactive "r")
  185. (unless (fboundp 'libxml-parse-html-region)
  186. (error "This function requires Emacs to be compiled with libxml2"))
  187. (with-current-buffer (or buffer (current-buffer))
  188. (let ((dom (libxml-parse-html-region begin end)))
  189. (delete-region begin end)
  190. (goto-char begin)
  191. (shr-insert-document dom))))
  192. (defun shr--have-one-fringe-p ()
  193. "Return non-nil if we know at least one of the fringes has non-zero width."
  194. (and (fboundp 'fringe-columns)
  195. (or (not (zerop (fringe-columns 'right)))
  196. (not (zerop (fringe-columns 'left))))))
  197. ;;;###autoload
  198. (defun shr-insert-document (dom)
  199. "Render the parsed document DOM into the current buffer.
  200. DOM should be a parse tree as generated by
  201. `libxml-parse-html-region' or similar."
  202. (setq shr-content-cache nil)
  203. (let ((start (point))
  204. (shr-start nil)
  205. (shr-base nil)
  206. (shr-depth 0)
  207. (shr-table-id 0)
  208. (shr-warning nil)
  209. (shr-table-separator-pixel-width (shr-string-pixel-width "-"))
  210. (shr-internal-bullet (cons shr-bullet
  211. (shr-string-pixel-width shr-bullet)))
  212. (shr-internal-width (or (and shr-width
  213. (if (not shr-use-fonts)
  214. shr-width
  215. (* shr-width (frame-char-width))))
  216. ;; We need to adjust the available
  217. ;; width for when the user disables
  218. ;; the fringes, which will cause the
  219. ;; display engine usurp one column for
  220. ;; the continuation glyph.
  221. (if (not shr-use-fonts)
  222. (- (window-body-width) 1
  223. (if (and (null shr-width)
  224. (not (shr--have-one-fringe-p)))
  225. 0
  226. 1))
  227. (- (window-body-width nil t)
  228. (* 2 (frame-char-width))
  229. (if (and (null shr-width)
  230. (not (shr--have-one-fringe-p)))
  231. (* (frame-char-width) 2)
  232. 0)))))
  233. bidi-display-reordering)
  234. ;; If the window was hscrolled for some reason, shr-fill-lines
  235. ;; below will misbehave, because it silently assumes that it
  236. ;; starts with a non-hscrolled window (vertical-motion will move
  237. ;; to a wrong place otherwise).
  238. (set-window-hscroll nil 0)
  239. (shr-descend dom)
  240. (shr-fill-lines start (point))
  241. (shr--remove-blank-lines-at-the-end start (point))
  242. (when shr-warning
  243. (message "%s" shr-warning))))
  244. (defun shr--remove-blank-lines-at-the-end (start end)
  245. (save-restriction
  246. (save-excursion
  247. (narrow-to-region start end)
  248. (goto-char end)
  249. (when (and (re-search-backward "[^ \n]" nil t)
  250. (not (eobp)))
  251. (forward-line 1)
  252. (delete-region (point) (point-max))))))
  253. (defun shr-url-at-point (image-url)
  254. "Return the URL under point as a string.
  255. If IMAGE-URL is non-nil, or there is no link under point, but
  256. there is an image under point then copy the URL of the image
  257. under point instead."
  258. (if image-url
  259. (get-text-property (point) 'image-url)
  260. (or (get-text-property (point) 'shr-url)
  261. (get-text-property (point) 'image-url))))
  262. (defun shr-copy-url (url)
  263. "Copy the URL under point to the kill ring.
  264. If IMAGE-URL (the prefix) is non-nil, or there is no link under
  265. point, but there is an image under point then copy the URL of the
  266. image under point instead."
  267. (interactive (list (shr-url-at-point current-prefix-arg)))
  268. (if (not url)
  269. (message "No URL under point")
  270. (setq url (url-encode-url url))
  271. (kill-new url)
  272. (message "Copied %s" url)))
  273. (defun shr-probe-url (url cont)
  274. "Pass URL's redirect destination to CONT, if it has one.
  275. CONT should be a function of one argument, the redirect
  276. destination URL. If URL is not redirected, then CONT is never
  277. called."
  278. (interactive "P")
  279. (url-retrieve
  280. url (lambda (a)
  281. (pcase a
  282. (`(:redirect ,destination . ,_)
  283. ;; Remove common tracking junk from the URL.
  284. (funcall cont (replace-regexp-in-string
  285. ".utm_.*" "" destination)))))
  286. nil t))
  287. (defun shr-probe-and-copy-url (url)
  288. "Copy the URL under point to the kill ring.
  289. Like `shr-copy-url', but additionally fetch URL and use its
  290. redirection destination if it has one."
  291. (interactive (list (shr-url-at-point current-prefix-arg)))
  292. (if url (shr-probe-url url #'shr-copy-url)
  293. (shr-copy-url url)))
  294. (defun shr-maybe-probe-and-copy-url (url)
  295. "Copy the URL under point to the kill ring.
  296. If the URL is already at the front of the kill ring act like
  297. `shr-probe-and-copy-url', otherwise like `shr-copy-url'."
  298. (interactive (list (shr-url-at-point current-prefix-arg)))
  299. (if (equal url (car kill-ring))
  300. (shr-probe-and-copy-url url)
  301. (shr-copy-url url)))
  302. (defun shr-next-link ()
  303. "Skip to the next link."
  304. (interactive)
  305. (let ((current (get-text-property (point) 'shr-url))
  306. (start (point))
  307. skip)
  308. (while (and (not (eobp))
  309. (equal (get-text-property (point) 'shr-url) current))
  310. (forward-char 1))
  311. (cond
  312. ((and (not (eobp))
  313. (get-text-property (point) 'shr-url))
  314. ;; The next link is adjacent.
  315. (message "%s" (get-text-property (point) 'help-echo)))
  316. ((or (eobp)
  317. (not (setq skip (text-property-not-all (point) (point-max)
  318. 'shr-url nil))))
  319. (goto-char start)
  320. (message "No next link"))
  321. (t
  322. (goto-char skip)
  323. (message "%s" (get-text-property (point) 'help-echo))))))
  324. (defun shr-previous-link ()
  325. "Skip to the previous link."
  326. (interactive)
  327. (let ((start (point))
  328. (found nil))
  329. ;; Skip past the current link.
  330. (while (and (not (bobp))
  331. (get-text-property (point) 'help-echo))
  332. (forward-char -1))
  333. ;; Find the previous link.
  334. (while (and (not (bobp))
  335. (not (setq found (get-text-property (point) 'help-echo))))
  336. (forward-char -1))
  337. (if (not found)
  338. (progn
  339. (message "No previous link")
  340. (goto-char start))
  341. ;; Put point at the start of the link.
  342. (while (and (not (bobp))
  343. (get-text-property (point) 'help-echo))
  344. (forward-char -1))
  345. (forward-char 1)
  346. (message "%s" (get-text-property (point) 'help-echo)))))
  347. (defun shr-show-alt-text ()
  348. "Show the ALT text of the image under point."
  349. (interactive)
  350. (let ((text (get-text-property (point) 'shr-alt)))
  351. (if (not text)
  352. (message "No image under point")
  353. (message "%s" (shr-fill-text text)))))
  354. (defun shr-browse-image (&optional copy-url)
  355. "Browse the image under point.
  356. If COPY-URL (the prefix if called interactively) is non-nil, copy
  357. the URL of the image to the kill buffer instead."
  358. (interactive "P")
  359. (let ((url (get-text-property (point) 'image-url)))
  360. (cond
  361. ((not url)
  362. (message "No image under point"))
  363. (copy-url
  364. (with-temp-buffer
  365. (insert url)
  366. (copy-region-as-kill (point-min) (point-max))
  367. (message "Copied %s" url)))
  368. (t
  369. (message "Browsing %s..." url)
  370. (browse-url url)))))
  371. (defun shr-insert-image ()
  372. "Insert the image under point into the buffer."
  373. (interactive)
  374. (let ((url (get-text-property (point) 'image-url)))
  375. (if (not url)
  376. (message "No image under point")
  377. (message "Inserting %s..." url)
  378. (url-retrieve url 'shr-image-fetched
  379. (list (current-buffer) (1- (point)) (point-marker))
  380. t t))))
  381. (defun shr-zoom-image ()
  382. "Toggle the image size.
  383. The size will be rotated between the default size, the original
  384. size, and full-buffer size."
  385. (interactive)
  386. (let ((url (get-text-property (point) 'image-url))
  387. (size (get-text-property (point) 'image-size))
  388. (buffer-read-only nil))
  389. (if (not url)
  390. (message "No image under point")
  391. ;; Delete the old picture.
  392. (while (get-text-property (point) 'image-url)
  393. (forward-char -1))
  394. (forward-char 1)
  395. (let ((start (point)))
  396. (while (get-text-property (point) 'image-url)
  397. (forward-char 1))
  398. (forward-char -1)
  399. (put-text-property start (point) 'display nil)
  400. (when (> (- (point) start) 2)
  401. (delete-region start (1- (point)))))
  402. (message "Inserting %s..." url)
  403. (url-retrieve url 'shr-image-fetched
  404. (list (current-buffer) (1- (point)) (point-marker)
  405. (list (cons 'size
  406. (cond ((or (eq size 'default)
  407. (null size))
  408. 'original)
  409. ((eq size 'original)
  410. 'full)
  411. ((eq size 'full)
  412. 'default)))))
  413. t))))
  414. ;;; Utility functions.
  415. (defsubst shr-generic (dom)
  416. (dolist (sub (dom-children dom))
  417. (if (stringp sub)
  418. (shr-insert sub)
  419. (shr-descend sub))))
  420. (defun shr-descend (dom)
  421. (let ((function
  422. (intern (concat "shr-tag-" (symbol-name (dom-tag dom))) obarray))
  423. ;; Allow other packages to override (or provide) rendering
  424. ;; of elements.
  425. (external (cdr (assq (dom-tag dom) shr-external-rendering-functions)))
  426. (style (dom-attr dom 'style))
  427. (shr-stylesheet shr-stylesheet)
  428. (shr-depth (1+ shr-depth))
  429. (start (point)))
  430. ;; shr uses many frames per nested node.
  431. (if (> shr-depth (/ max-specpdl-size 15))
  432. (setq shr-warning "Too deeply nested to render properly; consider increasing `max-specpdl-size'")
  433. (when style
  434. (if (string-match "color\\|display\\|border-collapse" style)
  435. (setq shr-stylesheet (nconc (shr-parse-style style)
  436. shr-stylesheet))
  437. (setq style nil)))
  438. ;; If we have a display:none, then just ignore this part of the DOM.
  439. (unless (equal (cdr (assq 'display shr-stylesheet)) "none")
  440. (cond (external
  441. (funcall external dom))
  442. ((fboundp function)
  443. (funcall function dom))
  444. (t
  445. (shr-generic dom)))
  446. (when (and shr-target-id
  447. (equal (dom-attr dom 'id) shr-target-id))
  448. ;; If the element was empty, we don't have anything to put the
  449. ;; anchor on. So just insert a dummy character.
  450. (when (= start (point))
  451. (insert "*"))
  452. (put-text-property start (1+ start) 'shr-target-id shr-target-id))
  453. ;; If style is set, then this node has set the color.
  454. (when style
  455. (shr-colorize-region
  456. start (point)
  457. (cdr (assq 'color shr-stylesheet))
  458. (cdr (assq 'background-color shr-stylesheet))))))))
  459. (defun shr-fill-text (text)
  460. (if (zerop (length text))
  461. text
  462. (with-temp-buffer
  463. (let ((shr-indentation 0)
  464. (shr-start nil)
  465. (shr-internal-width (- (window-body-width nil t)
  466. (* 2 (frame-char-width))
  467. ;; Adjust the window width for when
  468. ;; the user disables the fringes,
  469. ;; which causes the display engine
  470. ;; to usurp one column for the
  471. ;; continuation glyph.
  472. (if (and (null shr-width)
  473. (not (shr--have-one-fringe-p)))
  474. (* (frame-char-width) 2)
  475. 0))))
  476. (shr-insert text)
  477. (shr-fill-lines (point-min) (point-max))
  478. (buffer-string)))))
  479. (define-inline shr-char-breakable-p (char)
  480. "Return non-nil if a line can be broken before and after CHAR."
  481. (inline-quote (aref fill-find-break-point-function-table ,char)))
  482. (define-inline shr-char-nospace-p (char)
  483. "Return non-nil if no space is required before and after CHAR."
  484. (inline-quote (aref fill-nospace-between-words-table ,char)))
  485. ;; KINSOKU is a Japanese word meaning a rule that should not be violated.
  486. ;; In Emacs, it is a term used for characters, e.g. punctuation marks,
  487. ;; parentheses, and so on, that should not be placed in the beginning
  488. ;; of a line or the end of a line.
  489. (define-inline shr-char-kinsoku-bol-p (char)
  490. "Return non-nil if a line ought not to begin with CHAR."
  491. (inline-letevals (char)
  492. (inline-quote (and (not (eq ,char ?'))
  493. (aref (char-category-set ,char) ?>)))))
  494. (define-inline shr-char-kinsoku-eol-p (char)
  495. "Return non-nil if a line ought not to end with CHAR."
  496. (inline-quote (aref (char-category-set ,char) ?<)))
  497. (unless (shr-char-kinsoku-bol-p (make-char 'japanese-jisx0208 33 35))
  498. (load "kinsoku" nil t))
  499. (defun shr-pixel-column ()
  500. (if (not shr-use-fonts)
  501. (current-column)
  502. (if (not (get-buffer-window (current-buffer)))
  503. (save-window-excursion
  504. ;; Avoid errors if the selected window is a dedicated one,
  505. ;; and they just want to insert a document into it.
  506. (set-window-dedicated-p nil nil)
  507. (set-window-buffer nil (current-buffer))
  508. (car (window-text-pixel-size nil (line-beginning-position) (point))))
  509. (car (window-text-pixel-size nil (line-beginning-position) (point))))))
  510. (defun shr-pixel-region ()
  511. (- (shr-pixel-column)
  512. (save-excursion
  513. (goto-char (mark))
  514. (shr-pixel-column))))
  515. (defun shr-string-pixel-width (string)
  516. (if (not shr-use-fonts)
  517. (length string)
  518. (with-temp-buffer
  519. (insert string)
  520. (shr-pixel-column))))
  521. (defsubst shr--translate-insertion-chars ()
  522. ;; Remove soft hyphens.
  523. (goto-char (point-min))
  524. (while (search-forward "­" nil t)
  525. (replace-match "" t t))
  526. ;; Translate non-breaking spaces into real spaces.
  527. (goto-char (point-min))
  528. (while (search-forward " " nil t)
  529. (replace-match " " t t)))
  530. (defun shr-insert (text)
  531. (when (and (not (bolp))
  532. (get-text-property (1- (point)) 'image-url))
  533. (insert "\n"))
  534. (cond
  535. ((eq shr-folding-mode 'none)
  536. (let ((start (point)))
  537. (insert text)
  538. (save-restriction
  539. (narrow-to-region start (point))
  540. (shr--translate-insertion-chars)
  541. (goto-char (point-max)))))
  542. (t
  543. (let ((font-start (point)))
  544. (when (and (string-match "\\`[ \t\n\r]" text)
  545. (not (bolp))
  546. (not (eq (char-after (1- (point))) ? )))
  547. (insert " "))
  548. (let ((start (point))
  549. (bolp (bolp)))
  550. (insert text)
  551. (save-restriction
  552. (narrow-to-region start (point))
  553. (goto-char start)
  554. (when (looking-at "[ \t\n\r]+")
  555. (replace-match "" t t))
  556. (while (re-search-forward "[ \t\n\r]+" nil t)
  557. (replace-match " " t t))
  558. (shr--translate-insertion-chars)
  559. (goto-char (point-max)))
  560. ;; We may have removed everything we inserted if if was just
  561. ;; spaces.
  562. (unless (= font-start (point))
  563. ;; Mark all lines that should possibly be folded afterwards.
  564. (when bolp
  565. (shr-mark-fill start))
  566. (when shr-use-fonts
  567. (put-text-property font-start (point)
  568. 'face
  569. (or shr-current-font 'variable-pitch)))))))))
  570. (defun shr-fill-lines (start end)
  571. (if (<= shr-internal-width 0)
  572. nil
  573. (save-restriction
  574. (narrow-to-region start end)
  575. (goto-char start)
  576. (when (get-text-property (point) 'shr-indentation)
  577. (shr-fill-line))
  578. (while (setq start (next-single-property-change start 'shr-indentation))
  579. (goto-char start)
  580. (when (bolp)
  581. (shr-fill-line)))
  582. (goto-char (point-max)))))
  583. (defun shr-vertical-motion (column)
  584. (if (not shr-use-fonts)
  585. (move-to-column column)
  586. (unless (eolp)
  587. (forward-char 1))
  588. (vertical-motion (cons (/ column (frame-char-width)) 0))
  589. (unless (eolp)
  590. (forward-char 1))))
  591. (defun shr-fill-line ()
  592. (let ((shr-indentation (get-text-property (point) 'shr-indentation))
  593. (continuation (get-text-property
  594. (point) 'shr-continuation-indentation))
  595. start)
  596. (put-text-property (point) (1+ (point)) 'shr-indentation nil)
  597. (let ((face (get-text-property (point) 'face))
  598. (background-start (point)))
  599. (shr-indent)
  600. (when face
  601. (put-text-property background-start (point) 'face
  602. `,(shr-face-background face))))
  603. (setq start (point))
  604. (setq shr-indentation (or continuation shr-indentation))
  605. (shr-vertical-motion shr-internal-width)
  606. (when (looking-at " $")
  607. (delete-region (point) (line-end-position)))
  608. (while (not (eolp))
  609. ;; We have to do some folding. First find the first
  610. ;; previous point suitable for folding.
  611. (if (or (not (shr-find-fill-point (line-beginning-position)))
  612. (= (point) start))
  613. ;; We had unbreakable text (for this width), so just go to
  614. ;; the first space and carry on.
  615. (progn
  616. (beginning-of-line)
  617. (skip-chars-forward " ")
  618. (search-forward " " (line-end-position) 'move)))
  619. ;; Success; continue.
  620. (when (= (preceding-char) ?\s)
  621. (delete-char -1))
  622. (let ((props (text-properties-at (point)))
  623. (gap-start (point)))
  624. (insert "\n")
  625. (shr-indent)
  626. (when props
  627. (add-text-properties gap-start (point) props)))
  628. (setq start (point))
  629. (shr-vertical-motion shr-internal-width)
  630. (when (looking-at " $")
  631. (delete-region (point) (line-end-position))))))
  632. (defun shr-find-fill-point (start)
  633. (let ((bp (point))
  634. (end (point))
  635. failed)
  636. (while (not (or (setq failed (<= (point) start))
  637. (eq (preceding-char) ? )
  638. (eq (following-char) ? )
  639. (shr-char-breakable-p (preceding-char))
  640. (shr-char-breakable-p (following-char))
  641. (and (shr-char-kinsoku-bol-p (preceding-char))
  642. (shr-char-breakable-p (following-char))
  643. (not (shr-char-kinsoku-bol-p (following-char))))
  644. (shr-char-kinsoku-eol-p (following-char))
  645. (bolp)))
  646. (backward-char 1))
  647. (if failed
  648. ;; There's no breakable point, so we give it up.
  649. (let (found)
  650. (goto-char bp)
  651. ;; Don't overflow the window edge, even if
  652. ;; shr-kinsoku-shorten is nil.
  653. (unless (or shr-kinsoku-shorten (null shr-width))
  654. (while (setq found (re-search-forward
  655. "\\(\\c>\\)\\| \\|\\c<\\|\\c|"
  656. (line-end-position) 'move)))
  657. (if (and found
  658. (not (match-beginning 1)))
  659. (goto-char (match-beginning 0)))))
  660. (or
  661. (eolp)
  662. ;; Don't put kinsoku-bol characters at the beginning of a line,
  663. ;; or kinsoku-eol characters at the end of a line.
  664. (cond
  665. ;; Don't overflow the window edge, even if shr-kinsoku-shorten
  666. ;; is nil.
  667. ((or shr-kinsoku-shorten (null shr-width))
  668. (while (and (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
  669. (or (shr-char-kinsoku-eol-p (preceding-char))
  670. (shr-char-kinsoku-bol-p (following-char))))
  671. (backward-char 1))
  672. (when (setq failed (<= (point) start))
  673. ;; There's no breakable point that doesn't violate kinsoku,
  674. ;; so we look for the second best position.
  675. (while (and (progn
  676. (forward-char 1)
  677. (<= (point) end))
  678. (progn
  679. (setq bp (point))
  680. (shr-char-kinsoku-eol-p (following-char)))))
  681. (goto-char bp)))
  682. ((shr-char-kinsoku-eol-p (preceding-char))
  683. ;; Find backward the point where kinsoku-eol characters begin.
  684. (let ((count 4))
  685. (while
  686. (progn
  687. (backward-char 1)
  688. (and (> (setq count (1- count)) 0)
  689. (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
  690. (or (shr-char-kinsoku-eol-p (preceding-char))
  691. (shr-char-kinsoku-bol-p (following-char)))))))
  692. (when (setq failed (<= (point) start))
  693. ;; There's no breakable point that doesn't violate kinsoku,
  694. ;; so we go to the second best position.
  695. (if (looking-at "\\(\\c<+\\)\\c<")
  696. (goto-char (match-end 1))
  697. (forward-char 1))))
  698. ((shr-char-kinsoku-bol-p (following-char))
  699. ;; Find forward the point where kinsoku-bol characters end.
  700. (let ((count 4))
  701. (while (progn
  702. (forward-char 1)
  703. (and (>= (setq count (1- count)) 0)
  704. (shr-char-kinsoku-bol-p (following-char))
  705. (shr-char-breakable-p (following-char))))))))
  706. (when (eq (following-char) ? )
  707. (forward-char 1))))
  708. (not failed)))
  709. (defun shr-parse-base (url)
  710. ;; Always chop off anchors.
  711. (when (string-match "#.*" url)
  712. (setq url (substring url 0 (match-beginning 0))))
  713. ;; NB: <base href="" > URI may itself be relative to the document s URI
  714. (setq url (shr-expand-url url))
  715. (let* ((parsed (url-generic-parse-url url))
  716. (local (url-filename parsed)))
  717. (setf (url-filename parsed) "")
  718. ;; Chop off the bit after the last slash.
  719. (when (string-match "\\`\\(.*/\\)[^/]+\\'" local)
  720. (setq local (match-string 1 local)))
  721. ;; Always make the local bit end with a slash.
  722. (when (and (not (zerop (length local)))
  723. (not (eq (aref local (1- (length local))) ?/)))
  724. (setq local (concat local "/")))
  725. (list (url-recreate-url parsed)
  726. local
  727. (url-type parsed)
  728. url)))
  729. (autoload 'url-expand-file-name "url-expand")
  730. ;; FIXME This needs some tests writing.
  731. ;; Does it even need to exist, given that url-expand-file-name does?
  732. (defun shr-expand-url (url &optional base)
  733. (setq base
  734. (if base
  735. ;; shr-parse-base should never call this with non-nil base!
  736. (shr-parse-base base)
  737. ;; Bound by the parser.
  738. shr-base))
  739. (when (zerop (length url))
  740. (setq url nil))
  741. ;; Strip leading whitespace
  742. (and url (string-match "\\`\\s-+" url)
  743. (setq url (substring url (match-end 0))))
  744. (cond ((zerop (length url))
  745. (nth 3 base))
  746. ((or (not base)
  747. (string-match "\\`[a-z]*:" url))
  748. ;; Absolute or empty URI
  749. url)
  750. ((eq (aref url 0) ?/)
  751. (if (and (> (length url) 1)
  752. (eq (aref url 1) ?/))
  753. ;; //host...; just use the protocol
  754. (concat (nth 2 base) ":" url)
  755. ;; Just use the host name part.
  756. (concat (car base) url)))
  757. ((eq (aref url 0) ?#)
  758. ;; A link to an anchor.
  759. (concat (nth 3 base) url))
  760. (t
  761. ;; Totally relative.
  762. (url-expand-file-name url (concat (car base) (cadr base))))))
  763. (defun shr-ensure-newline ()
  764. (unless (bobp)
  765. (let ((prefix (get-text-property (line-beginning-position)
  766. 'shr-prefix-length)))
  767. (unless (or (zerop (current-column))
  768. (and prefix
  769. (= prefix (- (point) (line-beginning-position)))))
  770. (insert "\n")))))
  771. (defun shr-ensure-paragraph ()
  772. (unless (bobp)
  773. (let ((prefix (get-text-property (line-beginning-position)
  774. 'shr-prefix-length)))
  775. (cond
  776. ((and (bolp)
  777. (save-excursion
  778. (forward-line -1)
  779. (looking-at " *$")))
  780. ;; We're already at a new paragraph; do nothing.
  781. )
  782. ((and prefix
  783. (= prefix (- (point) (line-beginning-position))))
  784. ;; Do nothing; we're at the start of a <li>.
  785. )
  786. ((save-excursion
  787. (beginning-of-line)
  788. ;; If the current line is totally blank, and doesn't even
  789. ;; have any face properties set, then delete the blank
  790. ;; space.
  791. (and (looking-at " *$")
  792. (not (get-text-property (point) 'face))
  793. (not (= (next-single-property-change (point) 'face nil
  794. (line-end-position))
  795. (line-end-position)))))
  796. (delete-region (match-beginning 0) (match-end 0)))
  797. ;; We have a single blank line.
  798. ((and (eolp) (bolp))
  799. (insert "\n"))
  800. ;; Insert new paragraph.
  801. (t
  802. (insert "\n\n"))))))
  803. (defun shr-indent ()
  804. (when (> shr-indentation 0)
  805. (insert
  806. (if (not shr-use-fonts)
  807. (make-string shr-indentation ?\s)
  808. (propertize " "
  809. 'display
  810. `(space :width (,shr-indentation)))))))
  811. (defun shr-fontize-dom (dom &rest types)
  812. (let ((start (point)))
  813. (shr-generic dom)
  814. (dolist (type types)
  815. (shr-add-font start (point) type))))
  816. ;; Add face to the region, but avoid putting the font properties on
  817. ;; blank text at the start of the line, and the newline at the end, to
  818. ;; avoid ugliness.
  819. (defun shr-add-font (start end type)
  820. (save-excursion
  821. (goto-char start)
  822. (while (< (point) end)
  823. (when (bolp)
  824. (skip-chars-forward " "))
  825. (add-face-text-property (point) (min (line-end-position) end) type t)
  826. (if (< (line-end-position) end)
  827. (forward-line 1)
  828. (goto-char end)))))
  829. (defun shr-mouse-browse-url (ev)
  830. "Browse the URL under the mouse cursor."
  831. (interactive "e")
  832. (mouse-set-point ev)
  833. (shr-browse-url))
  834. (defun shr-browse-url (&optional external mouse-event)
  835. "Browse the URL under point.
  836. If EXTERNAL, browse the URL using `shr-external-browser'."
  837. (interactive (list current-prefix-arg last-nonmenu-event))
  838. (mouse-set-point mouse-event)
  839. (let ((url (get-text-property (point) 'shr-url)))
  840. (cond
  841. ((not url)
  842. (message "No link under point"))
  843. ((string-match "^mailto:" url)
  844. (browse-url-mail url))
  845. (t
  846. (if external
  847. (funcall shr-external-browser url)
  848. (browse-url url))))))
  849. (defun shr-save-contents (directory)
  850. "Save the contents from URL in a file."
  851. (interactive "DSave contents of URL to directory: ")
  852. (let ((url (get-text-property (point) 'shr-url)))
  853. (if (not url)
  854. (message "No link under point")
  855. (url-retrieve (shr-encode-url url)
  856. 'shr-store-contents (list url directory)
  857. nil t))))
  858. (defun shr-store-contents (status url directory)
  859. (unless (plist-get status :error)
  860. (when (or (search-forward "\n\n" nil t)
  861. (search-forward "\r\n\r\n" nil t))
  862. (write-region (point) (point-max)
  863. (expand-file-name (file-name-nondirectory url)
  864. directory)))))
  865. (defun shr-image-fetched (status buffer start end &optional flags)
  866. (let ((image-buffer (current-buffer)))
  867. (when (and (buffer-name buffer)
  868. (not (plist-get status :error)))
  869. (url-store-in-cache image-buffer)
  870. (goto-char (point-min))
  871. (when (or (search-forward "\n\n" nil t)
  872. (search-forward "\r\n\r\n" nil t))
  873. (let ((data (shr-parse-image-data)))
  874. (with-current-buffer buffer
  875. (save-excursion
  876. (save-restriction
  877. (widen)
  878. (let ((alt (buffer-substring start end))
  879. (properties (text-properties-at start))
  880. (inhibit-read-only t))
  881. (delete-region start end)
  882. (goto-char start)
  883. (funcall shr-put-image-function data alt flags)
  884. (while properties
  885. (let ((type (pop properties))
  886. (value (pop properties)))
  887. (unless (memq type '(display image-size))
  888. (put-text-property start (point) type value)))))))))))
  889. (kill-buffer image-buffer)))
  890. (defun shr-image-from-data (data)
  891. "Return an image from the data: URI content DATA."
  892. (when (string-match
  893. "\\(\\([^/;,]+\\(/[^;,]+\\)?\\)\\(;[^;,]+\\)*\\)?,\\(.*\\)"
  894. data)
  895. (let ((param (match-string 4 data))
  896. (payload (url-unhex-string (match-string 5 data))))
  897. (when (string-match "^.*\\(;[ \t]*base64\\)$" param)
  898. (setq payload (ignore-errors
  899. (base64-decode-string payload))))
  900. payload)))
  901. ;; Behind display-graphic-p test.
  902. (declare-function image-size "image.c" (spec &optional pixels frame))
  903. (declare-function image-animate "image" (image &optional index limit))
  904. (defun shr-put-image (spec alt &optional flags)
  905. "Insert image SPEC with a string ALT. Return image.
  906. SPEC is either an image data blob, or a list where the first
  907. element is the data blob and the second element is the content-type."
  908. (if (display-graphic-p)
  909. (let* ((size (cdr (assq 'size flags)))
  910. (data (if (consp spec)
  911. (car spec)
  912. spec))
  913. (content-type (and (consp spec)
  914. (cadr spec)))
  915. (start (point))
  916. (image (cond
  917. ((eq size 'original)
  918. (create-image data nil t :ascent 100
  919. :format content-type))
  920. ((eq content-type 'image/svg+xml)
  921. (create-image data 'svg t :ascent 100))
  922. ((eq size 'full)
  923. (ignore-errors
  924. (shr-rescale-image data content-type
  925. (plist-get flags :width)
  926. (plist-get flags :height))))
  927. (t
  928. (ignore-errors
  929. (shr-rescale-image data content-type
  930. (plist-get flags :width)
  931. (plist-get flags :height)))))))
  932. (when image
  933. ;; When inserting big-ish pictures, put them at the
  934. ;; beginning of the line.
  935. (when (and (> (current-column) 0)
  936. (> (car (image-size image t)) 400))
  937. (insert "\n"))
  938. (if (eq size 'original)
  939. (insert-sliced-image image (or alt "*") nil 20 1)
  940. (insert-image image (or alt "*")))
  941. (put-text-property start (point) 'image-size size)
  942. (when (and shr-image-animate
  943. (cond ((fboundp 'image-multi-frame-p)
  944. ;; Only animate multi-frame things that specify a
  945. ;; delay; eg animated gifs as opposed to
  946. ;; multi-page tiffs. FIXME?
  947. (cdr (image-multi-frame-p image)))
  948. ((fboundp 'image-animated-p)
  949. (image-animated-p image))))
  950. (image-animate image nil 60)))
  951. image)
  952. (insert (or alt ""))))
  953. (defun shr-rescale-image (data content-type width height
  954. &optional max-width max-height)
  955. "Rescale DATA, if too big, to fit the current buffer.
  956. WIDTH and HEIGHT are the sizes given in the HTML data, if any.
  957. The size of the displayed image will not exceed
  958. MAX-WIDTH/MAX-HEIGHT. If not given, use the current window
  959. width/height instead."
  960. (if (or (not (fboundp 'imagemagick-types))
  961. (not (get-buffer-window (current-buffer))))
  962. (create-image data nil t :ascent 100)
  963. (let* ((edges (window-inside-pixel-edges
  964. (get-buffer-window (current-buffer))))
  965. (max-width (truncate (* shr-max-image-proportion
  966. (or max-width
  967. (- (nth 2 edges) (nth 0 edges))))))
  968. (max-height (truncate (* shr-max-image-proportion
  969. (or max-height
  970. (- (nth 3 edges) (nth 1 edges))))))
  971. (scaling (image-compute-scaling-factor image-scaling-factor)))
  972. (when (or (and width
  973. (> width max-width))
  974. (and height
  975. (> height max-height)))
  976. (setq width nil
  977. height nil))
  978. (if (and width height
  979. (< (* width scaling) max-width)
  980. (< (* height scaling) max-height))
  981. (create-image
  982. data 'imagemagick t
  983. :ascent 100
  984. :width width
  985. :height height
  986. :format content-type)
  987. (create-image
  988. data 'imagemagick t
  989. :ascent 100
  990. :max-width max-width
  991. :max-height max-height
  992. :format content-type)))))
  993. ;; url-cache-extract autoloads url-cache.
  994. (declare-function url-cache-create-filename "url-cache" (url))
  995. (autoload 'mm-disable-multibyte "mm-util")
  996. (autoload 'browse-url-mail "browse-url")
  997. (defun shr-get-image-data (url)
  998. "Get image data for URL.
  999. Return a string with image data."
  1000. (with-temp-buffer
  1001. (mm-disable-multibyte)
  1002. (when (ignore-errors
  1003. (url-cache-extract (url-cache-create-filename (shr-encode-url url)))
  1004. t)
  1005. (when (re-search-forward "\r?\n\r?\n" nil t)
  1006. (shr-parse-image-data)))))
  1007. (declare-function libxml-parse-xml-region "xml.c"
  1008. (start end &optional base-url discard-comments))
  1009. (defun shr-parse-image-data ()
  1010. (let ((data (buffer-substring (point) (point-max)))
  1011. (content-type
  1012. (save-excursion
  1013. (save-restriction
  1014. (narrow-to-region (point-min) (point))
  1015. (let ((content-type (mail-fetch-field "content-type")))
  1016. (and content-type
  1017. ;; Remove any comments in the type string.
  1018. (intern (replace-regexp-in-string ";.*" "" content-type)
  1019. obarray)))))))
  1020. ;; SVG images may contain references to further images that we may
  1021. ;; want to block. So special-case these by parsing the XML data
  1022. ;; and remove anything that looks like a blocked bit.
  1023. (when (and shr-blocked-images
  1024. (eq content-type 'image/svg+xml))
  1025. (setq data
  1026. ;; Note that libxml2 doesn't parse everything perfectly,
  1027. ;; so glitches may occur during this transformation.
  1028. (shr-dom-to-xml
  1029. (libxml-parse-xml-region (point) (point-max)))))
  1030. (list data content-type)))
  1031. (defun shr-image-displayer (content-function)
  1032. "Return a function to display an image.
  1033. CONTENT-FUNCTION is a function to retrieve an image for a cid url that
  1034. is an argument. The function to be returned takes three arguments URL,
  1035. START, and END. Note that START and END should be markers."
  1036. `(lambda (url start end)
  1037. (when url
  1038. (if (string-match "\\`cid:" url)
  1039. ,(when content-function
  1040. `(let ((image (funcall ,content-function
  1041. (substring url (match-end 0)))))
  1042. (when image
  1043. (goto-char start)
  1044. (funcall shr-put-image-function
  1045. image (buffer-substring start end))
  1046. (delete-region (point) end))))
  1047. (url-retrieve url 'shr-image-fetched
  1048. (list (current-buffer) start end)
  1049. t t)))))
  1050. (defun shr-heading (dom &rest types)
  1051. (shr-ensure-paragraph)
  1052. (apply #'shr-fontize-dom dom types)
  1053. (shr-ensure-paragraph))
  1054. (defun shr-urlify (start url &optional title)
  1055. (shr-add-font start (point) 'shr-link)
  1056. (add-text-properties
  1057. start (point)
  1058. (list 'shr-url url
  1059. 'help-echo (let ((iri (or (ignore-errors
  1060. (decode-coding-string
  1061. (url-unhex-string url)
  1062. 'utf-8 t))
  1063. url)))
  1064. (if title (format "%s (%s)" iri title) iri))
  1065. 'follow-link t
  1066. 'mouse-face 'highlight))
  1067. ;; Don't overwrite any keymaps that are already in the buffer (i.e.,
  1068. ;; image keymaps).
  1069. (while (and start
  1070. (< start (point)))
  1071. (let ((next (next-single-property-change start 'keymap nil (point))))
  1072. (if (get-text-property start 'keymap)
  1073. (setq start next)
  1074. (put-text-property start (or next (point)) 'keymap shr-map)))))
  1075. (defun shr-encode-url (url)
  1076. "Encode URL."
  1077. (browse-url-url-encode-chars url "[)$ ]"))
  1078. (autoload 'shr-color-visible "shr-color")
  1079. (autoload 'shr-color->hexadecimal "shr-color")
  1080. (defun shr-color-check (fg bg)
  1081. "Check that FG is visible on BG.
  1082. Returns (fg bg) with corrected values.
  1083. Returns nil if the colors that would be used are the default
  1084. ones, in case fg and bg are nil."
  1085. (when (or fg bg)
  1086. (let ((fixed (cond ((null fg) 'fg)
  1087. ((null bg) 'bg))))
  1088. ;; Convert colors to hexadecimal, or set them to default.
  1089. (let ((fg (or (shr-color->hexadecimal fg)
  1090. (frame-parameter nil 'foreground-color)))
  1091. (bg (or (shr-color->hexadecimal bg)
  1092. (frame-parameter nil 'background-color))))
  1093. (cond ((eq fixed 'bg)
  1094. ;; Only return the new fg
  1095. (list nil (cadr (shr-color-visible bg fg t))))
  1096. ((eq fixed 'fg)
  1097. ;; Invert args and results and return only the new bg
  1098. (list (cadr (shr-color-visible fg bg t)) nil))
  1099. (t
  1100. (shr-color-visible bg fg)))))))
  1101. (defun shr-colorize-region (start end fg &optional bg)
  1102. (when (and shr-use-colors
  1103. (or fg bg)
  1104. (>= (display-color-cells) 88))
  1105. (let ((new-colors (shr-color-check fg bg)))
  1106. (when new-colors
  1107. (when fg
  1108. (add-face-text-property start end
  1109. (list :foreground (cadr new-colors))
  1110. t))
  1111. (when bg
  1112. (add-face-text-property start end
  1113. (list :background (car new-colors))
  1114. t)))
  1115. new-colors)))
  1116. ;;; Tag-specific rendering rules.
  1117. (defun shr-tag-html (dom)
  1118. (let ((dir (dom-attr dom 'dir)))
  1119. (cond
  1120. ((equal dir "ltr")
  1121. (setq bidi-paragraph-direction 'left-to-right))
  1122. ((equal dir "rtl")
  1123. (setq bidi-paragraph-direction 'right-to-left))
  1124. ((equal dir "auto")
  1125. (setq bidi-paragraph-direction nil))))
  1126. (shr-generic dom))
  1127. (defun shr-tag-body (dom)
  1128. (let* ((start (point))
  1129. (fgcolor (or (dom-attr dom 'fgcolor) (dom-attr dom 'text)))
  1130. (bgcolor (dom-attr dom 'bgcolor))
  1131. (shr-stylesheet (list (cons 'color fgcolor)
  1132. (cons 'background-color bgcolor))))
  1133. (shr-generic dom)
  1134. (shr-colorize-region start (point) fgcolor bgcolor)))
  1135. (defun shr-tag-style (_dom)
  1136. )
  1137. (defun shr-tag-script (_dom)
  1138. )
  1139. (defun shr-tag-comment (_dom)
  1140. )
  1141. (defun shr-dom-to-xml (dom)
  1142. (with-temp-buffer
  1143. (shr-dom-print dom)
  1144. (buffer-string)))
  1145. (defun shr-dom-print (dom)
  1146. "Convert DOM into a string containing the xml representation."
  1147. (insert (format "<%s" (dom-tag dom)))
  1148. (dolist (attr (dom-attributes dom))
  1149. ;; Ignore attributes that start with a colon because they are
  1150. ;; private elements.
  1151. (unless (= (aref (format "%s" (car attr)) 0) ?:)
  1152. (insert (format " %s=\"%s\"" (car attr) (cdr attr)))))
  1153. (insert ">")
  1154. (let (url)
  1155. (dolist (elem (dom-children dom))
  1156. (cond
  1157. ((stringp elem)
  1158. (insert elem))
  1159. ((eq (dom-tag elem) 'comment)
  1160. )
  1161. ((or (not (eq (dom-tag elem) 'image))
  1162. ;; Filter out blocked elements inside the SVG image.
  1163. (not (setq url (dom-attr elem ':xlink:href)))
  1164. (not shr-blocked-images)
  1165. (not (string-match shr-blocked-images url)))
  1166. (insert " ")
  1167. (shr-dom-print elem)))))
  1168. (insert (format "</%s>" (dom-tag dom))))
  1169. (defun shr-tag-svg (dom)
  1170. (when (and (image-type-available-p 'svg)
  1171. (not shr-inhibit-images)
  1172. (dom-attr dom 'width)
  1173. (dom-attr dom 'height))
  1174. (funcall shr-put-image-function (list (shr-dom-to-xml dom) 'image/svg+xml)
  1175. "SVG Image")))
  1176. (defun shr-tag-sup (dom)
  1177. (let ((start (point)))
  1178. (shr-generic dom)
  1179. (put-text-property start (point) 'display '(raise 0.5))))
  1180. (defun shr-tag-sub (dom)
  1181. (let ((start (point)))
  1182. (shr-generic dom)
  1183. (put-text-property start (point) 'display '(raise -0.5))))
  1184. (defun shr-tag-label (dom)
  1185. (shr-generic dom)
  1186. (shr-ensure-paragraph))
  1187. (defun shr-tag-p (dom)
  1188. (shr-ensure-paragraph)
  1189. (shr-generic dom)
  1190. (shr-ensure-paragraph))
  1191. (defun shr-tag-div (dom)
  1192. (shr-ensure-newline)
  1193. (shr-generic dom)
  1194. (shr-ensure-newline))
  1195. (defun shr-tag-s (dom)
  1196. (shr-fontize-dom dom 'shr-strike-through))
  1197. (defun shr-tag-b (dom)
  1198. (shr-fontize-dom dom 'bold))
  1199. (defun shr-tag-i (dom)
  1200. (shr-fontize-dom dom 'italic))
  1201. (defun shr-tag-em (dom)
  1202. (shr-fontize-dom dom 'italic))
  1203. (defun shr-tag-strong (dom)
  1204. (shr-fontize-dom dom 'bold))
  1205. (defun shr-tag-u (dom)
  1206. (shr-fontize-dom dom 'underline))
  1207. (defun shr-tag-tt (dom)
  1208. (let ((shr-current-font 'default))
  1209. (shr-generic dom)))
  1210. (defun shr-tag-ins (cont)
  1211. (let* ((start (point))
  1212. (color "green")
  1213. (shr-stylesheet (nconc (list (cons 'color color))
  1214. shr-stylesheet)))
  1215. (shr-generic cont)
  1216. (shr-colorize-region start (point) color
  1217. (cdr (assq 'background-color shr-stylesheet)))))
  1218. (defun shr-tag-del (cont)
  1219. (let* ((start (point))
  1220. (color "red")
  1221. (shr-stylesheet (nconc (list (cons 'color color))
  1222. shr-stylesheet)))
  1223. (shr-fontize-dom cont 'shr-strike-through)
  1224. (shr-colorize-region start (point) color
  1225. (cdr (assq 'background-color shr-stylesheet)))))
  1226. (defun shr-parse-style (style)
  1227. (when style
  1228. (save-match-data
  1229. (when (string-match "\n" style)
  1230. (setq style (replace-match " " t t style))))
  1231. (let ((plist nil))
  1232. (dolist (elem (split-string style ";"))
  1233. (when elem
  1234. (setq elem (split-string elem ":"))
  1235. (when (and (car elem)
  1236. (cadr elem))
  1237. (let ((name (replace-regexp-in-string "^ +\\| +$" "" (car elem)))
  1238. (value (replace-regexp-in-string "^ +\\| +$" "" (cadr elem))))
  1239. (when (string-match " *!important\\'" value)
  1240. (setq value (substring value 0 (match-beginning 0))))
  1241. (unless (equal value "inherit")
  1242. (push (cons (intern name obarray)
  1243. value)
  1244. plist))))))
  1245. plist)))
  1246. (defun shr-tag-base (dom)
  1247. (when-let (base (dom-attr dom 'href))
  1248. (setq shr-base (shr-parse-base base)))
  1249. (shr-generic dom))
  1250. (defun shr-tag-a (dom)
  1251. (let ((url (dom-attr dom 'href))
  1252. (title (dom-attr dom 'title))
  1253. (start (point))
  1254. shr-start)
  1255. (shr-generic dom)
  1256. (when (and shr-target-id
  1257. (equal (dom-attr dom 'name) shr-target-id))
  1258. ;; We have a zero-length <a name="foo"> element, so just
  1259. ;; insert... something.
  1260. (when (= start (point))
  1261. (shr-ensure-newline)
  1262. (insert " "))
  1263. (put-text-property start (1+ start) 'shr-target-id shr-target-id))
  1264. (when url
  1265. (shr-urlify (or shr-start start) (shr-expand-url url) title))))
  1266. (defun shr-tag-object (dom)
  1267. (unless shr-inhibit-images
  1268. (let ((start (point))
  1269. url multimedia image)
  1270. (when-let (type (dom-attr dom 'type))
  1271. (when (string-match "\\`image/svg" type)
  1272. (setq url (dom-attr dom 'data)
  1273. image t)))
  1274. (dolist (child (dom-non-text-children dom))
  1275. (cond
  1276. ((eq (dom-tag child) 'embed)
  1277. (setq url (or url (dom-attr child 'src))
  1278. multimedia t))
  1279. ((and (eq (dom-tag child) 'param)
  1280. (equal (dom-attr child 'name) "movie"))
  1281. (setq url (or url (dom-attr child 'value))
  1282. multimedia t))))
  1283. (when url
  1284. (cond
  1285. (image
  1286. (shr-tag-img dom url)
  1287. (setq dom nil))
  1288. (multimedia
  1289. (shr-insert " [multimedia] ")
  1290. (shr-urlify start (shr-expand-url url)))))
  1291. (when dom
  1292. (shr-generic dom)))))
  1293. (defcustom shr-prefer-media-type-alist '(("webm" . 1.0)
  1294. ("ogv" . 1.0)
  1295. ("ogg" . 1.0)
  1296. ("opus" . 1.0)
  1297. ("flac" . 0.9)
  1298. ("wav" . 0.5))
  1299. "Preferences for media types.
  1300. The key element should be a regexp matched against the type of the source or
  1301. url if no type is specified. The value should be a float in the range 0.0 to
  1302. 1.0. Media elements with higher value are preferred."
  1303. :version "24.4"
  1304. :group 'shr
  1305. :type '(alist :key-type regexp :value-type float))
  1306. (defun shr--get-media-pref (elem)
  1307. "Determine the preference for ELEM.
  1308. The preference is a float determined from `shr-prefer-media-type'."
  1309. (let ((type (dom-attr elem 'type))
  1310. (p 0.0))
  1311. (unless type
  1312. (setq type (dom-attr elem 'src)))
  1313. (when type
  1314. (dolist (pref shr-prefer-media-type-alist)
  1315. (when (and
  1316. (> (cdr pref) p)
  1317. (string-match-p (car pref) type))
  1318. (setq p (cdr pref)))))
  1319. p))
  1320. (defun shr--extract-best-source (dom &optional url pref)
  1321. "Extract the best `:src' property from <source> blocks in DOM."
  1322. (setq pref (or pref -1.0))
  1323. (let (new-pref)
  1324. (dolist (elem (dom-non-text-children dom))
  1325. (when (and (eq (dom-tag elem) 'source)
  1326. (< pref
  1327. (setq new-pref
  1328. (shr--get-media-pref elem))))
  1329. (setq pref new-pref
  1330. url (dom-attr elem 'src))
  1331. ;; libxml's html parser isn't HTML5 compliant and non terminated
  1332. ;; source tags might end up as children. So recursion it is...
  1333. (dolist (child (dom-non-text-children elem))
  1334. (when (eq (dom-tag child) 'source)
  1335. (let ((ret (shr--extract-best-source (list child) url pref)))
  1336. (when (< pref (cdr ret))
  1337. (setq url (car ret)
  1338. pref (cdr ret)))))))))
  1339. (cons url pref))
  1340. (defun shr-tag-video (dom)
  1341. (let ((image (dom-attr dom 'poster))
  1342. (url (dom-attr dom 'src))
  1343. (start (point)))
  1344. (unless url
  1345. (setq url (car (shr--extract-best-source dom))))
  1346. (if (> (length image) 0)
  1347. (shr-tag-img nil image)
  1348. (shr-insert " [video] "))
  1349. (shr-urlify start (shr-expand-url url))))
  1350. (defun shr-tag-audio (dom)
  1351. (let ((url (dom-attr dom 'src))
  1352. (start (point)))
  1353. (unless url
  1354. (setq url (car (shr--extract-best-source dom))))
  1355. (shr-insert " [audio] ")
  1356. (shr-urlify start (shr-expand-url url))))
  1357. (defun shr-tag-img (dom &optional url)
  1358. (when (or url
  1359. (and dom
  1360. (or (> (length (dom-attr dom 'src)) 0)
  1361. (> (length (dom-attr dom 'srcset)) 0))))
  1362. (when (> (current-column) 0)
  1363. (insert "\n"))
  1364. (let ((alt (dom-attr dom 'alt))
  1365. (width (shr-string-number (dom-attr dom 'width)))
  1366. (height (shr-string-number (dom-attr dom 'height)))
  1367. (url (shr-expand-url (or url (shr--preferred-image dom)))))
  1368. (let ((start (point-marker)))
  1369. (when (zerop (length alt))
  1370. (setq alt "*"))
  1371. (cond
  1372. ((or (member (dom-attr dom 'height) '("0" "1"))
  1373. (member (dom-attr dom 'width) '("0" "1")))
  1374. ;; Ignore zero-sized or single-pixel images.
  1375. )
  1376. ((and (not shr-inhibit-images)
  1377. (string-match "\\`data:" url))
  1378. (let ((image (shr-image-from-data (substring url (match-end 0)))))
  1379. (if image
  1380. (funcall shr-put-image-function image alt
  1381. (list :width width :height height))
  1382. (insert alt))))
  1383. ((and (not shr-inhibit-images)
  1384. (string-match "\\`cid:" url))
  1385. (let ((url (substring url (match-end 0)))
  1386. image)
  1387. (if (or (not shr-content-function)
  1388. (not (setq image (funcall shr-content-function url))))
  1389. (insert alt)
  1390. (funcall shr-put-image-function image alt
  1391. (list :width width :height height)))))
  1392. ((or shr-inhibit-images
  1393. (and shr-blocked-images
  1394. (string-match shr-blocked-images url)))
  1395. (setq shr-start (point))
  1396. (shr-insert alt))
  1397. ((and (not shr-ignore-cache)
  1398. (url-is-cached (shr-encode-url url)))
  1399. (funcall shr-put-image-function (shr-get-image-data url) alt
  1400. (list :width width :height height)))
  1401. (t
  1402. (when (and shr-ignore-cache
  1403. (url-is-cached (shr-encode-url url)))
  1404. (let ((file (url-cache-create-filename (shr-encode-url url))))
  1405. (when (file-exists-p file)
  1406. (delete-file file))))
  1407. (when (image-type-available-p 'svg)
  1408. (insert-image
  1409. (shr-make-placeholder-image dom)
  1410. (or alt "")))
  1411. (insert " ")
  1412. (url-queue-retrieve
  1413. (shr-encode-url url) 'shr-image-fetched
  1414. (list (current-buffer) start (set-marker (make-marker) (point))
  1415. (list :width width :height height))
  1416. t t)))
  1417. (when (zerop shr-table-depth) ;; We are not in a table.
  1418. (put-text-property start (point) 'keymap shr-image-map)
  1419. (put-text-property start (point) 'shr-alt alt)
  1420. (put-text-property start (point) 'image-url url)
  1421. (put-text-property start (point) 'image-displayer
  1422. (shr-image-displayer shr-content-function))
  1423. (put-text-property start (point) 'help-echo
  1424. (shr-fill-text
  1425. (or (dom-attr dom 'title) alt))))))))
  1426. (defun shr--preferred-image (dom)
  1427. (let ((srcset (dom-attr dom 'srcset))
  1428. (frame-width (frame-pixel-width))
  1429. (width (string-to-number (or (dom-attr dom 'width) "100")))
  1430. candidate)
  1431. (when (> (length srcset) 0)
  1432. ;; srcset consist of a series of URL/size specifications
  1433. ;; separated by the ", " string.
  1434. (setq srcset
  1435. (sort (mapcar
  1436. (lambda (elem)
  1437. (let ((spec (split-string elem "[\t\n\r ]+")))
  1438. (cond
  1439. ((= (length spec) 1)
  1440. ;; Make sure it's well formed.
  1441. (list (car spec) 0))
  1442. ((string-match "\\([0-9]+\\)x\\'" (cadr spec))
  1443. ;; If we have an "x" form, then use the width
  1444. ;; spec to compute the real width.
  1445. (list (car spec)
  1446. (* width (string-to-number
  1447. (match-string 1 (cadr spec))))))
  1448. (t
  1449. (list (car spec)
  1450. (string-to-number (cadr spec)))))))
  1451. (split-string (replace-regexp-in-string
  1452. "\\`[\t\n\r ]+\\|[\t\n\r ]+\\'" "" srcset)
  1453. "[\t\n\r ]*,[\t\n\r ]*"))
  1454. (lambda (e1 e2)
  1455. (> (cadr e1) (cadr e2)))))
  1456. ;; Choose the smallest picture that's bigger than the current
  1457. ;; frame.
  1458. (setq candidate (caar srcset))
  1459. (while (and srcset
  1460. (> (cadr (car srcset)) frame-width))
  1461. (setq candidate (caar srcset))
  1462. (pop srcset)))
  1463. (or candidate (dom-attr dom 'src))))
  1464. (defun shr-string-number (string)
  1465. (if (null string)
  1466. nil
  1467. (setq string (replace-regexp-in-string "[^0-9]" "" string))
  1468. (if (zerop (length string))
  1469. nil
  1470. (string-to-number string))))
  1471. (defun shr-make-placeholder-image (dom)
  1472. (let* ((edges (and
  1473. (get-buffer-window (current-buffer))
  1474. (window-inside-pixel-edges
  1475. (get-buffer-window (current-buffer)))))
  1476. (scaling (image-compute-scaling-factor image-scaling-factor))
  1477. (width (truncate
  1478. (* (or (shr-string-number (dom-attr dom 'width)) 100)
  1479. scaling)))
  1480. (height (truncate
  1481. (* (or (shr-string-number (dom-attr dom 'height)) 100)
  1482. scaling)))
  1483. (max-width
  1484. (and edges
  1485. (truncate (* shr-max-image-proportion
  1486. (- (nth 2 edges) (nth 0 edges))))))
  1487. (max-height (and edges
  1488. (truncate (* shr-max-image-proportion
  1489. (- (nth 3 edges) (nth 1 edges))))))
  1490. svg)
  1491. (when (and max-width
  1492. (> width max-width))
  1493. (setq height (truncate (* (/ (float max-width) width) height))
  1494. width max-width))
  1495. (when (and max-height
  1496. (> height max-height))
  1497. (setq width (truncate (* (/ (float max-height) height) width))
  1498. height max-height))
  1499. (setq svg (svg-create width height))
  1500. (svg-gradient svg "background" 'linear '((0 . "#b0b0b0") (100 . "#808080")))
  1501. (svg-rectangle svg 0 0 width height :gradient "background"
  1502. :stroke-width 2 :stroke-color "black")
  1503. (let ((image (svg-image svg)))
  1504. (setf (image-property image :ascent) 100)
  1505. image)))
  1506. (defun shr-tag-pre (dom)
  1507. (let ((shr-folding-mode 'none)
  1508. (shr-current-font 'default))
  1509. (shr-ensure-newline)
  1510. (shr-generic dom)
  1511. (shr-ensure-newline)))
  1512. (defun shr-tag-blockquote (dom)
  1513. (shr-ensure-paragraph)
  1514. (let ((start (point))
  1515. (shr-indentation (+ shr-indentation
  1516. (* 4 shr-table-separator-pixel-width))))
  1517. (shr-generic dom)
  1518. (shr-ensure-paragraph)
  1519. (shr-mark-fill start)))
  1520. (defun shr-tag-dl (dom)
  1521. (shr-ensure-paragraph)
  1522. (shr-generic dom)
  1523. (shr-ensure-paragraph))
  1524. (defun shr-tag-dt (dom)
  1525. (shr-ensure-newline)
  1526. (shr-generic dom)
  1527. (shr-ensure-newline))
  1528. (defun shr-tag-dd (dom)
  1529. (shr-ensure-newline)
  1530. (let ((shr-indentation (+ shr-indentation
  1531. (* 4 shr-table-separator-pixel-width))))
  1532. (shr-generic dom)))
  1533. (defun shr-tag-ul (dom)
  1534. (shr-ensure-paragraph)
  1535. (let ((shr-list-mode 'ul))
  1536. (shr-generic dom))
  1537. ;; If we end on an empty <li>, then make sure we really end on a new
  1538. ;; paragraph.
  1539. (unless (bolp)
  1540. (insert "\n"))
  1541. (shr-ensure-paragraph))
  1542. (defun shr-tag-ol (dom)
  1543. (shr-ensure-paragraph)
  1544. (let ((shr-list-mode 1))
  1545. (shr-generic dom))
  1546. (shr-ensure-paragraph))
  1547. (defun shr-tag-li (dom)
  1548. (shr-ensure-newline)
  1549. (let ((start (point)))
  1550. (let* ((bullet
  1551. (if (numberp shr-list-mode)
  1552. (prog1
  1553. (format "%d " shr-list-mode)
  1554. (setq shr-list-mode (1+ shr-list-mode)))
  1555. (car shr-internal-bullet)))
  1556. (width (if (numberp shr-list-mode)
  1557. (shr-string-pixel-width bullet)
  1558. (cdr shr-internal-bullet))))
  1559. (insert bullet)
  1560. (shr-mark-fill start)
  1561. (let ((shr-indentation (+ shr-indentation width)))
  1562. (put-text-property start (1+ start)
  1563. 'shr-continuation-indentation shr-indentation)
  1564. (put-text-property start (1+ start) 'shr-prefix-length (length bullet))
  1565. (shr-generic dom))))
  1566. (unless (bolp)
  1567. (insert "\n")))
  1568. (defun shr-mark-fill (start)
  1569. ;; We may not have inserted any text to fill.
  1570. (unless (= start (point))
  1571. (put-text-property start (1+ start)
  1572. 'shr-indentation shr-indentation)))
  1573. (defun shr-tag-br (dom)
  1574. (when (and (not (bobp))
  1575. ;; Only add a newline if we break the current line, or
  1576. ;; the previous line isn't a blank line.
  1577. (or (not (bolp))
  1578. (and (> (- (point) 2) (point-min))
  1579. (not (= (char-after (- (point) 2)) ?\n)))))
  1580. (insert "\n"))
  1581. (shr-generic dom))
  1582. (defun shr-tag-span (dom)
  1583. (shr-generic dom))
  1584. (defun shr-tag-h1 (dom)
  1585. (shr-heading dom (if shr-use-fonts
  1586. '(variable-pitch (:height 1.3 :weight bold))
  1587. 'bold)))
  1588. (defun shr-tag-h2 (dom)
  1589. (shr-heading dom 'bold))
  1590. (defun shr-tag-h3 (dom)
  1591. (shr-heading dom 'italic))
  1592. (defun shr-tag-h4 (dom)
  1593. (shr-heading dom))
  1594. (defun shr-tag-h5 (dom)
  1595. (shr-heading dom))
  1596. (defun shr-tag-h6 (dom)
  1597. (shr-heading dom))
  1598. (defun shr-tag-hr (_dom)
  1599. (shr-ensure-newline)
  1600. (insert (make-string (if (not shr-use-fonts)
  1601. shr-internal-width
  1602. (1+ (/ shr-internal-width
  1603. shr-table-separator-pixel-width)))
  1604. shr-hr-line)
  1605. "\n"))
  1606. (defun shr-tag-title (dom)
  1607. (shr-heading dom 'bold 'underline))
  1608. (defun shr-tag-font (dom)
  1609. (let* ((start (point))
  1610. (color (dom-attr dom 'color))
  1611. (shr-stylesheet (nconc (list (cons 'color color))
  1612. shr-stylesheet)))
  1613. (shr-generic dom)
  1614. (when color
  1615. (shr-colorize-region start (point) color
  1616. (cdr (assq 'background-color shr-stylesheet))))))
  1617. (defun shr-tag-bdo (dom)
  1618. (let* ((direction (dom-attr dom 'dir))
  1619. (char (cond
  1620. ((equal direction "ltr")
  1621. ?\N{LEFT-TO-RIGHT OVERRIDE})
  1622. ((equal direction "rtl")
  1623. ?\N{RIGHT-TO-LEFT OVERRIDE}))))
  1624. (when char
  1625. (insert ?\N{FIRST STRONG ISOLATE} char))
  1626. (shr-generic dom)
  1627. (when char
  1628. (insert ?\N{POP DIRECTIONAL FORMATTING} ?\N{POP DIRECTIONAL ISOLATE}))))
  1629. (defun shr-tag-bdi (dom)
  1630. (insert ?\N{FIRST STRONG ISOLATE})
  1631. (shr-generic dom)
  1632. (insert ?\N{POP DIRECTIONAL ISOLATE}))
  1633. ;;; Table rendering algorithm.
  1634. ;; Table rendering is the only complicated thing here. We do this by
  1635. ;; first counting how many TDs there are in each TR, and registering
  1636. ;; how wide they think they should be ("width=45%", etc). Then we
  1637. ;; render each TD separately (this is done in temporary buffers, so
  1638. ;; that we can use all the rendering machinery as if we were in the
  1639. ;; main buffer). Now we know how much space each TD really takes, so
  1640. ;; we then render everything again with the new widths, and finally
  1641. ;; insert all these boxes into the main buffer.
  1642. (defun shr-tag-table-1 (dom)
  1643. (setq dom (or (dom-child-by-tag dom 'tbody) dom))
  1644. (let* ((shr-inhibit-images t)
  1645. (shr-table-depth (1+ shr-table-depth))
  1646. (shr-kinsoku-shorten t)
  1647. ;; Find all suggested widths.
  1648. (columns (shr-column-specs dom))
  1649. ;; Compute how many pixels wide each TD should be.
  1650. (suggested-widths (shr-pro-rate-columns columns))
  1651. ;; Do a "test rendering" to see how big each TD is (this can
  1652. ;; be smaller (if there's little text) or bigger (if there's
  1653. ;; unbreakable text).
  1654. (elems (or (dom-attr dom 'shr-suggested-widths)
  1655. (shr-make-table dom suggested-widths nil
  1656. 'shr-suggested-widths)))
  1657. (sketch (cl-loop for line in elems
  1658. collect (mapcar #'car line)))
  1659. (natural (cl-loop for line in elems
  1660. collect (mapcar #'cdr line)))
  1661. (sketch-widths (shr-table-widths sketch natural suggested-widths)))
  1662. ;; This probably won't work very well.
  1663. (when (> (+ (cl-loop for width across sketch-widths
  1664. summing (1+ width))
  1665. shr-indentation shr-table-separator-pixel-width)
  1666. (frame-width))
  1667. (setq truncate-lines t))
  1668. ;; Then render the table again with these new "hard" widths.
  1669. (shr-insert-table (shr-make-table dom sketch-widths t) sketch-widths)))
  1670. (defun shr-table-body (dom)
  1671. (let ((tbodies (seq-filter (lambda (child)
  1672. (eq (dom-tag child) 'tbody))
  1673. (dom-non-text-children dom))))
  1674. (cond
  1675. ((null tbodies)
  1676. dom)
  1677. ((= (length tbodies) 1)
  1678. (car tbodies))
  1679. (t
  1680. ;; Table with multiple tbodies. Convert into a single tbody.
  1681. `(tbody nil ,@(cl-reduce 'append
  1682. (mapcar 'dom-non-text-children tbodies)))))))
  1683. (defun shr-tag-table (dom)
  1684. (shr-ensure-paragraph)
  1685. (let* ((caption (dom-children (dom-child-by-tag dom 'caption)))
  1686. (header (dom-non-text-children (dom-child-by-tag dom 'thead)))
  1687. (body (dom-non-text-children (shr-table-body dom)))
  1688. (footer (dom-non-text-children (dom-child-by-tag dom 'tfoot)))
  1689. (bgcolor (dom-attr dom 'bgcolor))
  1690. (start (point))
  1691. (shr-stylesheet (nconc (list (cons 'background-color bgcolor))
  1692. shr-stylesheet))
  1693. (nheader (if header (shr-max-columns header)))
  1694. (nbody (if body (shr-max-columns body) 0))
  1695. (nfooter (if footer (shr-max-columns footer))))
  1696. (if (and (not caption)
  1697. (not header)
  1698. (not (dom-child-by-tag dom 'tbody))
  1699. (not (dom-child-by-tag dom 'tr))
  1700. (not footer))
  1701. ;; The table is totally invalid and just contains random junk.
  1702. ;; Try to output it anyway.
  1703. (shr-generic dom)
  1704. ;; It's a real table, so render it.
  1705. (if (dom-attr dom 'shr-fixed-table)
  1706. (shr-tag-table-1 dom)
  1707. ;; Only fix up the table once.
  1708. (let ((table
  1709. (nconc
  1710. (list 'table nil)
  1711. (if caption `((tr nil (td nil ,@caption))))
  1712. (cond
  1713. (header
  1714. (if footer
  1715. ;; header + body + footer
  1716. (if (= nheader nbody)
  1717. (if (= nbody nfooter)
  1718. `((tr nil (td nil (table nil
  1719. (tbody nil ,@header
  1720. ,@body ,@footer)))))
  1721. (nconc `((tr nil (td nil (table nil
  1722. (tbody nil ,@header
  1723. ,@body)))))
  1724. (if (= nfooter 1)
  1725. footer
  1726. `((tr nil (td nil (table
  1727. nil (tbody
  1728. nil ,@footer))))))))
  1729. (nconc `((tr nil (td nil (table nil (tbody
  1730. nil ,@header)))))
  1731. (if (= nbody nfooter)
  1732. `((tr nil (td nil (table
  1733. nil (tbody nil ,@body
  1734. ,@footer)))))
  1735. (nconc `((tr nil (td nil (table
  1736. nil (tbody nil
  1737. ,@body)))))
  1738. (if (= nfooter 1)
  1739. footer
  1740. `((tr nil (td nil (table
  1741. nil
  1742. (tbody
  1743. nil
  1744. ,@footer))))))))))
  1745. ;; header + body
  1746. (if (= nheader nbody)
  1747. `((tr nil (td nil (table nil (tbody nil ,@header
  1748. ,@body)))))
  1749. (if (= nheader 1)
  1750. `(,@header (tr nil (td nil (table
  1751. nil (tbody nil ,@body)))))
  1752. `((tr nil (td nil (table nil (tbody nil ,@header))))
  1753. (tr nil (td nil (table nil (tbody nil ,@body)))))))))
  1754. (footer
  1755. ;; body + footer
  1756. (if (= nbody nfooter)
  1757. `((tr nil (td nil (table
  1758. nil (tbody nil ,@body ,@footer)))))
  1759. (nconc `((tr nil (td nil (table nil (tbody nil ,@body)))))
  1760. (if (= nfooter 1)
  1761. footer
  1762. `((tr nil (td nil (table
  1763. nil (tbody nil ,@footer)))))))))
  1764. (caption
  1765. `((tr nil (td nil (table nil (tbody nil ,@body))))))
  1766. (body)))))
  1767. (dom-set-attribute table 'shr-fixed-table t)
  1768. (setcdr dom (cdr table))
  1769. (shr-tag-table-1 dom))))
  1770. (when bgcolor
  1771. (shr-colorize-region start (point) (cdr (assq 'color shr-stylesheet))
  1772. bgcolor))
  1773. ;; Finally, insert all the images after the table. The Emacs buffer
  1774. ;; model isn't strong enough to allow us to put the images actually
  1775. ;; into the tables. It inserts also non-td/th objects.
  1776. (when (zerop shr-table-depth)
  1777. (save-excursion
  1778. (shr-expand-alignments start (point)))
  1779. (let ((strings (shr-collect-extra-strings-in-table dom)))
  1780. (when strings
  1781. (save-restriction
  1782. (narrow-to-region (point) (point))
  1783. (insert (mapconcat #'identity strings "\n"))
  1784. (shr-fill-lines (point-min) (point-max))))))))
  1785. (defun shr-collect-extra-strings-in-table (dom &optional flags)
  1786. "Return extra strings in DOM of which the root is a table clause.
  1787. Render <img>s and <object>s, and strings and child <table>s of which
  1788. the parent <td> or <th> is lacking. FLAGS is a cons of two boolean
  1789. flags that control whether to collect or render objects."
  1790. ;; This function runs recursively and collects strings if the cdr of
  1791. ;; FLAGS is nil and the car is not nil, and it renders also child
  1792. ;; <table>s if the cdr is nil. Note: FLAGS may be nil, not a cons.
  1793. ;; FLAGS becomes (t . nil) if a <tr> clause is found in the children
  1794. ;; of DOM, and becomes (t . t) if a <td> or a <th> clause is found
  1795. ;; and the car is t then. When a <table> clause is found, FLAGS
  1796. ;; becomes nil if the cdr is t then. But if FLAGS is (t . nil) then,
  1797. ;; it renders the <table>.
  1798. (cl-loop for child in (dom-children dom) with recurse with tag
  1799. do (setq recurse nil)
  1800. if (stringp child)
  1801. unless (or (not (car flags)) (cdr flags))
  1802. when (string-match "\\(?:[^\t\n\r ]+[\t\n\r ]+\\)*[^\t\n\r ]+"
  1803. child)
  1804. collect (match-string 0 child)
  1805. end end
  1806. else if (consp child)
  1807. do (setq tag (dom-tag child)) and
  1808. unless (memq tag '(comment style))
  1809. if (eq tag 'img)
  1810. do (shr-tag-img child)
  1811. else if (eq tag 'object)
  1812. do (shr-tag-object child)
  1813. else
  1814. do (setq recurse t) and
  1815. if (eq tag 'tr)
  1816. do (setq flags '(t . nil))
  1817. else if (memq tag '(td th))
  1818. when (car flags)
  1819. do (setq flags '(t . t))
  1820. end
  1821. else if (eq tag 'table)
  1822. if (cdr flags)
  1823. do (setq flags nil)
  1824. else if (car flags)
  1825. do (setq recurse nil)
  1826. (shr-tag-table child)
  1827. end end end end end end end end end end
  1828. when recurse
  1829. append (shr-collect-extra-strings-in-table child flags)))
  1830. (defun shr-insert-table (table widths)
  1831. (let* ((collapse (equal (cdr (assq 'border-collapse shr-stylesheet))
  1832. "collapse"))
  1833. (shr-table-separator-length (if collapse 0 1))
  1834. (shr-table-vertical-line (if collapse "" shr-table-vertical-line))
  1835. (start (point)))
  1836. (setq shr-table-id (1+ shr-table-id))
  1837. (unless collapse
  1838. (shr-insert-table-ruler widths))
  1839. (dolist (row table)
  1840. (let ((start (point))
  1841. (align 0)
  1842. (column-number 0)
  1843. (height (let ((max 0))
  1844. (dolist (column row)
  1845. (setq max (max max (nth 2 column))))
  1846. max)))
  1847. (dotimes (_ (max height 1))
  1848. (shr-indent)
  1849. (insert shr-table-vertical-line "\n"))
  1850. (dolist (column row)
  1851. (when (> (nth 2 column) -1)
  1852. (goto-char start)
  1853. ;; Sum up all the widths from the column. (There may be
  1854. ;; more than one if this is a "colspan" column.)
  1855. (dotimes (_ (nth 4 column))
  1856. ;; The colspan directive may be wrong and there may not be
  1857. ;; that number of columns.
  1858. (when (<= column-number (1- (length widths)))
  1859. (setq align (+ align
  1860. (aref widths column-number)
  1861. (* 2 shr-table-separator-pixel-width))))
  1862. (setq column-number (1+ column-number)))
  1863. (let ((lines (nth 3 column))
  1864. (pixel-align (if (not shr-use-fonts)
  1865. (* align (frame-char-width))
  1866. align)))
  1867. (dolist (line lines)
  1868. (end-of-line)
  1869. (let ((start (point))
  1870. (background (and (> (length line) 0)
  1871. (shr-face-background
  1872. (get-text-property
  1873. (1- (length line)) 'face line))))
  1874. (space (propertize
  1875. " "
  1876. 'display `(space :align-to (,pixel-align))
  1877. 'shr-table-indent shr-table-id)))
  1878. (when background
  1879. (setq space (propertize space 'face background)))
  1880. (insert line space shr-table-vertical-line)
  1881. (shr-colorize-region
  1882. start (1- (point)) (nth 5 column) (nth 6 column)))
  1883. (forward-line 1))
  1884. ;; Add blank lines at padding at the bottom of the TD,
  1885. ;; possibly.
  1886. (dotimes (_ (- height (length lines)))
  1887. (end-of-line)
  1888. (let ((start (point)))
  1889. (insert (propertize " "
  1890. 'display `(space :align-to (,pixel-align))
  1891. 'shr-table-indent shr-table-id)
  1892. shr-table-vertical-line)
  1893. (shr-colorize-region
  1894. start (1- (point)) (nth 5 column) (nth 6 column)))
  1895. (forward-line 1))))))
  1896. (unless collapse
  1897. (shr-insert-table-ruler widths)))
  1898. (unless (= start (point))
  1899. (put-text-property start (1+ start) 'shr-table-id shr-table-id))))
  1900. (defun shr-face-background (face)
  1901. (and (consp face)
  1902. (or (and (plist-get face :background)
  1903. (list :background (plist-get face :background)))
  1904. (let ((background nil))
  1905. (dolist (elem face)
  1906. (when (and (consp elem)
  1907. (eq (car elem) :background)
  1908. (not background))
  1909. (setq background (cadr elem))))
  1910. (and background
  1911. (list :background background))))))
  1912. (defun shr-expand-alignments (start end)
  1913. (while (< (setq start (next-single-property-change
  1914. start 'shr-table-id nil end))
  1915. end)
  1916. (goto-char start)
  1917. (let* ((shr-use-fonts t)
  1918. (id (get-text-property (point) 'shr-table-id))
  1919. (base (shr-pixel-column))
  1920. elem)
  1921. (when id
  1922. (save-excursion
  1923. (while (setq elem (text-property-any
  1924. (point) end 'shr-table-indent id))
  1925. (goto-char elem)
  1926. (let ((align (get-text-property (point) 'display)))
  1927. (put-text-property (point) (1+ (point)) 'display
  1928. `(space :align-to (,(+ (car (nth 2 align))
  1929. base)))))
  1930. (forward-char 1)))))
  1931. (setq start (1+ start))))
  1932. (defun shr-insert-table-ruler (widths)
  1933. (when shr-table-horizontal-line
  1934. (when (and (bolp)
  1935. (> shr-indentation 0))
  1936. (shr-indent))
  1937. (insert shr-table-corner)
  1938. (let ((total-width 0))
  1939. (dotimes (i (length widths))
  1940. (setq total-width (+ total-width (aref widths i)
  1941. (* shr-table-separator-pixel-width 2)))
  1942. (insert (make-string (1+ (/ (aref widths i)
  1943. shr-table-separator-pixel-width))
  1944. shr-table-horizontal-line)
  1945. (propertize " "
  1946. 'display `(space :align-to (,total-width))
  1947. 'shr-table-indent shr-table-id)
  1948. shr-table-corner)))
  1949. (insert "\n")))
  1950. (defun shr-table-widths (table natural-table suggested-widths)
  1951. (let* ((length (length suggested-widths))
  1952. (widths (make-vector length 0))
  1953. (natural-widths (make-vector length 0)))
  1954. (dolist (row table)
  1955. (let ((i 0))
  1956. (dolist (column row)
  1957. (aset widths i (max (aref widths i) column))
  1958. (setq i (1+ i)))))
  1959. (dolist (row natural-table)
  1960. (let ((i 0))
  1961. (dolist (column row)
  1962. (aset natural-widths i (max (aref natural-widths i) column))
  1963. (setq i (1+ i)))))
  1964. (let ((extra (- (apply '+ (append suggested-widths nil))
  1965. (apply '+ (append widths nil))
  1966. (* shr-table-separator-pixel-width (1+ (length widths)))))
  1967. (expanded-columns 0))
  1968. ;; We have extra, unused space, so divide this space amongst the
  1969. ;; columns.
  1970. (when (> extra 0)
  1971. ;; If the natural width is wider than the rendered width, we
  1972. ;; want to allow the column to expand.
  1973. (dotimes (i length)
  1974. (when (> (aref natural-widths i) (aref widths i))
  1975. (setq expanded-columns (1+ expanded-columns))))
  1976. (dotimes (i length)
  1977. (when (> (aref natural-widths i) (aref widths i))
  1978. (aset widths i (min
  1979. (aref natural-widths i)
  1980. (+ (/ extra expanded-columns)
  1981. (aref widths i))))))))
  1982. widths))
  1983. (defun shr-make-table (dom widths &optional fill storage-attribute)
  1984. (or (cadr (assoc (list dom widths fill) shr-content-cache))
  1985. (let ((data (shr-make-table-1 dom widths fill)))
  1986. (push (list (list dom widths fill) data)
  1987. shr-content-cache)
  1988. (when storage-attribute
  1989. (dom-set-attribute dom storage-attribute data))
  1990. data)))
  1991. (defun shr-make-table-1 (dom widths &optional fill)
  1992. (let ((trs nil)
  1993. (rowspans (make-vector (length widths) 0))
  1994. (colspan-remaining 0)
  1995. colspan-width colspan-count
  1996. width colspan)
  1997. (dolist (row (dom-non-text-children dom))
  1998. (when (eq (dom-tag row) 'tr)
  1999. (let ((tds nil)
  2000. (columns (dom-non-text-children row))
  2001. (i 0)
  2002. (width-column 0)
  2003. column)
  2004. (while (< i (length widths))
  2005. ;; If we previously had a rowspan definition, then that
  2006. ;; means that we now have a "missing" td/th element here.
  2007. ;; So just insert a dummy, empty one to (sort of) emulate
  2008. ;; rowspan.
  2009. (setq column
  2010. (if (zerop (aref rowspans i))
  2011. (pop columns)
  2012. (aset rowspans i (1- (aref rowspans i)))
  2013. '(td)))
  2014. (when (and (not (stringp column))
  2015. (or (memq (dom-tag column) '(td th))
  2016. (not column)))
  2017. (when-let (span (dom-attr column 'rowspan))
  2018. (aset rowspans i (+ (aref rowspans i)
  2019. (1- (string-to-number span)))))
  2020. ;; Sanity check for invalid column-spans.
  2021. (when (>= width-column (length widths))
  2022. (setq width-column 0))
  2023. (setq width
  2024. (if column
  2025. (aref widths width-column)
  2026. (* 10 shr-table-separator-pixel-width)))
  2027. (when (setq colspan (dom-attr column 'colspan))
  2028. (setq colspan (min (string-to-number colspan)
  2029. ;; The colspan may be wrong, so
  2030. ;; truncate it to the length of the
  2031. ;; remaining columns.
  2032. (- (length widths) i)))
  2033. (dotimes (j (1- colspan))
  2034. (setq width
  2035. (if (> (+ i 1 j) (1- (length widths)))
  2036. ;; If we have a colspan spec that's longer
  2037. ;; than the table is wide, just use the last
  2038. ;; width as the width.
  2039. (aref widths (1- (length widths)))
  2040. ;; Sum up the widths of the columns we're
  2041. ;; spanning.
  2042. (+ width
  2043. shr-table-separator-length
  2044. (aref widths (+ i 1 j))))))
  2045. (setq width-column (+ width-column (1- colspan))
  2046. colspan-count colspan
  2047. colspan-remaining colspan))
  2048. (when column
  2049. (let ((data (shr-render-td column width fill)))
  2050. (if (and (not fill)
  2051. (> colspan-remaining 0))
  2052. (progn
  2053. (setq colspan-width (car data))
  2054. (let ((this-width (/ colspan-width colspan-count)))
  2055. (push (cons this-width (cadr data)) tds)
  2056. (setq colspan-remaining (1- colspan-remaining))))
  2057. (if (not fill)
  2058. (push (cons (car data) (cadr data)) tds)
  2059. (push data tds)))))
  2060. (when (and colspan
  2061. (> colspan 1))
  2062. (dotimes (_ (1- colspan))
  2063. (setq i (1+ i))
  2064. (push
  2065. (if fill
  2066. (list 0 0 -1 nil 1 nil nil)
  2067. '(0 . 0))
  2068. tds)))
  2069. (setq i (1+ i)
  2070. width-column (1+ width-column))))
  2071. (push (nreverse tds) trs))))
  2072. (nreverse trs)))
  2073. (defun shr-pixel-buffer-width ()
  2074. (if (not shr-use-fonts)
  2075. (save-excursion
  2076. (goto-char (point-min))
  2077. (let ((max 0))
  2078. (while (not (eobp))
  2079. (end-of-line)
  2080. (setq max (max max (current-column)))
  2081. (forward-line 1))
  2082. max))
  2083. (if (get-buffer-window)
  2084. (car (window-text-pixel-size nil (point-min) (point-max)))
  2085. (save-window-excursion
  2086. ;; Avoid errors if the selected window is a dedicated one,
  2087. ;; and they just want to insert a document into it.
  2088. (set-window-dedicated-p nil nil)
  2089. (set-window-buffer nil (current-buffer))
  2090. (car (window-text-pixel-size nil (point-min) (point-max)))))))
  2091. (defun shr-render-td (dom width fill)
  2092. (let ((cache (intern (format "shr-td-cache-%s-%s" width fill))))
  2093. (or (dom-attr dom cache)
  2094. (and fill
  2095. (let (result)
  2096. (dolist (attr (dom-attributes dom))
  2097. (let ((name (symbol-name (car attr))))
  2098. (when (string-match "shr-td-cache-\\([0-9]+\\)-nil" name)
  2099. (let ((cache-width (string-to-number
  2100. (match-string 1 name))))
  2101. (when (and (>= cache-width width)
  2102. (<= (car (cdr attr)) width))
  2103. (setq result (cdr attr)))))))
  2104. result))
  2105. (let ((result (shr-render-td-1 dom width fill)))
  2106. (dom-set-attribute dom cache result)
  2107. result))))
  2108. (defun shr-render-td-1 (dom width fill)
  2109. (with-temp-buffer
  2110. (let ((bgcolor (dom-attr dom 'bgcolor))
  2111. (fgcolor (dom-attr dom 'fgcolor))
  2112. (style (dom-attr dom 'style))
  2113. (shr-stylesheet shr-stylesheet)
  2114. (max-width 0)
  2115. natural-width)
  2116. (when style
  2117. (setq style (and (string-match "color" style)
  2118. (shr-parse-style style))))
  2119. (when bgcolor
  2120. (setq style (nconc (list (cons 'background-color bgcolor))
  2121. style)))
  2122. (when fgcolor
  2123. (setq style (nconc (list (cons 'color fgcolor)) style)))
  2124. (when style
  2125. (setq shr-stylesheet (append style shr-stylesheet)))
  2126. (let ((shr-internal-width width)
  2127. (shr-indentation 0))
  2128. (shr-descend dom))
  2129. (save-window-excursion
  2130. ;; Avoid errors if the selected window is a dedicated one,
  2131. ;; and they just want to insert a document into it.
  2132. (set-window-dedicated-p nil nil)
  2133. (set-window-buffer nil (current-buffer))
  2134. (unless fill
  2135. (setq natural-width
  2136. (or (dom-attr dom 'shr-td-cache-natural)
  2137. (let ((natural (max (shr-pixel-buffer-width)
  2138. (shr-dom-max-natural-width dom 0))))
  2139. (dom-set-attribute dom 'shr-td-cache-natural natural)
  2140. natural))))
  2141. (if (and natural-width
  2142. (<= natural-width width))
  2143. (setq max-width natural-width)
  2144. (let ((shr-internal-width width))
  2145. (shr-fill-lines (point-min) (point-max))
  2146. (setq max-width (shr-pixel-buffer-width)))))
  2147. (goto-char (point-max))
  2148. ;; Delete padding at the bottom of the TDs.
  2149. (delete-region
  2150. (point)
  2151. (progn
  2152. (skip-chars-backward " \t\n")
  2153. (end-of-line)
  2154. (point)))
  2155. (goto-char (point-min))
  2156. (list max-width
  2157. natural-width
  2158. (count-lines (point-min) (point-max))
  2159. (split-string (buffer-string) "\n")
  2160. (if (dom-attr dom 'colspan)
  2161. (string-to-number (dom-attr dom 'colspan))
  2162. 1)
  2163. (cdr (assq 'color shr-stylesheet))
  2164. (cdr (assq 'background-color shr-stylesheet))))))
  2165. (defun shr-dom-max-natural-width (dom max)
  2166. (if (eq (dom-tag dom) 'table)
  2167. (max max (or
  2168. (cl-loop
  2169. for line in (dom-attr dom 'shr-suggested-widths)
  2170. maximize (+
  2171. shr-table-separator-length
  2172. (cl-loop for elem in line
  2173. summing
  2174. (+ (cdr elem)
  2175. (* 2 shr-table-separator-length)))))
  2176. 0))
  2177. (dolist (child (dom-children dom))
  2178. (unless (stringp child)
  2179. (setq max (max (shr-dom-max-natural-width child max)))))
  2180. max))
  2181. (defun shr-buffer-width ()
  2182. (goto-char (point-min))
  2183. (let ((max 0))
  2184. (while (not (eobp))
  2185. (end-of-line)
  2186. (setq max (max max (current-column)))
  2187. (forward-line 1))
  2188. max))
  2189. (defun shr-pro-rate-columns (columns)
  2190. (let ((total-percentage 0)
  2191. (widths (make-vector (length columns) 0)))
  2192. (dotimes (i (length columns))
  2193. (setq total-percentage (+ total-percentage (aref columns i))))
  2194. (setq total-percentage (/ 1.0 total-percentage))
  2195. (dotimes (i (length columns))
  2196. (aset widths i (max (truncate (* (aref columns i)
  2197. total-percentage
  2198. (- shr-internal-width
  2199. (* (1+ (length columns))
  2200. shr-table-separator-pixel-width))))
  2201. 10)))
  2202. widths))
  2203. ;; Return a summary of the number and shape of the TDs in the table.
  2204. (defun shr-column-specs (dom)
  2205. (let ((columns (make-vector (shr-max-columns dom) 1)))
  2206. (dolist (row (dom-non-text-children dom))
  2207. (when (eq (dom-tag row) 'tr)
  2208. (let ((i 0))
  2209. (dolist (column (dom-non-text-children row))
  2210. (when (memq (dom-tag column) '(td th))
  2211. (let ((width (dom-attr column 'width)))
  2212. (when (and width
  2213. (string-match "\\([0-9]+\\)%" width)
  2214. (not (zerop (setq width (string-to-number
  2215. (match-string 1 width))))))
  2216. (aset columns i (/ width 100.0))))
  2217. (setq i (1+ i)))))))
  2218. columns))
  2219. (defun shr-count (dom elem)
  2220. (let ((i 0))
  2221. (dolist (sub (dom-children dom))
  2222. (when (and (not (stringp sub))
  2223. (eq (dom-tag sub) elem))
  2224. (setq i (1+ i))))
  2225. i))
  2226. (defun shr-max-columns (dom)
  2227. (let ((max 0))
  2228. (dolist (row (dom-children dom))
  2229. (when (and (not (stringp row))
  2230. (eq (dom-tag row) 'tr))
  2231. (setq max (max max (+ (shr-count row 'td)
  2232. (shr-count row 'th))))))
  2233. max))
  2234. (provide 'shr)
  2235. ;;; shr.el ends here