aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Ingebrigtsen2016-02-19 16:04:11 +1100
committerLars Ingebrigtsen2016-02-19 16:04:11 +1100
commit5e8a62917ade3751a328aa90830b51bbed90e15d (patch)
tree79f6fe39aff4aebe13d22183399c98213cb17f6d
parent466fc43182d1677c107856d4752ef4b6812baefe (diff)
downloademacs-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.texi126
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/svg.el230
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
5228SVG (Scalable Vector Graphics) is an XML format for specifying images.
5229If you build Emacs with SVG support, you can create and manipulate
5230these images with the following commands.
5231
5232@defun svg-create width height &rest args
5233Create 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
5238The default width (in pixels) of any lines created.
5239
5240@item :stroke
5241The default stroke color on any lines created.
5242@end table
5243
5244This function returns an SVG structure, and all the following commands
5245work on that structure.
5246@end defun
5247
5248@defun svg-gradient svg id type stops
5249Create a gradient in @var{svg} with identifier @var{id}. @var{type}
5250specifies the gradient type, and can be either @code{linear} or
5251@code{radial}. @var{stops} is a list of percentage/color pairs.
5252
5253The following will create a linear gradient that goes from red at the
5254start, 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
5261The gradient created (and inserted into the SVG object) can later be
5262used by all functions that create shapes.
5263@end defun
5264
5265All the following functions take an optional list of keyword
5266parameters that alter the various attributes from their default
5267values. Valid attributes include:
5268
5269@table @code
5270@item :stroke-width
5271The width (in pixels) of lines drawn, and outlines around solid
5272shapes.
5273
5274@item :stroke-color
5275The color of lines drawn, and outlines around solid shapes.
5276
5277@item :fill-color
5278The color used for solid shapes.
5279
5280@item :id
5281The identified of the shape.
5282
5283@item :gradient
5284If given, this should be the identifier of a previously defined
5285gradient object.
5286@end table
5287
5288@defun svg-rectangle svg x y width height &rest args
5289Add a rectangle to @var{svg} where the upper left corner is at
5290position @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
5298Add a circle to @var{svg} where the center is at @var{x}/@var{y}
5299and the radius is @var{radius}.
5300@end defun
5301
5302@defun svg-ellipse svg x y x-radius y-radius &rest args
5303Add a circle to @var{svg} where the center is at @var{x}/@var{y} and
5304the 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
5309Add a line to @var{svg} that starts at @var{x1}/@var{y1} and extends
5310to @var{x2}/@var{y2}.
5311@end defun
5312
5313@defun svg-polyline svg points &rest args
5314Add 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
5324Add a polygon to @var{svg} where @var{points} is a list of X/Y pairs
5325that 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
5333Finally, the @code{svg-image} takes an SVG object as its parameter and
5334returns an image object suitable for use in functions like
5335@code{insert-image}. Here's a complete example that creates and
5336inserts 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
5257Image type @code{png}. 5380Image type @code{png}.
5258 5381
5259@item SVG
5260Image type @code{svg}.
5261
5262@item TIFF 5382@item TIFF
5263Image type @code{tiff}. 5383Image type @code{tiff}.
5264Supports the @code{:index} property. @xref{Multi-Frame Images}. 5384Supports the @code{:index} property. @xref{Multi-Frame Images}.
diff --git a/etc/NEWS b/etc/NEWS
index cc99dbd1075..c3c3ebab94d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -851,6 +851,10 @@ keymap put into the text properties (or overlays) that span the
851image. This keymap binds keystrokes for manipulating size and 851image. This keymap binds keystrokes for manipulating size and
852rotation, as well as saving the image to a file. 852rotation, as well as saving the image to a file.
853 853
854+++
855*** A new library for creating and manipulating SVG images has been
856added. 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.
33ARGS can be used to provide `stroke' and `stroke-width' parameters to
34any 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.
44TYPE is `linear' or `radial'. STOPS is a list of percentage/color
45pairs."
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.
66ARGS 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.
82X/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.
93X/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.
116POINTS 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.
129POINTS 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.
202If 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