home *** CD-ROM | disk | FTP | other *** search
Ruby Source | 2011-07-08 | 6.1 KB | 208 lines |
- #!/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
- #TODO: create new exception
- 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|
- raise 'Invalid number of parameters' if command and isCommand
-
- unless command
- if isCommand
- raise 'Invalid path, must begin with moveto.' \
- unless lastCommand or token.upcase == 'M'
- command = token
- else
- #command was omited
- #use last command's implicit next command
- raise 'Invalid path, no initial command.' unless lastCommand
- if lastCommand =~ /[A-Z]/
- command = PATHDEFS[lastCommand].implicit_next
- else
- command = PATHDEFS[lastCommand.upcase].implicit_next.downcase
- end
- end
- outputCommand = command.upcase
- end
-
- unless 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 _transformPath(p)
- p.each do |cmd,params|
- coord_types = PATHDEFS[cmd].coord_types
- for i in 0...(params.length)
- yield params, i, coord_types[i]
- end
- end
- end
-
- def translatePath(p, x, y)
- _transformPath(p) do |params, i, coord_type|
- case coord_type
- when :x: params[i] += x
- when :y: params[i] += y
- end
- end
- end
-
- def scalePath(p, x, y)
- _transformPath(p) do |params, i, coord_type|
- case coord_type
- when :x: params[i] *= x
- when :y: params[i] *= y
- end
- end
- end
-
- def rotatePath(p, a, cx = 0, cy = 0)
- return p if a == 0
- _transformPath(p) do |params, i, coord_type|
- if coord_type == :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
-