text2path 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +14 -0
  4. data/README.md +40 -0
  5. data/lib/ext/savage/LICENSE +20 -0
  6. data/lib/ext/savage/README.rdoc +108 -0
  7. data/lib/ext/savage/Rakefile +39 -0
  8. data/lib/ext/savage/VERSION +1 -0
  9. data/lib/ext/savage/lib/savage/direction.rb +42 -0
  10. data/lib/ext/savage/lib/savage/direction_proxy.rb +19 -0
  11. data/lib/ext/savage/lib/savage/directions/arc_to.rb +29 -0
  12. data/lib/ext/savage/lib/savage/directions/close_path.rb +18 -0
  13. data/lib/ext/savage/lib/savage/directions/coordinate_target.rb +17 -0
  14. data/lib/ext/savage/lib/savage/directions/cubic_curve_to.rb +40 -0
  15. data/lib/ext/savage/lib/savage/directions/horizontal_to.rb +19 -0
  16. data/lib/ext/savage/lib/savage/directions/line_to.rb +15 -0
  17. data/lib/ext/savage/lib/savage/directions/move_to.rb +15 -0
  18. data/lib/ext/savage/lib/savage/directions/point_target.rb +17 -0
  19. data/lib/ext/savage/lib/savage/directions/quadratic_curve_to.rb +44 -0
  20. data/lib/ext/savage/lib/savage/directions/vertical_to.rb +19 -0
  21. data/lib/ext/savage/lib/savage/parser.rb +108 -0
  22. data/lib/ext/savage/lib/savage/path.rb +50 -0
  23. data/lib/ext/savage/lib/savage/sub_path.rb +54 -0
  24. data/lib/ext/savage/lib/savage/transformable.rb +48 -0
  25. data/lib/ext/savage/lib/savage/utils.rb +7 -0
  26. data/lib/ext/savage/lib/savage.rb +3 -0
  27. data/lib/ext/savage/savage.gemspec +80 -0
  28. data/lib/ext/savage/spec/savage/directions/arc_to_spec.rb +97 -0
  29. data/lib/ext/savage/spec/savage/directions/close_path_spec.rb +30 -0
  30. data/lib/ext/savage/spec/savage/directions/cubic_curve_to_spec.rb +146 -0
  31. data/lib/ext/savage/spec/savage/directions/horizontal_to_spec.rb +10 -0
  32. data/lib/ext/savage/spec/savage/directions/line_to_spec.rb +10 -0
  33. data/lib/ext/savage/spec/savage/directions/move_to_spec.rb +10 -0
  34. data/lib/ext/savage/spec/savage/directions/point_spec.rb +12 -0
  35. data/lib/ext/savage/spec/savage/directions/quadratic_curve_spec.rb +123 -0
  36. data/lib/ext/savage/spec/savage/directions/vertical_to_spec.rb +10 -0
  37. data/lib/ext/savage/spec/savage/parser_spec.rb +250 -0
  38. data/lib/ext/savage/spec/savage/path_spec.rb +105 -0
  39. data/lib/ext/savage/spec/savage/sub_path_spec.rb +195 -0
  40. data/lib/ext/savage/spec/savage/transformable_spec.rb +62 -0
  41. data/lib/ext/savage/spec/savage_spec.rb +5 -0
  42. data/lib/ext/savage/spec/shared/command.rb +13 -0
  43. data/lib/ext/savage/spec/shared/coordinate_target.rb +36 -0
  44. data/lib/ext/savage/spec/shared/direction.rb +29 -0
  45. data/lib/ext/savage/spec/shared/point_target.rb +45 -0
  46. data/lib/ext/savage/spec/spec_helper.rb +36 -0
  47. data/lib/text2path/converter.rb +65 -0
  48. data/lib/text2path/glyph.rb +13 -0
  49. data/lib/text2path/svg_font.rb +92 -0
  50. data/lib/text2path/svg_path.rb +38 -0
  51. data/lib/text2path/version.rb +3 -0
  52. data/lib/text2path.rb +10 -0
  53. data/libpeerconnection.log +0 -0
  54. data/out.svg +1 -0
  55. data/test.rb +6 -0
  56. metadata +127 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 26be4b45234941e24e0d27d2f836eac4de6b05ba
