├── LICENCE ├── README.org └── org-remoteimg.el /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dean Gao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * org-remoteimg 2 | Display remote inline images in org-mode with automatic caching. 3 | 4 | ** About 5 | This package displays remote images inline in org-mode with automatic caching. The next time you visit the file or fetch the image, it will be instantly fetched from the cache. 6 | 7 | This feature is present in Doom Emacs, and when I switched to GNU emacs, I found that I was quite fond of that feature, so I made it here. You can finally do ~[[https://gaodean.me/gen/cat.jpg]]~, and have it display inline just like local images. The code here is adapted from the Doom Emacs codebase, and I just thought to make a small plugin to make it simpler to use for GNU emacs users. 8 | 9 | [[https://gaodean.me/gen/cat.jpg]] 10 | 11 | (this image would display in emacs with the plugin enabled) 12 | 13 | ** Install 14 | *** use-package 15 | #+begin_src elisp 16 | (use-package org-remoteimg 17 | :straight (org-remoteimg :type git :host github :repo "gaoDean/org-remoteimg")) 18 | #+end_src 19 | 20 | *** Manual 21 | #+begin_src elisp 22 | (add-to-list 'load-path "/path/to/plugin/") 23 | (require 'org-remoteimg) 24 | #+end_src 25 | 26 | ** Caching 27 | After the install, remember to do: 28 | #+begin_src elisp 29 | ;; optional: set this to wherever you want the cache to be stored 30 | ;; (setq url-cache-directory "~/.cache/emacs/url") 31 | 32 | (setq org-display-remote-inline-images 'cache) ;; enable caching 33 | 34 | ;; or this if you don't want caching 35 | ;; (setq org-display-remote-inline-images 'download) 36 | 37 | ;; or this if you want to disable this plugin 38 | ;; (setq org-display-remote-inline-images 'skip) 39 | #+end_src 40 | 41 | By default, this plugin will only cache images. If you want to cache any web request, just do: 42 | #+begin_src elisp 43 | ;; this is a emacs built-in feature 44 | (setq url-automatic-caching t) 45 | #+end_src 46 | 47 | ** Other 48 | + This plugin works quite well with [[https://github.com/gaoDean/org-imgtog][org-imgtog]]. Remember to setup caching if you want to use it though, otherwise you'll have a bad time. 49 | + Credit to Tobias Zawada and his [[https://github.com/TobiasZawada/org-yt][org-yt]] plugin for the org-mode image display functions. 50 | + Credit to the Doom Emacs community for the basis of the remote image fetching function. 51 | -------------------------------------------------------------------------------- /org-remoteimg.el: -------------------------------------------------------------------------------- 1 | ;;; org-remoteimg.el --- Display remote inline images in org-mode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2023 Dean Gao - MIT License 4 | ;; Author: Dean Gao 5 | ;; Description: Inline display of remote images in org-mode 6 | ;; Homepage: https://github.com/gaoDean/org-imgtog 7 | ;; Package-Requires: ((emacs "25.1")) 8 | 9 | ;; Permission is hereby granted, free of charge, to any person obtaining a copy 10 | ;; of this software and associated documentation files (the "Software"), to deal 11 | ;; in the Software without restriction, including without limitation the rights 12 | ;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | ;; copies of the Software, and to permit persons to whom the Software is 14 | ;; furnished to do so, subject to the following conditions: 15 | ;; 16 | ;; The above copyright notice and this permission notice shall be included in all 17 | ;; copies or substantial portions of the Software. 18 | ;; 19 | ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | ;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | ;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | ;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | ;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | ;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | ;; SOFTWARE. 26 | 27 | ;;; Commentary: 28 | 29 | ;; This package displays remote images inline in org-mode with automatic caching. 30 | ;; This means you can do [[https://my-file.png]], and have it display inline. 31 | ;; The next time you visit the file or fetch the image, it will be instantly 32 | ;; fetched from the cache. 33 | 34 | ;;; Code: 35 | 36 | (require 'org) 37 | (require 'org-element) 38 | (require 'url) 39 | (require 'url-cache) 40 | 41 | (unless (fboundp 'image-supported-file-p) 42 | ;; `image-supported-file-p' isn't available before Emacs 28 43 | ;; Add alias to not break Emacs <28. 44 | (defalias 'image-type-from-file-name 'image-supported-file-p)) 45 | 46 | ;; Compatibility definitions for Org-mode < 9.8 47 | (unless (boundp 'org-link-preview-overlays) 48 | (defalias 'org-inline-image-overlays 'org-link-preview-overlays)) 49 | 50 | (defun org-image-update-overlay (file link &optional data-p refresh) 51 | "Create image overlay for FILE associtated with org-element LINK. 52 | If DATA-P is non-nil FILE is not a file name but a string with the image data. 53 | If REFRESH is non-nil don't download the file but refresh the image. 54 | See also `create-image'. 55 | This function is almost a duplicate of a part of `org-display-inline-images'." 56 | (when (or data-p (file-exists-p file)) 57 | (let ((width 58 | ;; Apply `org-image-actual-width' specifications. 59 | (cond 60 | ((eq org-image-actual-width t) nil) 61 | ((listp org-image-actual-width) 62 | (or 63 | ;; First try to find a width among 64 | ;; attributes associated to the paragraph 65 | ;; containing link. 66 | (let ((paragraph 67 | (let ((e link)) 68 | (while (and (setq e (org-element-property 69 | :parent e)) 70 | (not (eq (org-element-type e) 71 | 'paragraph)))) 72 | e))) 73 | (when paragraph 74 | (save-excursion 75 | (goto-char (org-element-property :begin paragraph)) 76 | (when 77 | (re-search-forward 78 | "^[ \t]*#\\+attr_.*?: +.*?:width +\\(\\S-+\\)" 79 | (org-element-property 80 | :post-affiliated paragraph) 81 | t) 82 | (string-to-number (match-string 1)))))) 83 | ;; Otherwise, fall-back to provided number. 84 | (car org-image-actual-width))) 85 | ((numberp org-image-actual-width) 86 | org-image-actual-width))) 87 | (old (get-char-property-and-overlay 88 | (org-element-property :begin link) 89 | 'org-image-overlay))) 90 | (if (and (car-safe old) refresh) 91 | (image-flush (overlay-get (cdr old) 'display)) 92 | (let ((image (create-image file 93 | (and (image-type-available-p 'imagemagick) 94 | width 95 | 'imagemagick) 96 | data-p 97 | :width width))) 98 | (when image 99 | (let* ((link 100 | ;; If inline image is the description 101 | ;; of another link, be sure to 102 | ;; consider the latter as the one to 103 | ;; apply the overlay on. 104 | (let ((parent 105 | (org-element-property :parent link))) 106 | (if (eq (org-element-type parent) 'link) 107 | parent 108 | link))) 109 | (ov (make-overlay 110 | (org-element-property :begin link) 111 | (progn 112 | (goto-char 113 | (org-element-property :end link)) 114 | (skip-chars-backward " \t") 115 | (point))))) 116 | (overlay-put ov 'display image) 117 | (overlay-put ov 'face 'default) 118 | (overlay-put ov 'org-image-overlay t) 119 | (overlay-put 120 | ov 'modification-hooks 121 | (list 'org-display-inline-remove-overlay)) 122 | (push ov org-link-preview-overlays) 123 | ov))))))) 124 | 125 | (defun org-display-user-inline-images (&optional _include-linked _refresh beg end) 126 | "Like `org-display-inline-images' but for image data links. 127 | _INCLUDE-LINKED and _REFRESH are ignored. 128 | Restrict to region between BEG and END if both are non-nil. 129 | Image data links have a :image-data-fun parameter. 130 | \(See `org-link-set-parameters'.) 131 | The value of the :image-data-fun parameter is a function 132 | taking the PROTOCOL, the LINK, and the DESCRIPTION as arguments. 133 | If that function returns nil the link is not interpreted as image. 134 | Otherwise the return value is the image data string to be displayed. 135 | 136 | Note that only bracket links are allowed as image data links 137 | with one of the formats 138 | [[PROTOCOL:LINK]] 139 | or 140 | [[PROTOCOL:LINK][DESCRIPTION]] 141 | are recognized. 142 | Full credit goes to org-yt by Tobias Zawada for this function." 143 | (interactive) 144 | (when (and (called-interactively-p 'any) 145 | (use-region-p)) 146 | (setq beg (region-beginning) 147 | end (region-end))) 148 | (when (display-graphic-p) 149 | (org-with-wide-buffer 150 | (goto-char (or beg (point-min))) 151 | (when-let* ((image-data-link-parameters 152 | (cl-loop for link-par-entry in org-link-parameters 153 | with fun 154 | when (setq fun (plist-get (cdr link-par-entry) :image-data-fun)) 155 | collect (cons (car link-par-entry) fun))) 156 | (image-data-link-re (regexp-opt (mapcar 'car image-data-link-parameters))) 157 | (re (format "\\[\\[\\(%s\\):\\([^]]+\\)\\]\\(?:\\[\\([^]]+\\)\\]\\)?\\]" 158 | image-data-link-re))) 159 | (while (re-search-forward re end t) 160 | (let* ((protocol (match-string-no-properties 1)) 161 | (link (match-string-no-properties 2)) 162 | (description (match-string-no-properties 3)) 163 | (image-data-link (assoc-string protocol image-data-link-parameters)) 164 | (el (save-excursion (goto-char (match-beginning 1)) (org-element-context))) 165 | image-data) 166 | (when el 167 | (setq image-data 168 | (or (let ((old (get-char-property-and-overlay 169 | (org-element-property :begin el) 170 | 'org-image-overlay))) 171 | (and old 172 | (car-safe old) 173 | (overlay-get (cdr old) 'display))) 174 | (funcall (cdr image-data-link) protocol link description))) 175 | (when image-data 176 | (let ((ol (org-image-update-overlay image-data el t t))) 177 | (when (and ol description) 178 | (overlay-put ol 'after-string description))))))))))) 179 | 180 | (defun org-remoteimg--fetch-image (protocol link _description) 181 | "Synchronously retrieve image from cache or web." 182 | (when (and (image-supported-file-p link) 183 | (not (eq org-display-remote-inline-images 'skip))) 184 | (let* ((cache (eq org-display-remote-inline-images 'cache)) 185 | (url-automatic-caching (if cache 186 | t 187 | url-automatic-caching)) 188 | (url (concat protocol ":" link)) 189 | (silent-output (file-exists-p (url-cache-create-filename url)))) 190 | (if-let* ((buf (url-retrieve-synchronously url 191 | silent-output 192 | nil 193 | 30))) 194 | (with-current-buffer buf 195 | (goto-char (point-min)) 196 | (re-search-forward "\r?\n\r?\n" nil t) 197 | (buffer-substring-no-properties (point) (point-max))) 198 | (error "Download of image \"%s\" failed" link) 199 | nil)))) 200 | 201 | (defun org-link-preview-http (ov _path link) 202 | "Generate preview OV for LINK in PATH." 203 | (when-let* ((raw-link (org-element-property :raw-link link)) 204 | (image-type (image-supported-file-p raw-link)) 205 | ;; cache? 206 | (image-buffer (org-remoteimg--fetch-image nil raw-link nil))) 207 | (overlay-put ov 'display (create-image image-buffer 208 | ;; scale? 209 | ;; (and (image-type-available-p 'imagemagick) 210 | ;; width 211 | ;; 'imagemagick) 212 | ;; :width width 213 | image-type 214 | t)) 215 | t)) 216 | 217 | (if (fboundp 'org-display-inline-images) 218 | (progn 219 | (advice-add #'org-display-inline-images :after #'org-display-user-inline-images) 220 | (org-link-set-parameters "http" :image-data-fun #'org-remoteimg--fetch-image) 221 | (org-link-set-parameters "https" :image-data-fun #'org-remoteimg--fetch-image)) 222 | (org-link-set-parameters "http" :preview #'org-link-preview-http) 223 | (org-link-set-parameters "https" :preview #'org-link-preview-http)) 224 | 225 | (provide 'org-remoteimg) 226 | 227 | ;;; org-remoteimg.el ends here 228 | --------------------------------------------------------------------------------