Code

Ruby port of simplepath.py
authormental <mental@users.sourceforge.net>
Sat, 12 Aug 2006 06:31:15 +0000 (06:31 +0000)
committermental <mental@users.sourceforge.net>
Sat, 12 Aug 2006 06:31:15 +0000 (06:31 +0000)
share/extensions/Makefile.am
share/extensions/simplepath.rb [new file with mode: 0755]

index fc4297abcd139a73f1dd9cd3e37a90220be160f4..13f6e0f2e2c32de15d0d4aac00cba132d6a145df 100644 (file)
@@ -36,6 +36,7 @@ extensions = \
        radiusrand.py \
        rtree.py \
        simplepath.py \
+       simplepath.rb \
        simplestyle.py \
        straightseg.py \
        wavy.py \
diff --git a/share/extensions/simplepath.rb b/share/extensions/simplepath.rb
new file mode 100755 (executable)
index 0000000..b8f9123
--- /dev/null
@@ -0,0 +1,208 @@
+#!/usr/bin/env ruby
+
+# simplepath.rb
+# functions for digesting paths into a simple list structure
+#
+# Ruby port by MenTaLguY
+#
+# Copyright (C) 2005 Aaron Spike  <aaron@ekips.org>
+# Copyright (C) 2006 MenTaLguY  <mental@rydia.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+require 'strscan'
+
+def lexPath(d)
+    # iterator which breaks path data 
+    # identifies command and parameter tokens
+
+    scanner = StringScanner.new(d)
+
+    delim = /[ \t\r\n,]+/
+    command = /[MLHVCSQTAZmlhvcsqtaz]/
+    parameter = /(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/
+
+    until scanner.eos?
+        scanner.skip(delim)
+        if m = scanner.scan(command)
+            yield m, true
+        elsif m = scanner.scan(parameter)
+            yield m, false
+        else
+            raise 'Invalid path data!'
+        end
+    end
+end
+
+PathDef = Struct.new :implicit_next, :param_count, :casts, :coord_types
+PATHDEFS = {
+    'M' => PathDef['L', 2, [:to_f, :to_f], [:x,:y]], 
+    'L' => PathDef['L', 2, [:to_f, :to_f], [:x,:y]], 
+    'H' => PathDef['H', 1, [:to_f], [:x]], 
+    'V' => PathDef['V', 1, [:to_f], [:y]], 
+    'C' => PathDef['C', 6, [:to_f, :to_f, :to_f, :to_f, :to_f, :to_f], [:x,:y,:x,:y,:x,:y]], 
+    'S' => PathDef['S', 4, [:to_f, :to_f, :to_f, :to_f], [:x,:y,:x,:y]], 
+    'Q' => PathDef['Q', 4, [:to_f, :to_f, :to_f, :to_f], [:x,:y,:x,:y]], 
+    'T' => PathDef['T', 2, [:to_f, :to_f], [:x,:y]], 
+    'A' => PathDef['A', 7, [:to_f, :to_f, :to_f, :to_i, :to_i, :to_f, :to_f], [0,0,0,0,0,:x,:y]], 
+    'Z' => PathDef['L', 0, [], []]
+}
+
+def parsePath(d)
+    # Parse SVG path and return an array of segments.
+    # Removes all shorthand notation.
+    # Converts coordinates to absolute.
+
+    retval = []
+
+    command = nil
+    outputCommand = nil
+    params = []
+
+    pen = [0.0,0.0]
+    subPathStart = pen
+    lastControl = pen
+    lastCommand = nil
+
+    lexPath(d) do |token, isCommand|
+        unless command
+            if isCommand
+                if lastCommand or token.upcase == 'M'
+                    command = token
+                else
+                    raise 'Invalid path, must begin with moveto.'
+                end
+            else
+                #command was omited
+                #use last command's implicit next command
+                if lastCommand
+                    if lastCommand =~ /[A-Z]/
+                        command = PATHDEFS[lastCommand].implicit_next
+                    else
+                        command = PATHDEFS[lastCommand.upcase].implicit_next.downcase
+                    end
+                else
+                    raise 'Invalid path, no initial command.'    
+                end
+            end
+            outputCommand = command.upcase
+        else
+            raise 'Invalid number of parameters' if isCommand
+            param = token.send PATHDEFS[outputCommand].casts[params.length]
+            if command =~ /[a-z]/
+                case PATHDEFS[outputCommand].coord_types[params.length]
+                when :x: param += pen[0]
+                when :y: param += pen[1]
+                end
+            end
+            params.push param
+        end
+
+        if params.length == PATHDEFS[outputCommand].param_count
+
+            #Flesh out shortcut notation
+            case outputCommand
+            when 'H','V'
+                case outputCommand
+                when 'H': params.push pen[1]
+                when 'V': params.unshift pen[0]
+                end
+                outputCommand = 'L'
+            when 'S','T'
+                params.unshift(pen[1]+(pen[1]-lastControl[1]))
+                params.unshift(pen[0]+(pen[0]-lastControl[0]))
+                case outputCommand
+                when 'S': outputCommand = 'C'
+                when 'T': outputCommand = 'Q'
+                end
+            end
+
+            #current values become "last" values
+            case outputCommand
+            when 'M'
+                subPathStart = params[0,2]
+                pen = subPathStart
+            when 'Z'
+                pen = subPathStart
+            else
+                pen = params[-2,2]
+            end
+
+            case outputCommand
+            when 'Q','C'
+                lastControl = params[-4,2]
+            else
+                lastControl = pen
+            end
+
+            lastCommand = command
+            retval.push [outputCommand,params]
+            command = nil
+            params = []
+        end
+    end
+
+    raise 'Unexpected end of path' if command
+
+    return retval
+end
+
+def formatPath(a)
+    # Format SVG path data from an array
+    a.map { |cmd,params| "#{cmd} #{params.join(' ')}" }.join
+end
+
+def translatePath(p, x, y)
+    p.each do |cmd,params|
+        coord_types = PATHDEFS[cmd].coord_types
+        for i in 0...(params.length)
+            case coord_types[i]
+            when :x: params[i] += x
+            when :y: params[i] += y
+            end
+        end
+    end
+end
+
+def scalePath(p, x, y)
+    p.each do |cmd,params|
+        coord_types = PATHDEFS[cmd].coord_types
+        for i in 0...(params.length)
+            case coord_types[i]
+            when :x: params[i] *= x
+            when :y: params[i] *= y
+            end
+        end
+    end
+end
+
+def rotatePath(p, a, cx = 0, cy = 0)
+    return p if a == 0
+    p.each do |cmd,params|
+        coord_types = PATHDEFS[cmd].coord_types
+        for i in 0...(params.length)
+            if coord_types[i] == :x
+                x = params[i] - cx
+                y = params[i + 1] - cy
+                r = Math.sqrt((x**2) + (y**2))
+                unless r.zero?
+                    theta = Math.atan2(y, x) + a
+                    params[i] = (r * Math.cos(theta)) + cx
+                    params[i + 1] = (r * Math.sin(theta)) + cy
+                end
+            end
+        end
+    end
+end