3 * Copyright 2012 Dan Vanderkam (danvdk@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
8 * @fileoverview Adds support for dashed lines to the HTML5 canvas.
11 * var ctx = canvas.getContext("2d");
12 * ctx.installPattern([10, 5]) // draw 10 pixels, skip 5 pixels, repeat.
14 * ctx.moveTo(100, 100); // start the first line segment.
15 * ctx.lineTo(150, 200);
16 * ctx.lineTo(200, 100);
17 * ctx.moveTo(300, 150); // start a second, unconnected line
18 * ctx.lineTo(400, 250);
20 * ctx.stroke(); // draw the dashed line.
21 * ctx.uninstallPattern();
23 * This is designed to leave the canvas untouched when it's not used.
24 * If you never install a pattern, or call uninstallPattern(), then the canvas
25 * will be exactly as it would have if you'd never used this library. The only
26 * difference from the standard canvas will be the "installPattern" method of
27 * the drawing context.
31 * Change the stroking style of the canvas drawing context from a solid line to
32 * a pattern (e.g. dashes, dash-dot-dash, etc.)
34 * Once you've installed the pattern, you can draw with it by using the
35 * beginPath(), moveTo(), lineTo() and stroke() method calls. Note that some
36 * more advanced methods (e.g. quadraticCurveTo() and bezierCurveTo()) are not
37 * supported. See file overview for a working example.
39 * Side effects of calling this method include adding an "isPatternInstalled"
40 * property and "uninstallPattern" method to this particular canvas context.
41 * You must call uninstallPattern() before calling installPattern() again.
43 * @param {Array.<number>} pattern A description of the stroke pattern. Even
44 * indices indicate a draw and odd indices indicate a gap (in pixels). The
45 * array should have a even length as any odd lengthed array could be expressed
46 * as a smaller even length array.
48 CanvasRenderingContext2D
.prototype.installPattern
= function(pattern
) {
51 if (typeof(this.isPatternInstalled
) !== 'undefined') {
52 throw "Must un-install old line pattern before installing a new one.";
54 this.isPatternInstalled
= true;
56 var dashedLineToHistory
= [0, 0];
58 // list of connected line segements:
59 // [ [x1, y1], ..., [xn, yn] ], [ [x1, y1], ..., [xn, yn] ]
62 // Stash away copies of the unmodified line-drawing functions.
63 var realBeginPath
= this.beginPath
;
64 var realLineTo
= this.lineTo
;
65 var realMoveTo
= this.moveTo
;
66 var realStroke
= this.stroke
;
68 /** @type {function()|undefined} */
69 this.uninstallPattern
= function() {
70 this.beginPath
= realBeginPath
;
71 this.lineTo
= realLineTo
;
72 this.moveTo
= realMoveTo
;
73 this.stroke
= realStroke
;
74 this.uninstallPattern
= undefined
;
75 this.isPatternInstalled
= undefined
;
78 // Keep our own copies of the line segments as they're drawn.
79 this.beginPath
= function() {
81 realBeginPath
.call(this);
83 this.moveTo
= function(x
, y
) {
84 segments
.push([[x
, y
]]);
85 realMoveTo
.call(this, x
, y
);
87 this.lineTo
= function(x
, y
) {
88 var last
= segments
[segments
.length
- 1];
92 this.stroke
= function() {
93 if (segments
.length
=== 0) {
94 // Maybe the user is drawing something other than a line.
95 // TODO(danvk): test this case.
96 realStroke
.call(this);
100 for (var i
= 0; i
< segments
.length
; i
++) {
101 var seg
= segments
[i
];
102 var x1
= seg
[0][0], y1
= seg
[0][1];
103 for (var j
= 1; j
< seg
.length
; j
++) {
104 // Draw a dashed line from (x1, y1) - (x2, y2)
105 var x2
= seg
[j
][0], y2
= seg
[j
][1];
108 // Calculate transformation parameters
111 var len
= Math
.sqrt(dx
*dx
+ dy
*dy
);
112 var rot
= Math
.atan2(dy
, dx
);
114 // Set transformation
115 this.translate(x1
, y1
);
116 realMoveTo
.call(this, 0, 0);
119 // Set last pattern index we used for this pattern.
120 var patternIndex
= dashedLineToHistory
[0];
123 // Get the length of the pattern segment we are dealing with.
124 var segment
= pattern
[patternIndex
];
125 // If our last draw didn't complete the pattern segment all the way
126 // we will try to finish it. Otherwise we will try to do the whole
128 if (dashedLineToHistory
[1]) {
129 x
+= dashedLineToHistory
[1];
135 // We were unable to complete this pattern index all the way, keep
136 // where we are the history so our next draw continues where we
137 // left off in the pattern.
138 dashedLineToHistory
= [patternIndex
, x
-len
];
141 // We completed this patternIndex, we put in the history that we
142 // are on the beginning of the next segment.
143 dashedLineToHistory
= [(patternIndex
+1)%pattern
.length
, 0];
146 // We do a line on a even pattern index and just move on a odd
147 // pattern index. The move is the empty space in the dash.
148 if (patternIndex
% 2 === 0) {
149 realLineTo
.call(this, x
, 0);
151 realMoveTo
.call(this, x
, 0);
154 // If we are not done, next loop process the next pattern segment, or
155 // the first segment again if we are at the end of the pattern.
156 patternIndex
= (patternIndex
+1) % pattern
.length
;
163 realStroke
.call(this);
169 * Removes the previously-installed pattern.
170 * You must call installPattern() before calling this. You can install at most
171 * one pattern at a time--there is no pattern stack.
173 CanvasRenderingContext2D
.prototype.uninstallPattern
= function() {
174 // This will be replaced by a non-error version when a pattern is installed.
175 throw "Must install a line pattern before uninstalling it.";