packages feed

juicy-gcode 0.3.0.0 → 1.0.0.0

raw patch · 7 files changed

+245/−106 lines, 7 filesdep +JuicyPixelsdep +aesondep +unordered-containersdep −configurator

Dependencies added: JuicyPixels, aeson, unordered-containers, yaml

Dependencies removed: configurator

Files

ChangeLog.md view
@@ -1,5 +1,10 @@ # Revision history for juicy-gcode +## 1.0.0.0 -- 2023-05-28++- Breaking change: change favor file format to YAML+- Enable color specific configuration+ ## 0.3.0.0 -- 2022-12-14  - Add linear approximation algorithm
README.md view
@@ -1,18 +1,26 @@-# Juicy-gcode: A lightweight SVG to GCode converter for maximal curve fitting+# Juicy-gcode: A lightweight 2.5D SVG to GCode converter for maximal curve fitting  [![Hackage](https://img.shields.io/hackage/v/juicy-gcode.svg)](https://hackage.haskell.org/package/juicy-gcode) [![Appveyor](https://ci.appveyor.com/api/projects/status/github/domoszlai/juicy-gcode?branch=master&svg=true)](https://ci.appveyor.com/project/domoszlai/juicy-gcode)  ## Overview -Juicy-gcode is a configurable SVG to G-code converter that approximates bezier curves based on your needs:-- with [biarcs](http://dlacko.org/blog/2016/10/19/approximating-bezier-curves-by-biarcs/) for maximal curve fitting: bezier curves are approximated with arcs ([G2, G3 commands](https://marlinfw.org/meta/gcode/)), the resulting path is [G1 continues](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types)-- with linear approximation when arcs are not supported by your firmware: bezier curves are approximated with line segments ([G0, G1 commands](https://marlinfw.org/meta/gcode/)), the resulting path is [not smooth](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types), and the generated gcode is usually significantly larger -- with cubic bezier curves if your firmware supports it: some firmwares (e.g. [Marlin](https://marlinfw.org/docs/gcode/G005.html)) can handle bezier curves directly ([G5 command](https://marlinfw.org/meta/gcode/)), the result is [G2 continues](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types)+Juicy-GCode is a command-line application that converts SVG files to GCode. It provides flexible options for curve approximation and allows configurable GCode generation based on color information. +## Features ++- **Configurable Curve Approximation**: Juicy-GCode offers three approximation methods to suit your needs+   - with [biarcs](http://dlacko.org/blog/2016/10/19/approximating-bezier-curves-by-biarcs/) for maximal curve fitting: bezier curves are approximated with arcs ([G2, G3 commands](https://marlinfw.org/meta/gcode/)), the resulting path is [G1 continuous](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types)+   - with linear approximation when arcs are not supported by your firmware: bezier curves are approximated with line segments ([G0, G1 commands](https://marlinfw.org/meta/gcode/)), the resulting path is [not smooth](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types), and the generated GCode is usually significantly larger +   - with cubic bezier curves if your firmware supports it: some firmwares (e.g. [Marlin](https://marlinfw.org/docs/gcode/G005.html)) can handle bezier curves directly ([G5 command](https://marlinfw.org/meta/gcode/)), the result is [G2 continuous](https://skill-lync.com/blogs/introductions-to-surface-continuities-and-its-types)+- **Configurable 2.5D GCode generation**: Juicy-GCode allows you to configure the GCode generation by color information (this feature enables e.g. carving of 3D objects based on a single SVG file)+   - extra GCode can be added to the beginning of a continous colored path+   - GCode [parameters](https://reprap.org/wiki/G-code#G0_.26_G1:_Move) e.g. `E`, `F` or `S` can be set per color+   - the number of passes can be set for a given color (upcoming feature)+ ## Installation -The easiest way is to download one of the pre-built binaries from the [releases page](https://github.com/domoszlai/juicy-gcode/releases).+The easiest way is to download one of the prebuilt binaries from the [releases page](https://github.com/domoszlai/juicy-gcode/releases). Alternatively, you can build from source code as follows:  - Install [Stack](https://docs.haskellstack.org/en/stable/install_and_upgrade/) if you do not have it yet@@ -23,6 +31,8 @@  ## Usage +> :warning: **Breaking change**: Since version 1.0.0.0, the flavor file format changed to YAML. If you use a pre 1.0 version, please refer to [this historical README file](https://github.com/domoszlai/juicy-gcode/blob/9d573918eb1c4a99801c8d6745f7471ba987828c/README.md)+ > :warning: **Breaking change**: Since version 0.3.0.0, `--generate-bezier` has been removed in favor of the more generic `--curve-fitting` parameter and `--resolution` has been renamed to `--tolerance`  > :warning: **Breaking change**: Since version 0.2.0.1, default DPI is changed to 96 and the option to mirror the Y axis is removed (it is always mirrored now for correct result)@@ -42,7 +52,7 @@ Sometimes you want to overwrite some default settings. These are the   * *--dpi* (default 96 DPI) [the resolution of the SVG file](https://developer.mozilla.org/en-US/docs/Web/CSS/resolution) that is used to determine the size of the SVG when it does not contain explicit units-* *--tolerance* (default is 0.1 mm) maximum allowed derivation of the approximation curve+* *--tolerance* (default is 0.1 mm) maximum allowed deviation of the approximation curve   ``` $ juicy-gcode SVGFILE --dpi 72 --tolerance 0.01 @@ -62,49 +72,61 @@  ## Configuration -The generated GCode is highly dependent on the actual device it will be executed by. In juicy-gcode these settings are called-GCode *flavor* and consists of the following:+The generated GCode is highly dependent on the actual device it will be executed by and one might also want to generate different GCode for different colors. In `juicy-gcode` these settings are called GCode *flavor* and consist of the following:  - Begin GCode routine (commands that are executed *before* the actual print job) - End GCode routine (commands that are executed *after* the actual print job)-- Tool on (commands to switch the tool on, e.g. lower pen)-- Tool off (commands to switch the tool off e.g. lift pen)+- Tool on (commands to switch the tool on, e.g. lower the pen)+- Tool off (commands to switch the tool off e.g. lift the pen)+- Color dependent settings -These settings can be provided by a configuration file. The default settings+These settings can be provided by a YAML configuration file. The default settings are made for being able to test the generated GCode in an emulator e.g. with [LaserWeb](https://laserweb.yurl.ch/) or [my hanging plotter simulator](https://github.com/domoszlai/hanging-plotter-simulator).  -```-gcode-{-   begin = "G17;G90;G0 Z1;G0 X0 Y0"-   end = "G0 Z1"-   toolon =  "G00 Z1"-   tooloff = "G01 Z0 F10.00"-}+```YAML+begin: |+  G17+  G90+  G0 Z1+  G0 X0 Y0+end: |+  G0 Z1+toolon: |+  G01 Z0 F10.00+tooloff: |+  G00 Z1 ``` -In the case you want to overwrite it, copy this favor to a text file and modify it according to your need. Then use juicy-gcode as follows:+By providing your own configuration file, you can also specify color dependent settings. As an example, the following+configuration adds extra GCode configuration for the color codes `#000000` and `#FF0000`. -```-$ juicy-gcode SVGFILE -f FLAVORFILE+```YAML+colors:+   "000000": +      before: |+        ; black+      passes: 1+      parameters:+        F: 1.0 +        S: 80+   "FF0000": +      before: |+        ; red+      passes: 1+      parameters:+        F: 2.0 +        S: 25 ``` -## Future development--Juicy-gcode was originally developed as a testbed for my hanging plotter project, but over the years-it reached maturity and became a really usuable tool. My main idea for further development is to turn it-into a tool that can drive CNCs in 2.5 dimensions (e.g. carving, engraving) with just one colored SVG file.--To be able to test and enjoy that software, I need a proper CNC. Please consider donating a small amount for that purpose,-or donate an actual CNC if you have a spare one for whatever reason.--**[Donate for a CNC](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UGFZYDQSTF58L&source=https://github.com/domoszlai/juicy-gcode/)**+In this example, GCode comments will be generated before any continous red or black path, the `F` and `S` GCode parameters are+set different for red and black paths (and not set for any other colors) and the number of passes are set to 1 for both colors (passes is an upcoming feature, currently this option is ignored) -Collected so far: 229.47€-Target: >= 209€+Use the `-f` option to pass the GCode flavor to `juicy-gcode`: -Thank you so much for all people supporting the development!+```+$ juicy-gcode SVGFILE -f FLAVORFILE+```  ## Limitations 
juicy-gcode.cabal view
@@ -1,5 +1,5 @@ name:                juicy-gcode-version:             0.3.0.0+version:             1.0.0.0 license:             BSD3 license-file:        LICENSE author:              dlacko@@ -11,7 +11,7 @@ bug-reports:         https://github.com/domoszlai/juicy-gcode/issues build-type:          Simple description:-  SVG to G-code converter that aims to support most SVG features. The flavor of the generated G-Code can be influenced providing a configuration file.+  SVG to G-code converter that aims to support most SVG features. It provides flexible options for curve approximation and allows configurable GCode generation based on color information.  extra-source-files:  ChangeLog.md, README.md cabal-version:       >=1.10@@ -39,14 +39,17 @@    build-depends:     base                    >=4.8    && <5,-    lens                    >=4.15.4 && <4.20,-    linear                  >=1.20   && <1.22,+    lens                    >=4.15.4 && <5.3,+    linear                  >=1.20   && <1.23,     optparse-applicative    >=0.13   && <0.20,-    configurator            >=0.3    && <0.4,-    text                    >=1.2.2  && <1.3,+    aeson                   >=1.5    && <2.2,+    yaml                    >=0.11.8 && <0.12,+    text                    >=1.2.2  && <1.3 || >=2.0 && <2.1,     matrix                  >=0.3.5  && <0.4,     svg-tree                >=0.6    && <0.7,-    gitrev                  >=1.3.0  && <1.4+    gitrev                  >=1.3.0  && <1.4,+    JuicyPixels             >=3.3.7  && <3.4,+    unordered-containers    >=0.2.16 && <0.3    GHC-Options:              -Wall   default-language:         Haskell2010
src/GCode.hs view
@@ -1,64 +1,154 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE StrictData #-}+ module GCode ( GCodeFlavor(..)-             , defaultFlavor              , toString              ) where +import GHC.Generics++import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap import Data.List+import Data.Aeson (FromJSON (..)) import Text.Printf  import Graphics.Path import Utils -data GCodeFlavor = GCodeFlavor { _begin   :: String-                               , _end     :: String-                               , _toolon  :: String-                               , _tooloff :: String-                               }+import Codec.Picture (PixelRGBA8(..))+import Data.Maybe (fromMaybe)+import Data.Char (isSpace) +data GCodeColorBehavior = GCodeColorBehavior {+     before     :: Maybe String+   , passes     :: Maybe Int+   , parameters :: Maybe (HashMap String Float)+} deriving (Show, Generic)++instance FromJSON GCodeColorBehavior++data GCodeFlavor = GCodeFlavor {+      begin   :: Maybe String+    , end     :: Maybe String+    , toolon  :: Maybe String+    , tooloff :: Maybe String+    , colors  :: Maybe (HashMap String GCodeColorBehavior)+} deriving (Show, Generic)++instance FromJSON GCodeFlavor++data ColorState = ColorState {+      csColor  :: String+    , csBefore :: Maybe String+    , csParams :: Maybe String+    , csPasses :: Int+}++trim :: String -> String+trim = dropWhileEnd isSpace . dropWhile isSpace++buildColorState :: HashMap String GCodeColorBehavior -> String -> ColorState+buildColorState colorMap color =+    ColorState color colorBefore colorParams colorPasses+    where+        colorBefore = fmap trim $ HashMap.lookup color colorMap >>= before+        colorPasses = fromMaybe 1 $ HashMap.lookup color colorMap >>= passes+        colorParams = case fromMaybe [] (HashMap.lookup color colorMap >>= parameters >>= (Just . HashMap.toList)) of+            [] -> Nothing+            params -> Just $ unwords $ map (\(w,p) -> w ++ show p) params+ defaultFlavor :: GCodeFlavor-defaultFlavor =  GCodeFlavor "G17\nG90\nG0 Z1\nG0 X0 Y0" "G0 Z1" "G01 Z0 F10.00" "G00 Z1"+defaultFlavor =  GCodeFlavor (Just "G17\nG90\nG0 Z1\nG0 X0 Y0") (Just "G0 Z1") (Just "G01 Z0 F10.00") (Just "G00 Z1") Nothing -toString :: GCodeFlavor -> Int -> [PathCommand] -> String-toString (GCodeFlavor begin end on off) dpi gops -    = begin ++-      "\n" ++ -      intercalate "\n" (toString' gops (0,0) True) ++ -      "\n" ++ -      end ++-      "\n"+toString :: Maybe GCodeFlavor -> Int -> [ColoredPath] -> String+toString mbConfig dpi cps+    = maybe [] ((++ "\n").trim) (begin config) +++      intercalate "\n" (toString' (flatten cps) (0,0) True Nothing) +++      "\n" +++      maybe [] ((++ "\n").trim) (end config)     where+        config = fromMaybe defaultFlavor mbConfig++        colorMap :: HashMap String GCodeColorBehavior+        colorMap = fromMaybe HashMap.empty (colors config)++        augmentCommand mbColor cmd = maybe cmd (\params -> cmd ++ " " ++ params) (mbColor >>= csParams)++        mbToolonCommands = fmap trim (toolon config)+        mbTooloffCommands = fmap trim (tooloff config)++        toColorString Nothing = "000000"+        toColorString (Just (PixelRGBA8 r g b _)) = concatMap (printf "%02X") [r, g, b]++        flatten = concatMap (\(ColoredPath color path) -> Left (toColorString color): map Right path)+         dd :: Double         dd = fromIntegral dpi          mm :: Double -> Double         mm px = (px * 2.54 * 10) / dd -        toString' (MoveTo p@(x,y) : gs) _ False-            = printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False-        toString' (MoveTo p@(x,y) : gs) _ True-            = off : printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False-        toString' gs cp False-            = on : toString' gs cp True-        toString' (LineTo p@(x,y) : gs) _ True-            = printf "G01 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p True-        toString' (ArcTo (ox,oy) p@(x,y) cw : gs) (cx,cy) True-            = arcStr : toString' gs p True+        toString' :: [Either String PathCommand] -> (Double, Double) -> Bool -> Maybe ColorState -> [String]++        -- Move, tool off+        toString' (Right (MoveTo p@(x,y)) : gs) _ False mbCurrentColor+            = printf "G00 X%.4f Y%.4f" (mm x) (mm y) :+              toString' gs p False mbCurrentColor++        -- Switch off tool before move+        toString' (Right (MoveTo p@(x,y)) : gs) _ True mbCurrentColor+            = let cont = printf "G00 X%.4f Y%.4f" (mm x) (mm y) : toString' gs p False mbCurrentColor+               in case mbTooloffCommands of+                    Nothing -> cont+                    Just tooloffCommands -> tooloffCommands : cont++        -- Switch on tool before drawing+        toString' gs cp False mbCurrentColor+            = let cont = toString' gs cp True mbCurrentColor+               in case mbToolonCommands of+                    Nothing -> cont+                    Just toolonCommands -> toolonCommands : cont++        -- Drawing line+        toString' (Right (LineTo p@(x,y)) : gs) _ True mbCurrentColor+            = augmentCommand mbCurrentColor (printf "G01 X%.4f Y%.4f" (mm x) (mm y)) :+              toString' gs p True mbCurrentColor++        -- Drawing arc+        toString' (Right (ArcTo (ox,oy) p@(x,y) cw) : gs) (cx,cy) True mbCurrentColor+            = arcStr : toString' gs p True mbCurrentColor             where                 i = ox - cx                 j = oy - cy                  cmd = if' cw "G03" "G02" -                arcStr = printf "%s X%.4f Y%.4f I%.4f J%.4f" cmd (mm x) (mm y) (mm i) (mm j)-        toString' (BezierTo (c1x,c1y) (c2x,c2y) p2@(p2x,p2y) : gs) (p1x,p1y) True-            = bStr : toString' gs p2 True+                arcStr = augmentCommand mbCurrentColor $+                         printf "%s X%.4f Y%.4f I%.4f J%.4f" cmd (mm x) (mm y) (mm i) (mm j)++        -- Drawing bezier+        toString' (Right (BezierTo (c1x,c1y) (c2x,c2y) p2@(p2x,p2y)) : gs) (p1x,p1y) True mbCurrentColor+            = bStr : toString' gs p2 True mbCurrentColor             where                 i = c1x - p1x                 j = c1y - p1y                 p = c2x - p2x                 q = c2y - p2y -                bStr = printf "G05 I%.4f J%.4f P%.4f Q%.4f X%.4f Y%.4f"-                        (mm i) (mm j) (mm p) (mm q) (mm p2x) (mm p2y)+                bStr = augmentCommand mbCurrentColor $+                       printf "G05 I%.4f J%.4f P%.4f Q%.4f X%.4f Y%.4f" (mm i) (mm j) (mm p) (mm q) (mm p2x) (mm p2y) -        toString' [] _ _ = []+        -- Potential color change, no current color+        toString' (Left newColor : gs) p drawing mbCurrentColor+            | fmap csColor mbCurrentColor == Just newColor+                =  toString' gs p drawing mbCurrentColor+            | otherwise+                = let cont = toString' gs p drawing (Just newColorState)+                   in case csBefore newColorState of+                        Nothing -> cont+                        Just beforeCmd -> beforeCmd : cont+            where+                newColorState = buildColorState colorMap newColor++        toString' [] _ _ _ = []
src/Graphics/Path.hs view
@@ -1,8 +1,12 @@ module Graphics.Path ( -      PathCommand(..)+      PathCommand(..),+      ColoredPath(..) ) where  import Graphics.Point+import Codec.Picture (PixelRGBA8 (..))++data ColoredPath = ColoredPath (Maybe PixelRGBA8) [PathCommand]  -- all of them are invariant under affine transformation data PathCommand 
src/Main.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# HLINT ignore "Use lambda-case" #-}+{-# HLINT ignore "Use tuple-section" #-}  import qualified Graphics.Svg as SVG @@ -9,12 +10,11 @@ import Data.Version (showVersion) import Development.GitRev (gitHash) -import Data.Text (Text, pack, unpack, replace)-import qualified Data.Configurator as C+import Data.Maybe (fromMaybe)+import qualified Data.Yaml as Y  import Render import GCode-import Data.Maybe (fromMaybe)  data Options = Options { _svgfile        :: String                        , _cfgfile        :: Maybe String@@ -68,24 +68,12 @@ runWithOptions (Options svgFile mbCfg mbOut dpi resolution mbApproximation) =     do         mbDoc <- SVG.loadSvgFile svgFile-        flavor <- maybe (return defaultFlavor) readFlavor mbCfg+        mbConfig <- maybe (return Nothing) (fmap Just . Y.decodeFileThrow) mbCfg         case mbDoc of-            (Just doc) -> writer (toString flavor dpi $ renderDoc (fromMaybe BiArc mbApproximation) dpi resolution doc)+            (Just doc) -> writer (toString mbConfig dpi $ renderDoc (fromMaybe BiArc mbApproximation) dpi resolution doc)             Nothing    -> putStrLn "juicy-gcode: error during opening the SVG file"     where         writer = maybe putStr writeFile mbOut--toLines :: Text -> String-toLines t = unpack $ replace (pack ";") (pack "\n") t--readFlavor :: FilePath -> IO GCodeFlavor-readFlavor cfgFile = do-  cfg          <- C.load [C.Required cfgFile]-  begin        <- C.require cfg (pack "gcode.begin")-  end          <- C.require cfg (pack "gcode.end")-  toolon       <- C.require cfg (pack "gcode.toolon")-  tooloff      <- C.require cfg (pack "gcode.tooloff")-  return $ GCodeFlavor (toLines begin) (toLines end) (toLines toolon) (toLines tooloff)  versionOption :: Parser (a -> a) versionOption = infoOption
src/Render.hs view
@@ -3,10 +3,16 @@     renderDoc ) where ++import Codec.Picture (PixelRGBA8(..))+ import Data.Maybe ( fromMaybe )+import Data.Monoid  import qualified Graphics.Svg as SVG import qualified Graphics.Svg.CssTypes as CSS+import Graphics.Svg.Types (DrawAttributes)+ import qualified Linear  import Graphics.Path@@ -86,15 +92,16 @@  data Approximation = BiArc | CubicBezier | Linear -renderDoc :: Approximation -> Int -> Double -> SVG.Document -> [PathCommand]+renderDoc :: Approximation -> Int -> Double -> SVG.Document -> [ColoredPath] renderDoc approximation dpi resolution doc     = stage2 $ renderTrees (docTransform dpi doc) (SVG._elements doc)     where         pxresolution = fromIntegral dpi / 2.45 / 10 * resolution          -- TODO: make it tail recursive-        stage2 :: [PathCommand] -> [PathCommand]-        stage2 dops = approximate dops (Linear.V2 0 0)+        stage2 :: [ColoredPath] -> [ColoredPath]+        stage2 = map (\(ColoredPath color path) -> +                            ColoredPath color (approximate path (Linear.V2 0 0)))             where                 approximate [] _ = []                 approximate (MoveTo p:ds) _ = MoveTo p : approximate ds (fromPoint p)@@ -217,64 +224,84 @@          renderPathCommands _ _ _ _ = [] -        renderTree :: TransformationMatrix -> SVG.Tree -> [PathCommand]+        extractColor :: DrawAttributes -> Maybe PixelRGBA8+        extractColor attr = case getLast $ SVG._strokeColor attr of+                                (Just (SVG.ColorRef rgba8)) -> Just rgba8+                                _ -> Nothing++        renderTree :: TransformationMatrix -> SVG.Tree -> [ColoredPath]         renderTree m (SVG.GroupTree g) = renderTrees (applyTransformations m (SVG._transform (SVG._groupDrawAttributes g))) (SVG._groupChildren g)-        renderTree m (SVG.PathTree p) = map (transform tr) $ renderPathCommands (0,0) (0,0) Nothing (SVG._pathDefinition p)+        renderTree m (SVG.PathTree p) +            = [ColoredPath color +                    (map (transform tr) $ renderPathCommands (0,0) (0,0) Nothing (SVG._pathDefinition p))]            where+                color = extractColor (SVG._pathDrawAttributes p)                 tr = applyTransformations m (SVG._transform (SVG._pathDrawAttributes p))          renderTree m (SVG.RectangleTree r)             | rx == 0.0 && ry == 0.0-                = map (transform tr) [MoveTo (x,y), LineTo (x+w,y), LineTo (x+w,y+h), LineTo (x,y+h), LineTo (x,y)]+                = [ColoredPath color+                    (map (transform tr) [MoveTo (x,y), LineTo (x+w,y), LineTo (x+w,y+h), LineTo (x,y+h), LineTo (x,y)])]             | otherwise-                = map (transform tr)+                = [ColoredPath color+                    (map (transform tr)                       ([MoveTo (x,y+ry)]     ++ convertSvgArc (x,y+ry) rx ry 0 False True (x+rx, y) ++                        [LineTo (x+w-rx,y)]   ++ convertSvgArc (x+w-rx,y) rx ry 0 False True (x+w, y+ry) ++                        [LineTo (x+w,y+h-ry)] ++ convertSvgArc (x+w,y+h-ry) rx ry 0 False True (x+w-rx, y+h) ++                        [LineTo (x+rx,y+h)]   ++ convertSvgArc (x+rx, y+h) rx ry 0 False True (x, y+h-ry) ++-                       [LineTo (x,y+ry)])+                       [LineTo (x,y+ry)]))]             where+                color = extractColor (SVG._rectDrawAttributes r)                 (x,y) = fromSvgPoint dpi (SVG._rectUpperLeftCorner r)                 w = fromSvgNumber dpi (SVG._rectWidth r)                 h = fromSvgNumber dpi (SVG._rectHeight r)                 (rx, ry) = mapTuple (fromSvgNumber dpi) (SVG._rectCornerRadius r)                 tr = applyTransformations m (SVG._transform (SVG._rectDrawAttributes r)) -        renderTree m (SVG.LineTree l) = [MoveTo p1, LineTo p2]+        renderTree m (SVG.LineTree l) = [ColoredPath color [MoveTo p1, LineTo p2]]             where+                color = extractColor (SVG._lineDrawAttributes l)                 p1 = transform tr (fromSvgPoint dpi (SVG._linePoint1 l))                 p2 = transform tr (fromSvgPoint dpi (SVG._linePoint2 l))                 tr = applyTransformations m (SVG._transform (SVG._lineDrawAttributes l)) -        renderTree m (SVG.PolyLineTree l) = map (transform tr) (MoveTo p0:map LineTo ps)+        renderTree m (SVG.PolyLineTree l) +                = [ColoredPath color (map (transform tr) (MoveTo p0:map LineTo ps))]             where+                color = extractColor (SVG._polyLineDrawAttributes l)                 (p0:ps) = map (\(Linear.V2 x y) -> (x,y)) (SVG._polyLinePoints l)                 tr = applyTransformations m (SVG._transform (SVG._polyLineDrawAttributes l)) -        renderTree m (SVG.PolygonTree l) = map (transform tr) (MoveTo p0:map LineTo (ps ++ [p0]))+        renderTree m (SVG.PolygonTree l) +                = [ColoredPath color (map (transform tr) (MoveTo p0:map LineTo (ps ++ [p0])))]             where+                color = extractColor (SVG._polygonDrawAttributes l)                 (p0:ps) = map (\(Linear.V2 x y) -> (x,y)) (SVG._polygonPoints l)                 tr = applyTransformations m (SVG._transform (SVG._polygonDrawAttributes l)) -        renderTree m (SVG.EllipseTree e) = map (transform tr) (MoveTo (cx-rx,cy) : bs1++bs2++bs3++bs4)+        renderTree m (SVG.EllipseTree e) +                = [ColoredPath color (map (transform tr) (MoveTo (cx-rx,cy) : bs1++bs2++bs3++bs4))]             where                 bs1 = convertSvgArc (cx-rx, cy) rx ry 0 False True (cx, cy-ry)                 bs2 = convertSvgArc (cx, cy-ry) rx ry 0 False True (cx+rx, cy)                 bs3 = convertSvgArc (cx+rx, cy) rx ry 0 False True (cx, cy+ry)                 bs4 = convertSvgArc (cx, cy+ry) rx ry 0 False True (cx-rx, cy) +                color = extractColor (SVG._ellipseDrawAttributes e)                 (cx,cy) = fromSvgPoint dpi (SVG._ellipseCenter e)                 rx = fromSvgNumber dpi (SVG._ellipseXRadius e)                 ry = fromSvgNumber dpi (SVG._ellipseYRadius e)                 tr = applyTransformations m (SVG._transform (SVG._ellipseDrawAttributes e)) -        renderTree m (SVG.CircleTree c) = map (transform tr) (MoveTo (cx-r,cy) : bs1++bs2++bs3++bs4)+        renderTree m (SVG.CircleTree c)+                = [ColoredPath color (map (transform tr) (MoveTo (cx-r,cy) : bs1++bs2++bs3++bs4))]             where                 bs1 = convertSvgArc (cx-r, cy) r r 0 False True (cx, cy-r)                 bs2 = convertSvgArc (cx, cy-r) r r 0 False True (cx+r, cy)                 bs3 = convertSvgArc (cx+r, cy) r r 0 False True (cx, cy+r)                 bs4 = convertSvgArc (cx, cy+r) r r 0 False True (cx-r, cy) +                color = extractColor (SVG._circleDrawAttributes c)                 (cx,cy) = fromSvgPoint dpi (SVG._circleCenter c)                 r = fromSvgNumber dpi (SVG._circleRadius c)                 tr = applyTransformations m (SVG._transform (SVG._circleDrawAttributes c))@@ -282,5 +309,5 @@         {- The rest: None, UseTree, SymbolTree, TextTree, ImageTree -}         renderTree _ _ = [] -        renderTrees :: TransformationMatrix -> [SVG.Tree] -> [PathCommand]+        renderTrees :: TransformationMatrix -> [SVG.Tree] -> [ColoredPath]         renderTrees m es = concatMap (renderTree m) es