home *** CD-ROM | disk | FTP | other *** search
/ Chip 2011 November / CHIP_2011_11.iso / Programy / Narzedzia / Inkscape / Inkscape-0.48.2-1-win32.exe / share / extensions / simplepath.rb < prev    next >
Encoding:
Ruby Source  |  2011-07-08  |  6.1 KB  |  208 lines

  1. #!/usr/bin/env ruby
  2.  
  3. # simplepath.rb
  4. # functions for digesting paths into a simple list structure
  5. #
  6. # Ruby port by MenTaLguY
  7. #
  8. # Copyright (C) 2005 Aaron Spike  <aaron@ekips.org>
  9. # Copyright (C) 2006 MenTaLguY  <mental@rydia.net>
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. # GNU General Public License for more details.
  20. #
  21. # You should have received a copy of the GNU General Public License
  22. # along with this program; if not, write to the Free Software
  23. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  24.  
  25. require 'strscan'
  26.  
  27. def lexPath(d)
  28.     # iterator which breaks path data 
  29.     # identifies command and parameter tokens
  30.  
  31.     scanner = StringScanner.new(d)
  32.  
  33.     delim = /[ \t\r\n,]+/
  34.     command = /[MLHVCSQTAZmlhvcsqtaz]/
  35.     parameter = /(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/
  36.  
  37.     until scanner.eos?
  38.         scanner.skip(delim)
  39.         if m = scanner.scan(command)
  40.             yield m, true
  41.         elsif m = scanner.scan(parameter)
  42.             yield m, false
  43.         else
  44.             #TODO: create new exception
  45.             raise 'Invalid path data!'
  46.         end
  47.     end
  48. end
  49.  
  50. PathDef = Struct.new :implicit_next, :param_count, :casts, :coord_types
  51. PATHDEFS = {
  52.     'M' => PathDef['L', 2, [:to_f, :to_f], [:x,:y]], 
  53.     'L' => PathDef['L', 2, [:to_f, :to_f], [:x,:y]], 
  54.     'H' => PathDef['H', 1, [:to_f], [:x]], 
  55.     'V' => PathDef['V', 1, [:to_f], [:y]], 
  56.     'C' => PathDef['C', 6, [:to_f, :to_f, :to_f, :to_f, :to_f, :to_f], [:x,:y,:x,:y,:x,:y]], 
  57.     'S' => PathDef['S', 4, [:to_f, :to_f, :to_f, :to_f], [:x,:y,:x,:y]], 
  58.     'Q' => PathDef['Q', 4, [:to_f, :to_f, :to_f, :to_f], [:x,:y,:x,:y]], 
  59.     'T' => PathDef['T', 2, [:to_f, :to_f], [:x,:y]], 
  60.     '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]], 
  61.     'Z' => PathDef['L', 0, [], []]
  62. }
  63.  
  64. def parsePath(d)
  65.     # Parse SVG path and return an array of segments.
  66.     # Removes all shorthand notation.
  67.     # Converts coordinates to absolute.
  68.  
  69.     retval = []
  70.  
  71.     command = nil
  72.     outputCommand = nil
  73.     params = []
  74.  
  75.     pen = [0.0,0.0]
  76.     subPathStart = pen
  77.     lastControl = pen
  78.     lastCommand = nil
  79.  
  80.     lexPath(d) do |token, isCommand|
  81.         raise 'Invalid number of parameters' if command and isCommand
  82.  
  83.         unless command
  84.             if isCommand
  85.                 raise 'Invalid path, must begin with moveto.' \
  86.                   unless lastCommand or token.upcase == 'M'
  87.                 command = token
  88.             else
  89.                 #command was omited
  90.                 #use last command's implicit next command
  91.                 raise 'Invalid path, no initial command.' unless lastCommand
  92.                 if lastCommand =~ /[A-Z]/
  93.                     command = PATHDEFS[lastCommand].implicit_next
  94.                 else
  95.                     command = PATHDEFS[lastCommand.upcase].implicit_next.downcase
  96.                 end
  97.             end
  98.             outputCommand = command.upcase
  99.         end
  100.  
  101.         unless isCommand
  102.             param = token.send PATHDEFS[outputCommand].casts[params.length]
  103.             if command =~ /[a-z]/
  104.                 case PATHDEFS[outputCommand].coord_types[params.length]
  105.                 when :x: param += pen[0]
  106.                 when :y: param += pen[1]
  107.                 end
  108.             end
  109.             params.push param
  110.         end
  111.  
  112.         if params.length == PATHDEFS[outputCommand].param_count
  113.  
  114.             #Flesh out shortcut notation
  115.             case outputCommand
  116.             when 'H','V'
  117.                 case outputCommand
  118.                 when 'H': params.push pen[1]
  119.                 when 'V': params.unshift pen[0]
  120.                 end
  121.                 outputCommand = 'L'
  122.             when 'S','T'
  123.                 params.unshift(pen[1]+(pen[1]-lastControl[1]))
  124.                 params.unshift(pen[0]+(pen[0]-lastControl[0]))
  125.                 case outputCommand
  126.                 when 'S': outputCommand = 'C'
  127.                 when 'T': outputCommand = 'Q'
  128.                 end
  129.             end
  130.  
  131.             #current values become "last" values
  132.             case outputCommand
  133.             when 'M'
  134.                 subPathStart = params[0,2]
  135.                 pen = subPathStart
  136.             when 'Z'
  137.                 pen = subPathStart
  138.             else
  139.                 pen = params[-2,2]
  140.             end
  141.  
  142.             case outputCommand
  143.             when 'Q','C'
  144.                 lastControl = params[-4,2]
  145.             else
  146.                 lastControl = pen
  147.             end
  148.  
  149.             lastCommand = command
  150.             retval.push [outputCommand,params]
  151.             command = nil
  152.             params = []
  153.         end
  154.     end
  155.  
  156.     raise 'Unexpected end of path' if command
  157.  
  158.     return retval
  159. end
  160.  
  161. def formatPath(a)
  162.     # Format SVG path data from an array
  163.     a.map { |cmd,params| "#{cmd} #{params.join(' ')}" }.join
  164. end
  165.  
  166. def _transformPath(p)
  167.     p.each do |cmd,params|
  168.         coord_types = PATHDEFS[cmd].coord_types
  169.         for i in 0...(params.length)
  170.             yield params, i, coord_types[i]
  171.         end
  172.     end
  173. end
  174.  
  175. def translatePath(p, x, y)
  176.     _transformPath(p) do |params, i, coord_type|
  177.         case coord_type
  178.         when :x: params[i] += x
  179.         when :y: params[i] += y
  180.         end
  181.     end
  182. end
  183.  
  184. def scalePath(p, x, y)
  185.     _transformPath(p) do |params, i, coord_type|
  186.         case coord_type
  187.         when :x: params[i] *= x
  188.         when :y: params[i] *= y
  189.         end
  190.     end
  191. end
  192.  
  193. def rotatePath(p, a, cx = 0, cy = 0)
  194.     return p if a == 0
  195.     _transformPath(p) do |params, i, coord_type|
  196.         if coord_type == :x
  197.             x = params[i] - cx
  198.             y = params[i + 1] - cy
  199.             r = Math.sqrt((x**2) + (y**2))
  200.             unless r.zero?
  201.                 theta = Math.atan2(y, x) + a
  202.                 params[i] = (r * Math.cos(theta)) + cx
  203.                 params[i + 1] = (r * Math.sin(theta)) + cy
  204.             end
  205.         end
  206.     end
  207. end
  208.