diff options
| author | Lars Ingebrigtsen | 2016-02-19 16:04:11 +1100 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2016-02-19 16:04:11 +1100 |
| commit | 5e8a62917ade3751a328aa90830b51bbed90e15d (patch) | |
| tree | 79f6fe39aff4aebe13d22183399c98213cb17f6d | |
| parent | 466fc43182d1677c107856d4752ef4b6812baefe (diff) | |
| download | emacs-5e8a62917ade3751a328aa90830b51bbed90e15d.tar.gz emacs-5e8a62917ade3751a328aa90830b51bbed90e15d.zip | |
Add a library for creating and manipulating SVG images
* doc/lispref/display.texi (SVG Images): New section.
* lisp/svg.el: New file.
| -rw-r--r-- | doc/lispref/display.texi | 126 | ||||
| -rw-r--r-- | etc/NEWS | 4 | ||||
| -rw-r--r-- | lisp/svg.el | 230 |
3 files changed, 357 insertions, 3 deletions
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 26f3de40e91..17025cd1994 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi | |||
| @@ -4761,6 +4761,7 @@ displayed (@pxref{Display Feature Testing}). | |||
| 4761 | * XPM Images:: Special features for XPM format. | 4761 | * XPM Images:: Special features for XPM format. |
| 4762 | * PostScript Images:: Special features for PostScript format. | 4762 | * PostScript Images:: Special features for PostScript format. |
| 4763 | * ImageMagick Images:: Special features available through ImageMagick. | 4763 | * ImageMagick Images:: Special features available through ImageMagick. |
| 4764 | * SVG Images:: Creating and manipulating SVG images. | ||
| 4764 | * Other Image Types:: Various other formats are supported. | 4765 | * Other Image Types:: Various other formats are supported. |
| 4765 | * Defining Images:: Convenient ways to define an image for later use. | 4766 | * Defining Images:: Convenient ways to define an image for later use. |
| 4766 | * Showing Images:: Convenient ways to display an image once it is defined. | 4767 | * Showing Images:: Convenient ways to display an image once it is defined. |
| @@ -5220,6 +5221,128 @@ Specifies a rotation angle in degrees. | |||
| 5220 | @xref{Multi-Frame Images}. | 5221 | @xref{Multi-Frame Images}. |
| 5221 | @end table | 5222 | @end table |
| 5222 | 5223 | ||
| 5224 | @node SVG Images | ||
| 5225 | @subsection SVG Images | ||
| 5226 | @cindex SVG images | ||
| 5227 | |||
| 5228 | SVG (Scalable Vector Graphics) is an XML format for specifying images. | ||
| 5229 | If you build Emacs with SVG support, you can create and manipulate | ||
| 5230 | these images with the following commands. | ||
| 5231 | |||
| 5232 | @defun svg-create width height &rest args | ||
| 5233 | Create a new, empty SVG image with the specified dimensions. | ||
| 5234 | @var{args} is an argument plist with you can specify following: | ||
| 5235 | |||
| 5236 | @table @code | ||
| 5237 | @item :stroke-width | ||
| 5238 | The default width (in pixels) of any lines created. | ||
| 5239 | |||
| 5240 | @item :stroke | ||
| 5241 | The default stroke color on any lines created. | ||
| 5242 | @end table | ||
| 5243 | |||
| 5244 | This function returns an SVG structure, and all the following commands | ||
| 5245 | work on that structure. | ||
| 5246 | @end defun | ||
| 5247 | |||
| 5248 | @defun svg-gradient svg id type stops | ||
| 5249 | Create a gradient in @var{svg} with identifier @var{id}. @var{type} | ||
| 5250 | specifies the gradient type, and can be either @code{linear} or | ||
| 5251 | @code{radial}. @var{stops} is a list of percentage/color pairs. | ||
| 5252 | |||
| 5253 | The following will create a linear gradient that goes from red at the | ||
| 5254 | start, to green 25% of the way, to blue at the end: | ||
| 5255 | |||
| 5256 | @lisp | ||
| 5257 | (svg-gradient svg "gradient1" 'linear | ||
| 5258 | '((0 . "red") (25 . "green") (100 . "blue"))) | ||
| 5259 | @end lisp | ||
| 5260 | |||
| 5261 | The gradient created (and inserted into the SVG object) can later be | ||
| 5262 | used by all functions that create shapes. | ||
| 5263 | @end defun | ||
| 5264 | |||
| 5265 | All the following functions take an optional list of keyword | ||
| 5266 | parameters that alter the various attributes from their default | ||
| 5267 | values. Valid attributes include: | ||
| 5268 | |||
| 5269 | @table @code | ||
| 5270 | @item :stroke-width | ||
| 5271 | The width (in pixels) of lines drawn, and outlines around solid | ||
| 5272 | shapes. | ||
| 5273 | |||
| 5274 | @item :stroke-color | ||
| 5275 | The color of lines drawn, and outlines around solid shapes. | ||
| 5276 | |||
| 5277 | @item :fill-color | ||
| 5278 | The color used for solid shapes. | ||
| 5279 | |||
| 5280 | @item :id | ||
| 5281 | The identified of the shape. | ||
| 5282 | |||
| 5283 | @item :gradient | ||
| 5284 | If given, this should be the identifier of a previously defined | ||
| 5285 | gradient object. | ||
| 5286 | @end table | ||
| 5287 | |||
| 5288 | @defun svg-rectangle svg x y width height &rest args | ||
| 5289 | Add a rectangle to @var{svg} where the upper left corner is at | ||
| 5290 | position @var{x}/@var{y} and is of size @var{width}/@var{height}. | ||
| 5291 | |||
| 5292 | @lisp | ||
| 5293 | (svg-rectangle svg 100 100 500 500 :gradient "gradient1") | ||
| 5294 | @end lisp | ||
| 5295 | @end defun | ||
| 5296 | |||
| 5297 | @defun svg-circle svg x y radius &rest args | ||
| 5298 | Add a circle to @var{svg} where the center is at @var{x}/@var{y} | ||
| 5299 | and the radius is @var{radius}. | ||
| 5300 | @end defun | ||
| 5301 | |||
| 5302 | @defun svg-ellipse svg x y x-radius y-radius &rest args | ||
| 5303 | Add a circle to @var{svg} where the center is at @var{x}/@var{y} and | ||
| 5304 | the horizontal radius is @var{x-radius} and the vertical radius is | ||
| 5305 | @var{y-radius}. | ||
| 5306 | @end defun | ||
| 5307 | |||
| 5308 | @defun svg-line svg x1 y1 x2 y2 &rest args | ||
| 5309 | Add a line to @var{svg} that starts at @var{x1}/@var{y1} and extends | ||
| 5310 | to @var{x2}/@var{y2}. | ||
| 5311 | @end defun | ||
| 5312 | |||
| 5313 | @defun svg-polyline svg points &rest args | ||
| 5314 | Add a multiple segment line to @var{svg} that goes through | ||
| 5315 | @var{points}, which is a list of X/Y position pairs. | ||
| 5316 | |||
| 5317 | @lisp | ||
| 5318 | (svg-polyline svg '((200 . 100) (500 . 450) (80 . 100)) | ||
| 5319 | :stroke-color "green") | ||
| 5320 | @end lisp | ||
| 5321 | @end defun | ||
| 5322 | |||
| 5323 | @defun svg-polygon svg points &rest args | ||
| 5324 | Add a polygon to @var{svg} where @var{points} is a list of X/Y pairs | ||
| 5325 | that describe the outer circumference of the polygon. | ||
| 5326 | |||
| 5327 | @lisp | ||
| 5328 | (svg-polygon svg '((100 . 100) (200 . 150) (150 . 90)) | ||
| 5329 | :stroke-color "blue" :fill-color "red"") | ||
| 5330 | @end lisp | ||
| 5331 | @end defun | ||
| 5332 | |||
| 5333 | Finally, the @code{svg-image} takes an SVG object as its parameter and | ||
| 5334 | returns an image object suitable for use in functions like | ||
| 5335 | @code{insert-image}. Here's a complete example that creates and | ||
| 5336 | inserts an image with a circle: | ||
| 5337 | |||
| 5338 | @lisp | ||
| 5339 | (let ((svg (svg-create 400 400 :stroke-width 10))) | ||
| 5340 | (svg-gradient svg "gradient1" 'linear '((0 . "red") (100 . "blue"))) | ||
| 5341 | (svg-circle svg 200 200 100 :gradient "gradient1" :stroke-color "green") | ||
| 5342 | (insert-image (svg-image svg))) | ||
| 5343 | @end lisp | ||
| 5344 | |||
| 5345 | |||
| 5223 | @node Other Image Types | 5346 | @node Other Image Types |
| 5224 | @subsection Other Image Types | 5347 | @subsection Other Image Types |
| 5225 | @cindex PBM | 5348 | @cindex PBM |
| @@ -5256,9 +5379,6 @@ Image type @code{jpeg}. | |||
| 5256 | @item PNG | 5379 | @item PNG |
| 5257 | Image type @code{png}. | 5380 | Image type @code{png}. |
| 5258 | 5381 | ||
| 5259 | @item SVG | ||
| 5260 | Image type @code{svg}. | ||
| 5261 | |||
| 5262 | @item TIFF | 5382 | @item TIFF |
| 5263 | Image type @code{tiff}. | 5383 | Image type @code{tiff}. |
| 5264 | Supports the @code{:index} property. @xref{Multi-Frame Images}. | 5384 | Supports the @code{:index} property. @xref{Multi-Frame Images}. |
| @@ -851,6 +851,10 @@ keymap put into the text properties (or overlays) that span the | |||
| 851 | image. This keymap binds keystrokes for manipulating size and | 851 | image. This keymap binds keystrokes for manipulating size and |
| 852 | rotation, as well as saving the image to a file. | 852 | rotation, as well as saving the image to a file. |
| 853 | 853 | ||
| 854 | +++ | ||
| 855 | *** A new library for creating and manipulating SVG images has been | ||
| 856 | added. See the "SVG Images" section in the lispref manual for details. | ||
| 857 | |||
| 854 | ** Lisp mode | 858 | ** Lisp mode |
| 855 | 859 | ||
| 856 | --- | 860 | --- |
diff --git a/lisp/svg.el b/lisp/svg.el new file mode 100644 index 00000000000..b6beaadc032 --- /dev/null +++ b/lisp/svg.el | |||
| @@ -0,0 +1,230 @@ | |||
| 1 | ;;; svg.el --- SVG image creation functions -*- lexical-binding: t -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2016 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> | ||
| 6 | ;; Keywords: image | ||
| 7 | |||
| 8 | ;; This file is part of GNU Emacs. | ||
| 9 | |||
| 10 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 11 | ;; it under the terms of the GNU General Public License as published by | ||
| 12 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 13 | ;; (at your option) any later version. | ||
| 14 | |||
| 15 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 18 | ;; GNU General Public License for more details. | ||
| 19 | |||
| 20 | ;; You should have received a copy of the GNU General Public License | ||
| 21 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ;;; Commentary: | ||
| 24 | |||
| 25 | ;;; Code: | ||
| 26 | |||
| 27 | (require 'cl-lib) | ||
| 28 | (require 'xml) | ||
| 29 | (require 'dom) | ||
| 30 | |||
| 31 | (defun svg-create (width height &rest args) | ||
| 32 | "Create a new, empty SVG image with dimentions WIDTHxHEIGHT. | ||
| 33 | ARGS can be used to provide `stroke' and `stroke-width' parameters to | ||
| 34 | any further elements added." | ||
| 35 | (dom-node 'svg | ||
| 36 | `((width . ,width) | ||
| 37 | (height . ,height) | ||
| 38 | (version . "1.1") | ||
| 39 | (xmlsn . "http://www.w3.org/2000/svg") | ||
| 40 | ,@(svg--arguments nil args)))) | ||
| 41 | |||
| 42 | (defun svg-gradient (svg id type stops) | ||
| 43 | "Add a gradient with ID to SVG. | ||
| 44 | TYPE is `linear' or `radial'. STOPS is a list of percentage/color | ||
| 45 | pairs." | ||
| 46 | (svg--def | ||
| 47 | svg | ||
| 48 | (apply | ||
| 49 | 'dom-node | ||
| 50 | (if (eq type 'linear) | ||
| 51 | 'linearGradient | ||
| 52 | 'radialGradient) | ||
| 53 | `((id . ,id) | ||
| 54 | (x1 . 0) | ||
| 55 | (x2 . 0) | ||
| 56 | (y1 . 0) | ||
| 57 | (y2 . 1)) | ||
| 58 | (mapcar | ||
| 59 | (lambda (stop) | ||
| 60 | (dom-node 'stop `((offset . ,(format "%s%%" (car stop))) | ||
| 61 | (stop-color . ,(cdr stop))))) | ||
| 62 | stops)))) | ||
| 63 | |||
| 64 | (defun svg-rectangle (svg x y width height &rest args) | ||
| 65 | "Create a rectangle on SVG, starting at position X/Y, of WIDTH/HEIGHT. | ||
| 66 | ARGS is a plist of modifiers. Possible values are | ||
| 67 | |||
| 68 | :stroke-width PIXELS. The line width. | ||
| 69 | :stroke-color COLOR. The line color. | ||
| 70 | :gradient ID. The gradient ID to use." | ||
| 71 | (svg--append | ||
| 72 | svg | ||
| 73 | (dom-node 'rect | ||
| 74 | `((width . ,width) | ||
| 75 | (height . ,height) | ||
| 76 | (x . ,x) | ||
| 77 | (y . ,y) | ||
| 78 | ,@(svg--arguments svg args))))) | ||
| 79 | |||
| 80 | (defun svg-circle (svg x y radius &rest args) | ||
| 81 | "Create a circle of RADIUS on SVG. | ||
| 82 | X/Y denote the center of the circle." | ||
| 83 | (svg--append | ||
| 84 | svg | ||
| 85 | (dom-node 'circle | ||
| 86 | `((cx . ,x) | ||
| 87 | (cy . ,y) | ||
| 88 | (r . ,radius) | ||
| 89 | ,@(svg--arguments svg args))))) | ||
| 90 | |||
| 91 | (defun svg-ellipse (svg x y x-radius y-radius &rest args) | ||
| 92 | "Create an ellipse of X-RADIUS/Y-RADIUS on SVG. | ||
| 93 | X/Y denote the center of the ellipse." | ||
| 94 | (svg--append | ||
| 95 | svg | ||
| 96 | (dom-node 'ellipse | ||
| 97 | `((cx . ,x) | ||
| 98 | (cy . ,y) | ||
| 99 | (rx . ,x-radius) | ||
| 100 | (ry . ,y-radius) | ||
| 101 | ,@(svg--arguments svg args))))) | ||
| 102 | |||
| 103 | (defun svg-line (svg x1 y1 x2 y2 &rest args) | ||
| 104 | "Create a line of starting in X1/Y1, ending at X2/Y2 in SVG." | ||
| 105 | (svg--append | ||
| 106 | svg | ||
| 107 | (dom-node 'line | ||
| 108 | `((x1 . ,x1) | ||
| 109 | (x2 . ,y1) | ||
| 110 | (y1 . ,x2) | ||
| 111 | (y2 . ,y2) | ||
| 112 | ,@(svg--arguments svg args))))) | ||
| 113 | |||
| 114 | (defun svg-polyline (svg points &rest args) | ||
| 115 | "Create a polyline going through POINTS on SVG. | ||
| 116 | POINTS is a list of x/y pairs." | ||
| 117 | (svg--append | ||
| 118 | svg | ||
| 119 | (dom-node | ||
| 120 | 'polyline | ||
| 121 | `((points . ,(mapconcat (lambda (pair) | ||
| 122 | (format "%s %s" (car pair) (cdr pair))) | ||
| 123 | points | ||
| 124 | ", ")) | ||
| 125 | ,@(svg--arguments svg args))))) | ||
| 126 | |||
| 127 | (defun svg-polygon (svg points &rest args) | ||
| 128 | "Create a polygon going through POINTS on SVG. | ||
| 129 | POINTS is a list of x/y pairs." | ||
| 130 | (svg--append | ||
| 131 | svg | ||
| 132 | (dom-node | ||
| 133 | 'polygon | ||
| 134 | `((points . ,(mapconcat (lambda (pair) | ||
| 135 | (format "%s %s" (car pair) (cdr pair))) | ||
| 136 | points | ||
| 137 | ", ")) | ||
| 138 | ,@(svg--arguments svg args))))) | ||
| 139 | |||
| 140 | (defun svg--append (svg node) | ||
| 141 | (let ((old (and (dom-attr node 'id) | ||
| 142 | (dom-by-id svg | ||
| 143 | (concat "\\`" (regexp-quote (dom-attr node 'id)) | ||
| 144 | "\\'"))))) | ||
| 145 | (if old | ||
| 146 | (dom-set-attributes old (dom-attributes node)) | ||
| 147 | (dom-append-child svg node))) | ||
| 148 | (svg-possibly-update-image svg)) | ||
| 149 | |||
| 150 | (defun svg--arguments (svg args) | ||
| 151 | (let ((stroke-width (or (plist-get args :stroke-width) | ||
| 152 | (dom-attr svg 'stroke-width))) | ||
| 153 | (stroke-color (or (plist-get args :stroke-color) | ||
| 154 | (dom-attr svg 'stroke-color))) | ||
| 155 | (fill-color (plist-get args :fill-color)) | ||
| 156 | attr) | ||
| 157 | (when stroke-width | ||
| 158 | (push (cons 'stroke-width stroke-width) attr)) | ||
| 159 | (when stroke-color | ||
| 160 | (push (cons 'stroke stroke-color) attr)) | ||
| 161 | (when fill-color | ||
| 162 | (push (cons 'fill fill-color) attr)) | ||
| 163 | (when (plist-get args :gradient) | ||
| 164 | (setq attr | ||
| 165 | (append | ||
| 166 | ;; We need a way to specify the gradient direction here... | ||
| 167 | `((x1 . 0) | ||
| 168 | (x2 . 0) | ||
| 169 | (y1 . 0) | ||
| 170 | (y2 . 1) | ||
| 171 | (fill . ,(format "url(#%s)" | ||
| 172 | (plist-get args :gradient)))) | ||
| 173 | attr))) | ||
| 174 | (cl-loop for (key value) on args by #'cddr | ||
| 175 | unless (memq key '(:stroke-color :stroke-width :gradient | ||
| 176 | :fill-color)) | ||
| 177 | ;; Drop the leading colon. | ||
| 178 | do (push (cons (intern (substring (symbol-name key) 1) obarray) | ||
| 179 | value) | ||
| 180 | attr)) | ||
| 181 | attr)) | ||
| 182 | |||
| 183 | (defun svg--def (svg def) | ||
| 184 | (dom-append-child | ||
| 185 | (or (dom-by-tag svg 'defs) | ||
| 186 | (let ((node (dom-node 'defs))) | ||
| 187 | (dom-add-child-before svg node) | ||
| 188 | node)) | ||
| 189 | def) | ||
| 190 | svg) | ||
| 191 | |||
| 192 | (defun svg-image (svg) | ||
| 193 | "Return an image object from SVG." | ||
| 194 | (create-image | ||
| 195 | (with-temp-buffer | ||
| 196 | (svg-print svg) | ||
| 197 | (buffer-string)) | ||
| 198 | 'svg t)) | ||
| 199 | |||
| 200 | (defun svg-insert-image (svg) | ||
| 201 | "Insert SVG as an image at point. | ||
| 202 | If the SVG is later changed, the image will also be updated." | ||
| 203 | (let ((image (svg-image svg)) | ||
| 204 | (marker (point-marker))) | ||
| 205 | (insert-image image) | ||
| 206 | (dom-set-attribute svg :image marker))) | ||
| 207 | |||
| 208 | (defun svg-possibly-update-image (svg) | ||
| 209 | (let ((marker (dom-attr svg :image))) | ||
| 210 | (when (and marker | ||
| 211 | (buffer-live-p (marker-buffer marker))) | ||
| 212 | (with-current-buffer (marker-buffer marker) | ||
| 213 | (put-text-property marker (1+ marker) 'display (svg-image svg)))))) | ||
| 214 | |||
| 215 | (defun svg-print (dom) | ||
| 216 | "Convert DOM into a string containing the xml representation." | ||
| 217 | (insert (format "<%s" (car dom))) | ||
| 218 | (dolist (attr (nth 1 dom)) | ||
| 219 | ;; Ignore attributes that start with a colon. | ||
| 220 | (unless (= (aref (format "%s" (car attr)) 0) ?:) | ||
| 221 | (insert (format " %s=\"%s\"" (car attr) (cdr attr))))) | ||
| 222 | (insert ">") | ||
| 223 | (dolist (elem (nthcdr 2 dom)) | ||
| 224 | (insert " ") | ||
| 225 | (svg-print elem)) | ||
| 226 | (insert (format "</%s>" (car dom)))) | ||
| 227 | |||
| 228 | (provide 'svg) | ||
| 229 | |||
| 230 | ;;; svg.el ends here | ||