aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix E. Klee2019-07-15 17:07:56 +0200
committerLars Ingebrigtsen2019-07-15 17:07:56 +0200
commitd6bc55ae2dc98c83e58a28e380ce4bcf2ed00bb3 (patch)
tree47e633d687d0bda97e8613c57bed4ab816e55d44
parent3b6992118501d0a17b6817a91011f8e8dcdf8164 (diff)
downloademacs-d6bc55ae2dc98c83e58a28e380ce4bcf2ed00bb3.tar.gz
emacs-d6bc55ae2dc98c83e58a28e380ce4bcf2ed00bb3.zip
Add support for paths to svg.el
* doc/lispref/display.texi (SVG Images): Document svg-path, svg-clip-path and svg-node (bug#32359). * doc/lispref/display.texi (SVG Path Commands): New node. * lisp/svg.el (svg--plist-delete, svg--path-command-symbol) (svg--elliptical-arc-coordinates, svg--elliptical-arc-command) (svg--moveto-command, svg--closepath-command) (svg--lineto-command, svg--horizontal-lineto-command) (svg--vertical-lineto-command, svg--curveto-command) (svg--smooth-curveto-command) (svg--quadratic-bezier-curveto-command) (svg--smooth-quadratic-bezier-curveto-command) (svg--eval-path-command, svg-path, svg-clip-path, svg-node): New functions.
-rw-r--r--doc/lispref/display.texi237
-rw-r--r--lisp/svg.el148
2 files changed, 385 insertions, 0 deletions
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index a38569f7263..ecaf2e054e0 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -5587,6 +5587,9 @@ The identified of the shape.
5587@item :gradient 5587@item :gradient
5588If given, this should be the identifier of a previously defined 5588If given, this should be the identifier of a previously defined
5589gradient object. 5589gradient object.
5590
5591@item :clip-path
5592Identifier of a clip path.
5590@end table 5593@end table
5591 5594
5592@defun svg-rectangle svg x y width height &rest args 5595@defun svg-rectangle svg x y width height &rest args
@@ -5634,6 +5637,29 @@ that describe the outer circumference of the polygon.
5634@end lisp 5637@end lisp
5635@end defun 5638@end defun
5636 5639
5640@defun svg-path svg commands &rest args
5641Add the outline of a shape to @var{svg} according to @var{commands},
5642see @ref{SVG Path Commands}.
5643
5644Coordinates by default are absolute. To use coordinates relative to
5645the last position, or -- initially -- to the origin, set the attribute
5646@var{:relative} to @code{t}. This attribute can be specified for the
5647function or for individual commands. If specified for the function,
5648then all commands use relative coordinates by default. To make an
5649individual command use absolute coordinates, set @var{:relative} to
5650@code{nil}.
5651
5652@lisp
5653(svg-path svg
5654 '((moveto ((100 . 100)))
5655 (lineto ((200 . 0) (0 . 200) (-200 . 0)))
5656 (lineto ((100 . 100)) :relative nil))
5657 :stroke-color "blue"
5658 :fill-color "lightblue"
5659 :relative t)
5660@end lisp
5661@end defun
5662
5637@defun svg-text svg text &rest args 5663@defun svg-text svg text &rest args
5638Add the specified @var{text} to @var{svg}. 5664Add the specified @var{text} to @var{svg}.
5639 5665
@@ -5665,6 +5691,30 @@ string containing the image data as raw bytes. @var{image-type} should be a
5665@end lisp 5691@end lisp
5666@end defun 5692@end defun
5667 5693
5694@defun svg-clip-path svg &rest args
5695Add a clipping path to @var{svg}. If applied to a shape via the
5696@var{:clip-path} property, parts of that shape which lie outside of
5697the clipping path are not drawn.
5698
5699@lisp
5700(let ((clip-path (svg-clip-path svg :id "foo")))
5701 (svg-circle clip-path 200 200 175))
5702(svg-rectangle svg 50 50 300 300
5703 :fill-color "red"
5704 :clip-path "url(#foo)")
5705@end lisp
5706@end defun
5707
5708@defun svg-node svg tag &rest args
5709Add the custom node @var{tag} to @var{svg}.
5710
5711@lisp
5712(svg-node svg
5713 'rect
5714 :width 300 :height 200 :x 50 :y 100 :fill-color "green")
5715@end lisp
5716@end defun
5717
5668@defun svg-remove svg id 5718@defun svg-remove svg id
5669Remove the element with identifier @code{id} from the @code{svg}. 5719Remove the element with identifier @code{id} from the @code{svg}.
5670@end defun 5720@end defun
@@ -5687,6 +5737,193 @@ circle:
5687@end lisp 5737@end lisp
5688 5738
5689 5739
5740@node SVG Path Commands
5741@subsubsection SVG Path Commands
5742
5743@deffn Command moveto points
5744Move the pen to the first point in @var{points}. Additional points
5745are connected with lines. @var{points} is a list of X/Y coordinate
5746pairs. Subsequent @command{moveto} commands represent the start of a
5747new @dfn{subpath}.
5748
5749@lisp
5750(svg-path svg '((moveto ((200 . 100) (100 . 200) (0 . 100))))
5751 :fill "white" :stroke "black")
5752@end lisp
5753@end deffn
5754
5755@deffn Command closepath
5756End the current subpath by connecting it back to its initial point. A
5757line is drawn along the connection.
5758
5759@lisp
5760(svg-path svg '((moveto ((200 . 100) (100 . 200) (0 . 100)))
5761 (closepath)
5762 (moveto ((75 . 125) (100 . 150) (125 . 125)))
5763 (closepath))
5764 :fill "red" :stroke "black")
5765@end lisp
5766@end deffn
5767
5768@deffn Command lineto points
5769Draw a line from the current point to the first element in
5770@var{points}, a list of X/Y position pairs. If more than one point is
5771specified, draw a polyline.
5772@lisp
5773(svg-path svg '((moveto ((200 . 100)))
5774 (lineto ((100 . 200) (0 . 100))))
5775 :fill "yellow" :stroke "red")
5776@end lisp
5777@end deffn
5778
5779@deffn Command horizontal-lineto x-coordinates
5780Draw a horizontal line from the current point to the first element in
5781@var{x-coordinates}. Specifying multiple coordinates is possible,
5782although usually this doesn’t make sense.
5783
5784@lisp
5785(svg-path svg '((moveto ((100 . 200)))
5786 (horizontal-lineto (300)))
5787 :stroke "green")
5788@end lisp
5789@end deffn
5790
5791@deffn Command vertical-lineto y-coordinates
5792Draw vertical lines.
5793
5794@lisp
5795(svg-path svg '((moveto ((200 . 100)))
5796 (vertical-lineto (300)))
5797 :stroke "green")
5798@end lisp
5799@end deffn
5800
5801@deffn Command curveto coordinate-sets
5802Using the first element in @var{coordinate-sets}, draw a cubic Bézier
5803curve from the current point. If there are multiple coordinate sets,
5804draw a polybézier. Each coordinate set is a list of the form
5805@code{(@var{x1} @var{y1} @var{x2} @var{y2} @var{x} @var{y})}, where
5806@w{(@var{x}, @var{y})} is the curve’s end point. @w{(@var{x1},
5807@var{y1})} and @w{(@var{x2}, @var{y2})} are control points at the
5808beginning and at the end, respectively.
5809
5810@lisp
5811(svg-path svg '((moveto ((100 . 100)))
5812 (curveto ((200 100 100 200 200 200)
5813 (300 200 0 100 100 100))))
5814 :fill "transparent" :stroke "red")
5815@end lisp
5816@end deffn
5817
5818@deffn Command smooth-curveto coordinate-sets
5819Using the first element in @var{coordinate-sets}, draw a cubic Bézier
5820curve from the current point. If there are multiple coordinate sets,
5821draw a polybézier. Each coordinate set is a list of the form
5822@code{(@var{x2} @var{y2} @var{x} @var{y})}, where @w{(@var{x},
5823@var{y})} is the curve’s end point and @w{(@var{x2}, @var{y2})} is the
5824corresponding control point. The first control point is the
5825reflection of the second control point of the previous command
5826relative to the current point, if that command was @command{curveto}
5827or @command{smooth-curveto}. Otherwise the first control point
5828coincides with the current point.
5829
5830@lisp
5831(svg-path svg '((moveto ((100 . 100)))
5832 (curveto ((200 100 100 200 200 200)))
5833 (smooth-curveto ((0 100 100 100))))
5834 :fill "transparent" :stroke "blue")
5835@end lisp
5836@end deffn
5837
5838@deffn Command quadratic-bezier-curveto coordinate-sets
5839Using the first element in @var{coordinate-sets}, draw a quadratic
5840Bézier curve from the current point. If there are multiple coordinate
5841sets, draw a polybézier. Each coordinate set is a list of the form
5842@code{(@var{x1} @var{y1} @var{x} @var{y})}, where @w{(@var{x},
5843@var{y})} is the curve’s end point and @w{(@var{x1}, @var{y1})} is the
5844control point.
5845
5846@lisp
5847(svg-path svg '((moveto ((200 . 100)))
5848 (quadratic-bezier-curveto ((300 100 300 200)))
5849 (quadratic-bezier-curveto ((300 300 200 300)))
5850 (quadratic-bezier-curveto ((100 300 100 200)))
5851 (quadratic-bezier-curveto ((100 100 200 100))))
5852 :fill "transparent" :stroke "pink")
5853@end lisp
5854@end deffn
5855
5856@deffn Command smooth-quadratic-bezier-curveto coordinate-sets
5857Using the first element in @var{coordinate-sets}, draw a quadratic
5858Bézier curve from the current point. If there are multiple coordinate
5859sets, draw a polybézier. Each coordinate set is a list of the form
5860@code{(@var{x} @var{y})}, where @w{(@var{x}, @var{y})} is the curve’s
5861end point. The control point is the reflection of the control point
5862of the previous command relative to the current point, if that command
5863was @command{quadratic-bezier-curveto} or
5864@command{smooth-quadratic-bezier-curveto}. Otherwise the control
5865point coincides with the current point.
5866
5867@lisp
5868(svg-path svg '((moveto ((200 . 100)))
5869 (quadratic-bezier-curveto ((300 100 300 200)))
5870 (smooth-quadratic-bezier-curveto ((200 300)))
5871 (smooth-quadratic-bezier-curveto ((100 200)))
5872 (smooth-quadratic-bezier-curveto ((200 100))))
5873 :fill "transparent" :stroke "lightblue")
5874@end lisp
5875@end deffn
5876
5877@deffn Command elliptical-arc coordinate-sets
5878Using the first element in @var{coordinate-sets}, draw an elliptical
5879arc from the current point. If there are multiple coordinate sets,
5880draw a sequence of elliptical arcs. Each coordinate set is a list of
5881the form @code{(@var{rx} @var{ry} @var{x} @var{y})}, where
5882@w{(@var{x}, @var{y})} is the end point of the ellipse, and
5883@w{(@var{rx}, @var{ry})} are its radii. Attributes may be appended to
5884the list:
5885
5886@table @code
5887@item :x-axis-rotation
5888The angle in degrees by which the x-axis of the ellipse is rotated
5889relative to the x-axis of the current coordinate system.
5890
5891@item :large-arc
5892If set to @code{t}, draw an arc sweep greater than or equal to 180
5893degrees. Otherwise, draw an arc sweep smaller than or equal to 180
5894degrees.
5895
5896@item :sweep
5897If set to @code{t}, draw an arc in @dfn{positive angle direction}.
5898Otherwise, draw it in @dfn{negative angle direction}.
5899@end table
5900
5901@lisp
5902(svg-path svg '((moveto ((200 . 250)))
5903 (elliptical-arc ((75 75 200 350))))
5904 :fill "transparent" :stroke "red")
5905(svg-path svg '((moveto ((200 . 250)))
5906 (elliptical-arc ((75 75 200 350 :large-arc t))))
5907 :fill "transparent" :stroke "green")
5908(svg-path svg '((moveto ((200 . 250)))
5909 (elliptical-arc ((75 75 200 350 :sweep t))))
5910 :fill "transparent" :stroke "blue")
5911(svg-path svg '((moveto ((200 . 250)))
5912 (elliptical-arc ((75 75 200 350 :large-arc t
5913 :sweep t))))
5914 :fill "transparent" :stroke "gray")
5915(svg-path svg '((moveto ((160 . 100)))
5916 (elliptical-arc ((40 100 80 0)))
5917 (elliptical-arc ((40 100 -40 -70
5918 :x-axis-rotation -120)))
5919 (elliptical-arc ((40 100 -40 70
5920 :x-axis-rotation -240))))
5921 :stroke "pink" :fill "lightblue"
5922 :relative t)
5923@end lisp
5924@end deffn
5925
5926
5690@node Other Image Types 5927@node Other Image Types
5691@subsection Other Image Types 5928@subsection Other Image Types
5692@cindex PBM 5929@cindex PBM
diff --git a/lisp/svg.el b/lisp/svg.el
index 86b56a03d56..2ab56d3960d 100644
--- a/lisp/svg.el
+++ b/lisp/svg.el
@@ -3,6 +3,7 @@
3;; Copyright (C) 2014-2019 Free Software Foundation, Inc. 3;; Copyright (C) 2014-2019 Free Software Foundation, Inc.
4 4
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> 5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Felix E. Klee <felix.klee@inka.de>
6;; Keywords: image 7;; Keywords: image
7;; Version: 1.0 8;; Version: 1.0
8;; Package-Requires: ((emacs "25")) 9;; Package-Requires: ((emacs "25"))
@@ -324,6 +325,153 @@ If the SVG is later changed, the image will also be updated."
324 "\\'"))))) 325 "\\'")))))
325 (when node (dom-remove-node svg node)))) 326 (when node (dom-remove-node svg node))))
326 327
328;; Function body copied from `org-plist-delete' in Emacs 26.1.
329(defun svg--plist-delete (plist property)
330 "Delete PROPERTY from PLIST.
331This is in contrast to merely setting it to 0."
332 (let (p)
333 (while plist
334 (if (not (eq property (car plist)))
335 (setq p (plist-put p (car plist) (nth 1 plist))))
336 (setq plist (cddr plist)))
337 p))
338
339(defun svg--path-command-symbol (command-symbol command-args)
340 (let ((char (symbol-name command-symbol))
341 (relative (if (plist-member command-args :relative)
342 (plist-get command-args :relative)
343 (plist-get command-args :default-relative))))
344 (intern (if relative (downcase char) (upcase char)))))
345
346(defun svg--elliptical-arc-coordinates
347 (rx ry x y &rest args)
348 (list
349 rx ry
350 (or (plist-get args :x-axis-rotation) 0)
351 (if (plist-get args :large-arc) 1 0)
352 (if (plist-get args :sweep) 1 0)
353 x y))
354
355(defun svg--elliptical-arc-command (coordinates-list &rest args)
356 (cons
357 (svg--path-command-symbol 'a args)
358 (apply 'append
359 (mapcar
360 (lambda (coordinates)
361 (apply 'svg--elliptical-arc-coordinates
362 coordinates))
363 coordinates-list))))
364
365(defun svg--moveto-command (coordinates-list &rest args)
366 (cons
367 (svg--path-command-symbol 'm args)
368 (apply 'append
369 (mapcar
370 (lambda (coordinates)
371 (list (car coordinates) (cdr coordinates)))
372 coordinates-list))))
373
374(defun svg--closepath-command (&rest args)
375 (list (svg--path-command-symbol 'z args)))
376
377(defun svg--lineto-command (coordinates-list &rest args)
378 (cons
379 (svg--path-command-symbol 'l args)
380 (apply 'append
381 (mapcar
382 (lambda (coordinates)
383 (list (car coordinates) (cdr coordinates)))
384 coordinates-list))))
385
386(defun svg--horizontal-lineto-command (coordinate-list &rest args)
387 (cons
388 (svg--path-command-symbol 'h args)
389 coordinate-list))
390
391(defun svg--vertical-lineto-command (coordinate-list &rest args)
392 (cons
393 (svg--path-command-symbol 'v args)
394 coordinate-list))
395
396(defun svg--curveto-command (coordinates-list &rest args)
397 (cons
398 (svg--path-command-symbol 'c args)
399 (apply 'append coordinates-list)))
400
401(defun svg--smooth-curveto-command (coordinates-list &rest args)
402 (cons
403 (svg--path-command-symbol 's args)
404 (apply 'append coordinates-list)))
405
406(defun svg--quadratic-bezier-curveto-command (coordinates-list
407 &rest args)
408 (cons
409 (svg--path-command-symbol 'q args)
410 (apply 'append coordinates-list)))
411
412(defun svg--smooth-quadratic-bezier-curveto-command (coordinates-list
413 &rest args)
414 (cons
415 (svg--path-command-symbol 't args)
416 (apply 'append coordinates-list)))
417
418(defun svg--eval-path-command (command default-relative)
419 (cl-letf
420 (((symbol-function 'moveto) #'svg--moveto-command)
421 ((symbol-function 'closepath) #'svg--closepath-command)
422 ((symbol-function 'lineto) #'svg--lineto-command)
423 ((symbol-function 'horizontal-lineto)
424 #'svg--horizontal-lineto-command)
425 ((symbol-function 'vertical-lineto)
426 #'svg--vertical-lineto-command)
427 ((symbol-function 'curveto) #'svg--curveto-command)
428 ((symbol-function 'smooth-curveto)
429 #'svg--smooth-curveto-command)
430 ((symbol-function 'quadratic-bezier-curveto)
431 #'svg--quadratic-bezier-curveto-command)
432 ((symbol-function 'smooth-quadratic-bezier-curveto)
433 #'svg--smooth-quadratic-bezier-curveto-command)
434 ((symbol-function 'elliptical-arc)
435 #'svg--elliptical-arc-command)
436 (extended-command (append command (list :default-relative
437 default-relative))))
438 (mapconcat 'prin1-to-string (apply extended-command) " ")))
439
440(defun svg-path (svg commands &rest args)
441 "Add the outline of a shape to SVG according to COMMANDS.
442Coordinates by default are absolute. ARGS is a plist of
443modifiers. If :relative is t, then coordinates are relative to
444the last position, or -- initially -- to the origin."
445 (let* ((default-relative (plist-get args :relative))
446 (stripped-args (svg--plist-delete args :relative))
447 (d (mapconcat 'identity
448 (mapcar
449 (lambda (command)
450 (svg--eval-path-command command
451 default-relative))
452 commands) " ")))
453 (svg--append
454 svg
455 (dom-node 'path
456 `((d . ,d)
457 ,@(svg--arguments svg stripped-args))))))
458
459(defun svg-clip-path (svg &rest args)
460 "Add a clipping path to SVG, where ARGS is a plist of modifiers.
461If applied to a shape via the :clip-path property, parts of that
462shape which lie outside of the clipping path are not drawn."
463 (let ((new-dom-node (dom-node 'clipPath
464 `(,@(svg--arguments svg args)))))
465 (svg--append svg new-dom-node)
466 new-dom-node))
467
468(defun svg-node (svg tag &rest args)
469 "Add the custom node TAG to SVG."
470 (let ((new-dom-node (dom-node tag
471 `(,@(svg--arguments svg args)))))
472 (svg--append svg new-dom-node)
473 new-dom-node))
474
327(provide 'svg) 475(provide 'svg)
328 476
329;;; svg.el ends here 477;;; svg.el ends here