4
+ data.tar.gz: a6f2ad6421d8f76d39d82724c89b48ce711d8e6e
5
+ SHA512:
6
+ metadata.gz: b4ec9fff758cc8f14284a0e1da00441895361d3008cb5c09adff07694ea588a34a611e4d9525b5b92e6b6cd60daefc7ebc96ee5bc4c767bf3f368766a1fac58e
7
+ data.tar.gz: ce6910fa7f327ea147f3b1ad93665e502d72110d2a10734a00b2436769c032a5576880bf3f5a0a9244c74cb63fc35988376742c29bdc2c726b5bc709a53ab6b5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://ruby.taobao.org'
2
+
3
+ gem 'nokogiri'
4
+ gem 'htmlentities'
data/Gemfile.lock ADDED
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: http://ruby.taobao.org/
3
+ specs:
4
+ htmlentities (4.3.1)
5
+ mini_portile (0.5.1)
6
+ nokogiri (1.6.0)
7
+ mini_portile (~> 0.5.0)
8
+
9
+ PLATFORMS
10
+ ruby
11
+
12
+ DEPENDENCIES
13
+ htmlentities
14
+ nokogiri
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Text2Path
2
+
3
+ This library convert text into svg paths. For example:
4
+
5
+ ~~~ruby
6
+ require 'text2path'
7
+
8
+ # load font from pre-built svg font file
9
+ # You can make svg font from .ttf or other formats with Batik tools
10
+ font = Text2Path::SvgFont.load 'assets/fonts/arial.svg'
11
+
12
+ # Code below converts text to svg output that can be displayed in browsers:
13
+ Text2Path.convert( 'Hello, world!', font, font_size: 20 ).to_svg
14
+
15
+ # <?xml version="1.0" standalone="no"?>
16
+ # <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
17
+ # <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
18
+ # <g>
19
+ # <path d="M4.296875 34..." />
20
+ # <path d="M4.296875 34..." />
21
+ # <path d="M4.296875 34..." />
22
+ # <path d="M4.296875 34..." />
23
+ # </g>
24
+ # </svg>
25
+
26
+ # This convert text to an array containing a list of Savage::Path instance
27
+ Text2Path.convert( 'Hello, world!', font, font_size: 20 ).to_paths
28
+
29
+ ~~~
30
+
31
+ ## helpful infomation
32
+ [generating svg font file](http://xmlgraphics.apache.org/batik/tools/font-converter.html)
33
+
34
+ ## TODO
35
+ This project is at its very beginning, features missing:
36
+
37
+ 1. support right-to-left text directions
38
+ 2. support vertical orientation
39
+ 3. support more options like font-weight, font-style, kerning.. etc.
40
+ 4. helper to create svg fonts
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jeremy Holland
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,108 @@
1
+ = Savage
2
+
3
+ A little gem for extracting and manipulating SVG vector path data, Savage will make your life easier when it comes time to dynamically draw new and manipulate existing SVG paths. No more wacky regex to capture those cryptic path data strings; just pass the whole "d" attribute into Savage's parser, et voilá: You'll be given an instance of Savage::Path, replete with an array of subpaths split on the "move to" commands (to better help with those pesky winding issues). In turn, each subpath contains its own array of directions that can be swapped around, reconfigured, and otherwise manipulated to your heart's content in a truly human-readable way.
4
+
5
+ == Latest Update
6
+
7
+ Version 1.2.0 includes a new array abstraction (see below) and optimized implementation of Direction#to_command, courtesy of {bdon}[https://github.com/bdon]!
8
+
9
+ == Usage
10
+
11
+ <b>Parsing existing data</b>
12
+
13
+ Easy-peasy Japanesey. Just take a look at the below:
14
+
15
+ path = Savage::Parser.parse my_path_data_string
16
+ path.subpaths # [subpath_1, subpath_2, ... subpath_n]
17
+ path.subpaths.last.directions # [direction_1, direction_2, ... direction_n]
18
+
19
+ Once extracted, you can manipulate, add, and remove the subpaths and directions as you see fit. See below for instructions on using the various direction types
20
+
21
+ <b>Creating from scratch</b>
22
+
23
+ What if you want to roll your own from the get-go? No problem:
24
+
25
+ path = Savage::Path.new do |p|
26
+ p.move_to 100, 200
27
+ p.line_to 300, 400
28
+ p.horizontal_to 500
29
+ p.close_path
30
+ end
31
+
32
+ We'll learn more about the actual drawing methods here shortly, but suffice it to say they are provided both as methods on the constructor block parameter for the sake of your visual organization, and the path itself after instantiation, as below:
33
+
34
+ path = Savage::Path.new
35
+ path.move_to 100, 200
36
+ path.line_to 300, 400
37
+ path.horizontal_to 500
38
+ path.close_path
39
+
40
+ <b>Drawing with Savage</b>
41
+
42
+ So what are the different directions we can give our virtual pen? Here I'll refer you first to the SVG 1.1 specification by our friends down at the W3C, as they can describe all the specifics much better than I: http://www.w3.org/TR/SVG/paths.html#PathData. Each "command" listed in that section has an analog method on the Savage::SubPath class as well as the Savage::Path class (calling one of the methods on an instance of the Savage::Path class actually just delegates the call to the last SubPath in that path's selection of subpaths). Every parameter of these methods is expected to be a Numeric except where noted otherwise. The methods are as follows:
43
+
44
+ * <b>path.move_to(target_x, target_y)</b> - Move the 'pen' to the specified coordinates on the 'canvas' without drawing anything. Note that you can only call this method on a subpath if it's otherwise empty. Most of the time, just call this on an instance of the Path class, which will actually create a new subpath for you to continue drawing into
45
+ * <b>path.close_path()</b> - Draw a straight line from the current position to the coordinates of the start of the current subpath
46
+ * <b>path.line_to(target_x, target_y)</b> - Draw a straight line from the current position to the specified coordinates
47
+ * <b>path.horizontal_to(target_x)</b> - Draw a straight horizontal line to the provided x coordinate
48
+ * <b>path.vertical_to(target_y)</b> - Draw a straight vertical line to the provided y coordinate
49
+ * <b>path.cubic_curve_to(control_1_x, control_1_y, control_2_x, control_2_y, target_x, target_y)</b> - Draw a cubic Bézier curve from the current position to the target coordinates (target_x/y) using the control points specified - see the SVG specification referred to above for more info
50
+ * <b>path.cubic_curve_to(control_2_x, control_2_y, target_x, target_y)</b> - Shorthand cubic Bézier curve; assumes continuation of previous curve. See SVG specification
51
+ * <b>path.quadratic_curve_to(control_x, control_y, target_x, target_y)</b> - Draw a quadratic Bézier curve from the current position to the target coordinates (target_x/y) using the control point specified - see the SVG specification referred to above for more info
52
+ * <b>path.quadratic_curve_to(target_x, target_y)</b> - Shorthand quadratic Bézier curve; assumes continuation of previous curve. See SVG specification
53
+ * <b>path.arc_to(radius_x,radius_y,rotation,large_arc_flag,sweep_flag,target_x,target_y)</b> - This is a doozy and complicated as sin to explain, so we'll let the W3C do it for us - see the spec. Otherwise, be aware that the large_arc_flag and sweep_flag arguments are expected to be boolean values, not Numeric.
54
+
55
+ There you have it, pretty much right out of the book. One thing to keep in mind is that all of these methods use ABSOLUTE coordinates by default; if you want to use relative coordinates (based on the current position of the 'pen'), just pass false as the final argument to any of them:
56
+
57
+ path.line_to 100, 200, false
58
+
59
+ Each of these commands just creates a new instance of the appropriate subclass of the Savage::Direction class and pushes it onto the end of the directions list in question. Don't necessarily want to draw onto the end? Just create your own instance using the standard constructor and insert it wherever it butters your biscuit so to do:
60
+
61
+ direction = Savage::Directions::LineTo.new(100,200)
62
+ subpath.directions.insert(3,direction)
63
+
64
+ Did you mess up before and need to change a directions coordinates after you drew it (or, more likely, after you parsed it out of some pre-existing data)? No problem:
65
+
66
+ direction = Savage::Directions::LineTo.new(100,200)
67
+ direction.target.x = 234
68
+
69
+ As you may have guessed, Savage::Path#subpaths and Savage::SubPath#directions are both just good old-fashioned arrays, so you can fiddle with them using all your favorite Enumerable tricks as always.
70
+
71
+ <b>Abstracting it to an array (new in v1.2.0!)</b>
72
+
73
+ Bang!:
74
+
75
+ path.to_a # => ["M", 100, 200, ...]
76
+
77
+ <b>Turning it back into a path data string</b>
78
+
79
+ Bang!:
80
+
81
+ path.to_command # => 'M100 200-342 43.5Q-34.88 123.9 13-532' or whatever...
82
+
83
+ Note that the #to_command method exists on Savage::Path, Savage::SubPath, and each subclass of Savage::Direction, so you can be as fine-grained with it as needed.
84
+
85
+ == Issues / Feature Requests
86
+
87
+ I have no doubt that will be some problems with this thing, as well as features missing that some of you fine folks might want. Please, please, please check Github's issue-tracking system to see if a ticket for the problem has already been submitted, or create a new one if not. I'll check the issues listed therein regularly, but you email me directly at your own risk (and by risk, I mean risk of not receiving a reply). :)
88
+
89
+ == Note on Patches/Pull Requests
90
+
91
+ * Fork the project.
92
+ * Make your feature addition or bug fix. I very much prefer feature-specific branches.
93
+ * Add specs for it. This is important so I don't break it in a future version unintentionally, which I can guarantee I will do.
94
+ * Commit. Do not mess with rakefile, version, or history, please.
95
+ * Send me a pull request - again, bonus points for topic branches.
96
+
97
+ == Contributors
98
+
99
+ * Jeremy Holland (jeremy@jeremypholland.com, github:{awebneck}[https://github.com/awebneck]) -- author
100
+ * Christoffer Klang (toffeklang@yahoo.se, github:{christoffer}[https://github.com/christoffer]) -- regexp improvements
101
+ * Bartosz Dz (github:{MatmaRex}[https://github.com/matmarex]) -- parser improvements, scientific notation support, bug fixes
102
+ * Brandon Liu (github:{bdon}[https://github.com/bdon]) -- spec improvements, array abstraction
103
+
104
+ == Copyright
105
+
106
+ Copyright (c) 2010 Jeremy Holland. See LICENSE for details.
107
+
108
+
@@ -0,0 +1,39 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "savage"
8
+ gem.summary = %Q{A little library to manipulate SVG path data}
9
+ gem.description = %Q{A little gem for extracting and manipulating SVG vector path data.}
10
+ gem.email = "jeremy@jeremypholland.com"
11
+ gem.homepage = "http://github.com/awebneck/savage"
12
+ gem.authors = ["Jeremy Holland"]
13
+ gem.add_development_dependency "rspec", ">= 2.3.0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rspec/core/rake_task'
22
+ RSpec::Core::RakeTask.new(:spec)
23
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
24
+ spec.rcov = true
25
+ end
26
+
27
+ task :spec => :check_dependencies
28
+
29
+ task :default => :spec
30
+
31
+ require 'rake/rdoctask'
32
+ Rake::RDocTask.new do |rdoc|
33
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
34
+
35
+ rdoc.rdoc_dir = 'rdoc'
36
+ rdoc.title = "savage #{version}"
37
+ rdoc.rdoc_files.include('README*')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ end
@@ -0,0 +1 @@
1
+ 1.2.0
@@ -0,0 +1,42 @@
1
+ module Savage
2
+ module Directions
3
+ Point = Struct.new :x, :y
4
+ end
5
+
6
+ class Direction
7
+
8
+ include Utils
9
+ include Transformable
10
+
11
+ def initialize(absolute)
12
+ @absolute = absolute
13
+ end
14
+
15
+ def absolute?
16
+ @absolute
17
+ end
18
+
19
+ def relative?
20
+ !absolute?
21
+ end
22
+
23
+ def to_command
24
+ arr = to_a
25
+ arr.map! do |x|
26
+ x.to_i == x ? x.to_i : x
27
+ end
28
+ arr[0] + arr[1..-1].join(' ').gsub(/ -/,'-')
29
+ end
30
+ end
31
+ end
32
+
33
+ require File.dirname(__FILE__) + "/directions/close_path"
34
+ require File.dirname(__FILE__) + "/directions/coordinate_target"
35
+ require File.dirname(__FILE__) + "/directions/horizontal_to"
36
+ require File.dirname(__FILE__) + "/directions/vertical_to"
37
+ require File.dirname(__FILE__) + "/directions/point_target"
38
+ require File.dirname(__FILE__) + "/directions/move_to"
39
+ require File.dirname(__FILE__) + "/directions/line_to"
40
+ require File.dirname(__FILE__) + "/directions/quadratic_curve_to"
41
+ require File.dirname(__FILE__) + "/directions/cubic_curve_to"
42
+ require File.dirname(__FILE__) + "/directions/arc_to"
@@ -0,0 +1,19 @@
1
+ module Savage
2
+ module DirectionProxy
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def define_proxies(&block)
9
+ Directions.constants.each do |constant_sym|
10
+ constant = (constant_sym.is_a?(Symbol)) ? constant_sym.to_s : constant_sym
11
+ unless %w[PointTarget CoordinateTarget Point MoveTo].include? constant
12
+ sym = constant.to_s.gsub(/[A-Z]/) { |p| '_' + p.downcase }[1..-1].to_sym
13
+ block.call(sym,constant)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module Savage
2
+ module Directions
3
+ class ArcTo < PointTarget
4
+ attr_accessor :radius, :rotation, :large_arc, :sweep
5
+
6
+ def initialize(radius_x, radius_y, rotation, large_arc, sweep, target_x, target_y, absolute=true)
7
+ super(target_x, target_y, absolute)
8
+ @radius = Point.new(radius_x, radius_y)
9
+ @rotation = rotation
10
+ @large_arc = large_arc.is_a?(Numeric) ? large_arc > 0 : large_arc
11
+ @sweep = sweep.is_a?(Numeric) ? sweep > 0 : sweep
12
+ end
13
+
14
+ def to_a
15
+ [command_code, @radius.x, @radius.y, @rotation, bool_to_int(@large_arc), bool_to_int(@sweep), target.x, target.y]
16
+ end
17
+
18
+ def command_code
19
+ (absolute?) ? 'A' : 'a'
20
+ end
21
+
22
+ def transform(scale_x, skew_x, skew_y, scale_y, tx, ty)
23
+ # relative arc_to dont't need to be tranlated
24
+ tx = ty = 0 if relative?
25
+ transform_dot( target, scale_x, skew_x, skew_y, scale_y, tx, ty)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ module Savage
2
+ module Directions
3
+ class ClosePath < Direction
4
+
5
+ def initialize(absolute=true)
6
+ super(absolute)
7
+ end
8
+
9
+ def to_a
10
+ [command_code]
11
+ end
12
+
13
+ def command_code
14
+ (absolute?) ? 'Z' : 'z'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module Savage
2
+ module Directions
3
+ class CoordinateTarget < Direction
4
+
5
+ attr_accessor :target
6
+
7
+ def initialize(target, absolute=true)
8
+ @target = target
9
+ super(absolute)
10
+ end
11
+
12
+ def to_a
13
+ [command_code, @target]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ module Savage
2
+ module Directions
3
+ class CubicCurveTo < QuadraticCurveTo
4
+ attr_accessor :control_1
5
+
6
+ def initialize(*args)
7
+ raise ArgumentError if args.length < 4
8
+ case args.length
9
+ when 4
10
+ super(args[0],args[1],args[2],args[3],true)
11
+ when 5
12
+ raise ArgumentError if args[4].kind_of?(Numeric)
13
+ super(args[0],args[1],args[2],args[3],args[4])
14
+ when 6
15
+ @control_1 = Point.new(args[0],args[1])
16
+ super(args[2],args[3],args[4],args[5],true)
17
+ when 7
18
+ @control_1 = Point.new(args[0],args[1])
19
+ super(args[2],args[3],args[4],args[5],args[6])
20
+ end
21
+ end
22
+
23
+ def to_a
24
+ if @control_1
25
+ [command_code, @control_1.x, @control_1.y, @control.x, @control.y, @target.x, @target.y]
26
+ else
27
+ [command_code, @control.x, @control.y, @target.x, @target.y]
28
+ end
29
+ end
30
+
31
+ def control_2; @control; end
32
+ def control_2=(value); @control = value; end
33
+
34
+ def command_code
35
+ return (absolute?) ? 'C' : 'c' if @control_1
36
+ (absolute?) ? 'S' : 's'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module Savage
2
+ module Directions
3
+ class HorizontalTo < CoordinateTarget
4
+ def command_code
5
+ (absolute?) ? 'H' : 'h'
6
+ end
7
+
8
+ def transform(scale_x, skew_x, skew_y, scale_y, tx, ty)
9
+
10
+ unless skew_y.zero?
11
+ raise 'rotating or skewing (in Y axis) an "horizontal_to" direction is not supported yet.'
12
+ end
13
+
14
+ self.target *= scale_x
15
+ self.target += tx if absolute?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Savage
2
+ module Directions
3
+ class LineTo < PointTarget
4
+ def command_code
5
+ (absolute?) ? 'L' : 'l'
6
+ end
7
+
8
+ def transform(scale_x, skew_x, skew_y, scale_y, tx, ty)
9
+ # relative line_to dont't need to be tranlated
10
+ tx = ty = 0 if relative?
11
+ transform_dot( target, scale_x, skew_x, skew_y, scale_y, tx, ty)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Savage
2
+ module Directions
3
+ class MoveTo < PointTarget
4
+ def command_code
5
+ (absolute?) ? 'M' : 'm'
6
+ end
7
+
8
+ def transform(scale_x, skew_x, skew_y, scale_y, tx, ty)
9
+ # relative move_to dont't need to be tranlated
10
+ tx = ty = 0 if relative?
11
+ transform_dot( target, scale_x, skew_x, skew_y, scale_y, tx, ty )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Savage
2
+ module Directions
3
+ class PointTarget < Direction
4
+
5
+ attr_accessor :target
6
+
7
+ def initialize(x, y, absolute=true)
8
+ @target = Point.new(x,y)
9
+ super(absolute)
10
+ end
11
+
12
+ def to_a
13
+ [command_code, @target.x, @target.y]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ module Savage
2
+ module Directions
3
+ class QuadraticCurveTo < PointTarget
4
+ attr_accessor :control
5
+
6
+ def initialize(*args)
7
+ raise ArgumentError if args.length < 2
8
+ case args.length
9
+ when 2
10
+ super(args[0],args[1],true)
11
+ when 3
12
+ raise ArgumentError if args[2].kind_of?(Numeric)
13
+ super(args[0],args[1],args[2])
14
+ when 4
15
+ @control = Point.new(args[0],args[1])
16
+ super(args[2],args[3],true)
17
+ when 5
18
+ @control = Point.new(args[0],args[1])
19
+ super(args[2],args[3],args[4])
20
+ end
21
+ end
22
+
23
+ def to_a
24
+ if @control
25
+ [command_code, @control.x, @control.y, @target.x, @target.y]
26
+ else
27
+ [command_code, @target.x, @target.y]
28
+ end
29
+ end
30
+
31
+ def command_code
32
+ return (absolute?) ? 'Q' : 'q' if @control
33
+ (absolute?) ? 'T' : 't'
34
+ end
35
+
36
+ def transform(scale_x, skew_x, skew_y, scale_y, tx, ty)
37
+ # relative line_to dont't need to be tranlated
38
+ tx = ty = 0 if relative?
39
+ transform_dot( target, scale_x, skew_x, skew_y, scale_y, tx, ty)
40
+ transform_dot( control, scale_x, skew_x, skew_y, scale_y, tx, ty)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,19 @@
1
+ module Savage
2
+ module Directions
3
+ class VerticalTo < CoordinateTarget
4
+ def command_code
5
+ (absolute?) ? 'V' : 'v'
6
+ end
7
+
8
+ def transform(scale_x, skew_x, skew_y, scale_y, tx, ty)
9
+
10
+ unless skew_x.zero?
11
+ raise 'rotating or skewing (in X axis) an "vertical_to" direction is not supported yet.'
12
+ end
13
+
14
+ self.target *= scale_y
15
+ self.target += ty if absolute?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,108 @@
1
+ module Savage
2
+ class Parser
3
+ DIRECTIONS = {
4
+ :m => {:class => Directions::MoveTo,
5
+ :args => 2},
6
+ :l => {:class => Directions::LineTo,
7
+ :args => 2},
8
+ :h => {:class => Directions::HorizontalTo,
9
+ :args => 1},
10
+ :v => {:class => Directions::VerticalTo,
11
+ :args => 1},
12
+ :c => {:class => Directions::CubicCurveTo,
13
+ :args => 6},
14
+ :s => {:class => Directions::CubicCurveTo,
15
+ :args => 4},
16
+ :q => {:class => Directions::QuadraticCurveTo,
17
+ :args => 4},
18
+ :t => {:class => Directions::QuadraticCurveTo,
19
+ :args => 2},
20
+ :a => {:class => Directions::ArcTo,
21
+ :args => 7}
22
+ }
23
+
24
+ class << self
25
+ def parse(parsable)
26
+ raise TypeError if parsable.class != String
27
+ subpaths = extract_subpaths parsable
28
+ raise TypeError if (subpaths.empty?)
29
+ path = Path.new
30
+ path.subpaths = []
31
+ subpaths.each_with_index do |subpath, i|
32
+ path.subpaths << parse_subpath(subpath, i == 0)
33
+ end
34
+ path
35
+ end
36
+
37
+ private
38
+ def extract_subpaths(parsable)
39
+ subpaths = []
40
+ if move_index = parsable.index(/[Mm]/)
41
+ subpaths << parsable[0...move_index] if move_index > 0
42
+ parsable.scan(/[Mm](?:\d|[eE.,+-]|[LlHhVvQqCcTtSsAaZz]|\W)+/m) do |match_group|
43
+ subpaths << $&
44
+ end
45
+ else
46
+ subpaths << parsable
47
+ end
48
+ subpaths
49
+ end
50
+
51
+ def parse_subpath(parsable, force_absolute=false)
52
+ subpath = SubPath.new
53
+ subpath.directions = extract_directions parsable, force_absolute
54
+ subpath
55
+ end
56
+
57
+ def extract_directions(parsable, force_absolute=false)
58
+ directions = []
59
+ i = 0
60
+ parsable.scan(/[MmLlHhVvQqCcTtSsAaZz](?:\d|[eE.,+-]|\W)*/m) do |match_group|
61
+ direction = build_direction $&, force_absolute && i == 0
62
+ if direction.kind_of?(Array)
63
+ directions.concat direction
64
+ else
65
+ directions << direction
66
+ end
67
+ i += 1
68
+ end
69
+ directions
70
+ end
71
+
72
+ def build_direction(parsable, force_absolute=false)
73
+ directions = []
74
+ @coordinates = extract_coordinates parsable
75
+ recurse_code = parsable[0,1]
76
+ first_absolute = force_absolute
77
+
78
+ # we need to handle this separately, since ClosePath doesn't take any coordinates
79
+ if @coordinates.empty? && recurse_code =~ /[Zz]/
80
+ directions << Directions::ClosePath.new(parsable[0,1] == parsable[0,1].upcase)
81
+ end
82
+
83
+ until @coordinates.empty?
84
+ absolute = (first_absolute || parsable[0,1] == parsable[0,1].upcase)
85
+ directions << construct_direction(recurse_code.strip[0].downcase.intern, absolute)
86
+ recurse_code = 'L' if recurse_code.downcase =~ /m/
87
+ first_absolute = false
88
+ end
89
+
90
+ directions
91
+ end
92
+
93
+ def construct_direction(recurse_code, absolute)
94
+ args = @coordinates.shift DIRECTIONS[recurse_code][:args]
95
+ raise TypeError if args.any?(&:nil?)
96
+ DIRECTIONS[recurse_code][:class].new(*args, absolute)
97
+ end
98
+
99
+ def extract_coordinates(command_string)
100
+ coordinates = []
101
+ command_string.scan(/-?\d+(\.\d+)?([eE][+-]?\d+)?/) do |match_group|
102
+ coordinates << $&.to_f
103
+ end
104
+ coordinates
105
+ end
106
+ end
107
+ end
108
+ end