fabric-rails 1.0.12 → 1.0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +16 -0
- data/README.md +1 -1
- data/lib/fabric/rails/version.rb +2 -2
- data/vendor/assets/javascripts/event.js +1909 -0
- data/vendor/assets/javascripts/fabric.js +64 -16464
- data/vendor/assets/javascripts/fabric/HEADER.js +31 -0
- data/vendor/assets/javascripts/fabric/canvas.class.js +1007 -0
- data/vendor/assets/javascripts/fabric/canvas_animation.mixin.js +113 -0
- data/vendor/assets/javascripts/fabric/canvas_events.mixin.js +482 -0
- data/vendor/assets/javascripts/fabric/canvas_gestures.mixin.js +79 -0
- data/vendor/assets/javascripts/fabric/canvas_serialization.mixin.js +311 -0
- data/vendor/assets/javascripts/fabric/circle.class.js +182 -0
- data/vendor/assets/javascripts/fabric/color.class.js +284 -0
- data/vendor/assets/javascripts/fabric/ellipse.class.js +169 -0
- data/vendor/assets/javascripts/fabric/freedrawing.class.js +256 -0
- data/vendor/assets/javascripts/fabric/gradient.class.js +211 -0
- data/vendor/assets/javascripts/fabric/group.class.js +556 -0
- data/vendor/assets/javascripts/fabric/image.class.js +418 -0
- data/vendor/assets/javascripts/fabric/image_filters.js +809 -0
- data/vendor/assets/javascripts/fabric/intersection.class.js +178 -0
- data/vendor/assets/javascripts/fabric/line.class.js +188 -0
- data/vendor/assets/javascripts/fabric/log.js +26 -0
- data/vendor/assets/javascripts/fabric/node.js +149 -0
- data/vendor/assets/javascripts/fabric/object.class.js +1068 -0
- data/vendor/assets/javascripts/fabric/object_geometry.mixin.js +308 -0
- data/vendor/assets/javascripts/fabric/object_interactivity.mixin.js +496 -0
- data/vendor/assets/javascripts/fabric/object_origin.mixin.js +207 -0
- data/vendor/assets/javascripts/fabric/object_straightening.mixin.js +94 -0
- data/vendor/assets/javascripts/fabric/observable.mixin.js +91 -0
- data/vendor/assets/javascripts/fabric/parser.js +750 -0
- data/vendor/assets/javascripts/fabric/path.class.js +794 -0
- data/vendor/assets/javascripts/fabric/path_group.class.js +240 -0
- data/vendor/assets/javascripts/fabric/pattern.class.js +69 -0
- data/vendor/assets/javascripts/fabric/point.class.js +327 -0
- data/vendor/assets/javascripts/fabric/polygon.class.js +184 -0
- data/vendor/assets/javascripts/fabric/polyline.class.js +157 -0
- data/vendor/assets/javascripts/fabric/rect.class.js +298 -0
- data/vendor/assets/javascripts/fabric/scout.js +45 -0
- data/vendor/assets/javascripts/fabric/shadow.class.js +70 -0
- data/vendor/assets/javascripts/fabric/stateful.js +88 -0
- data/vendor/assets/javascripts/fabric/static_canvas.class.js +1298 -0
- data/vendor/assets/javascripts/fabric/text.class.js +934 -0
- data/vendor/assets/javascripts/fabric/triangle.class.js +108 -0
- data/vendor/assets/javascripts/fabric/util/anim_ease.js +360 -0
- data/vendor/assets/javascripts/fabric/util/dom_event.js +237 -0
- data/vendor/assets/javascripts/fabric/util/dom_misc.js +245 -0
- data/vendor/assets/javascripts/fabric/util/dom_request.js +72 -0
- data/vendor/assets/javascripts/fabric/util/dom_style.js +71 -0
- data/vendor/assets/javascripts/fabric/util/lang_array.js +250 -0
- data/vendor/assets/javascripts/fabric/util/lang_class.js +97 -0
- data/vendor/assets/javascripts/fabric/util/lang_function.js +35 -0
- data/vendor/assets/javascripts/fabric/util/lang_object.js +34 -0
- data/vendor/assets/javascripts/fabric/util/lang_string.js +60 -0
- data/vendor/assets/javascripts/fabric/util/misc.js +406 -0
- data/vendor/assets/javascripts/json2.js +491 -0
- metadata +53 -2
@@ -0,0 +1,934 @@
|
|
1
|
+
(function(global) {
|
2
|
+
|
3
|
+
"use strict";
|
4
|
+
|
5
|
+
var fabric = global.fabric || (global.fabric = { }),
|
6
|
+
extend = fabric.util.object.extend,
|
7
|
+
clone = fabric.util.object.clone,
|
8
|
+
toFixed = fabric.util.toFixed;
|
9
|
+
|
10
|
+
if (fabric.Text) {
|
11
|
+
fabric.warn('fabric.Text is already defined');
|
12
|
+
return;
|
13
|
+
}
|
14
|
+
|
15
|
+
var dimensionAffectingProps = {
|
16
|
+
fontSize: true,
|
17
|
+
fontWeight: true,
|
18
|
+
fontFamily: true,
|
19
|
+
textDecoration: true,
|
20
|
+
fontStyle: true,
|
21
|
+
lineHeight: true,
|
22
|
+
strokeStyle: true,
|
23
|
+
strokeWidth: true,
|
24
|
+
text: true
|
25
|
+
};
|
26
|
+
|
27
|
+
var stateProperties = fabric.Object.prototype.stateProperties.concat();
|
28
|
+
stateProperties.push(
|
29
|
+
'fontFamily',
|
30
|
+
'fontWeight',
|
31
|
+
'fontSize',
|
32
|
+
'path',
|
33
|
+
'text',
|
34
|
+
'textDecoration',
|
35
|
+
'textShadow',
|
36
|
+
'textAlign',
|
37
|
+
'fontStyle',
|
38
|
+
'lineHeight',
|
39
|
+
'strokeStyle',
|
40
|
+
'strokeWidth',
|
41
|
+
'backgroundColor',
|
42
|
+
'textBackgroundColor',
|
43
|
+
'useNative'
|
44
|
+
);
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Text class
|
48
|
+
* @class Text
|
49
|
+
* @extends fabric.Object
|
50
|
+
*/
|
51
|
+
fabric.Text = fabric.util.createClass(fabric.Object, /** @scope fabric.Text.prototype */ {
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Font size (in pixels)
|
55
|
+
* @property
|
56
|
+
* @type Number
|
57
|
+
*/
|
58
|
+
fontSize: 40,
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Font weight (e.g. bold, normal, 400, 600, 800)
|
62
|
+
* @property
|
63
|
+
* @type Number
|
64
|
+
*/
|
65
|
+
fontWeight: 'normal',
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Font family
|
69
|
+
* @property
|
70
|
+
* @type String
|
71
|
+
*/
|
72
|
+
fontFamily: 'Times New Roman',
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Text decoration (e.g. underline, overline)
|
76
|
+
* @property
|
77
|
+
* @type String
|
78
|
+
*/
|
79
|
+
textDecoration: '',
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Text shadow
|
83
|
+
* @property
|
84
|
+
* @type String | null
|
85
|
+
*/
|
86
|
+
textShadow: '',
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Text alignment. Possible values: "left", "center", or "right".
|
90
|
+
* @property
|
91
|
+
* @type String
|
92
|
+
*/
|
93
|
+
textAlign: 'left',
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Font style (e.g. italic)
|
97
|
+
* @property
|
98
|
+
* @type String
|
99
|
+
*/
|
100
|
+
fontStyle: '',
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Line height
|
104
|
+
* @property
|
105
|
+
* @type Number
|
106
|
+
*/
|
107
|
+
lineHeight: 1.3,
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Stroke style. When specified, text is rendered with stroke
|
111
|
+
* @property
|
112
|
+
* @type String
|
113
|
+
*/
|
114
|
+
strokeStyle: '',
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Stroke width
|
118
|
+
* @property
|
119
|
+
* @type Number
|
120
|
+
*/
|
121
|
+
strokeWidth: 1,
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Background color of an entire text box
|
125
|
+
* @property
|
126
|
+
* @type String
|
127
|
+
*/
|
128
|
+
backgroundColor: '',
|
129
|
+
|
130
|
+
/**
|
131
|
+
* Background color of text lines
|
132
|
+
* @property
|
133
|
+
* @type String
|
134
|
+
*/
|
135
|
+
textBackgroundColor: '',
|
136
|
+
|
137
|
+
/**
|
138
|
+
* URL of a font file, when using Cufon
|
139
|
+
* @property
|
140
|
+
* @type String | null
|
141
|
+
*/
|
142
|
+
path: null,
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Type of an object
|
146
|
+
* @property
|
147
|
+
* @type String
|
148
|
+
*/
|
149
|
+
type: 'text',
|
150
|
+
|
151
|
+
/**
|
152
|
+
* Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used)
|
153
|
+
* @property
|
154
|
+
* @type Boolean
|
155
|
+
*/
|
156
|
+
useNative: true,
|
157
|
+
|
158
|
+
/**
|
159
|
+
* List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged)
|
160
|
+
* as well as for history (undo/redo) purposes
|
161
|
+
* @property
|
162
|
+
* @type Array
|
163
|
+
*/
|
164
|
+
stateProperties: stateProperties,
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Constructor
|
168
|
+
* @method initialize
|
169
|
+
* @param {String} text
|
170
|
+
* @param {Object} [options]
|
171
|
+
* @return {fabric.Text} thisArg
|
172
|
+
*/
|
173
|
+
initialize: function(text, options) {
|
174
|
+
options = options || { };
|
175
|
+
|
176
|
+
this.text = text;
|
177
|
+
this.setOptions(options);
|
178
|
+
this._initDimensions();
|
179
|
+
this.setCoords();
|
180
|
+
},
|
181
|
+
|
182
|
+
/**
|
183
|
+
* Renders text object on offscreen canvas, so that it would get dimensions
|
184
|
+
* @private
|
185
|
+
* @method _initDimensions
|
186
|
+
*/
|
187
|
+
_initDimensions: function() {
|
188
|
+
var canvasEl = fabric.util.createCanvasElement();
|
189
|
+
this._render(canvasEl.getContext('2d'));
|
190
|
+
},
|
191
|
+
|
192
|
+
/**
|
193
|
+
* Returns string representation of an instance
|
194
|
+
* @method toString
|
195
|
+
* @return {String} String representation of text object
|
196
|
+
*/
|
197
|
+
toString: function() {
|
198
|
+
return '#<fabric.Text (' + this.complexity() +
|
199
|
+
'): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';
|
200
|
+
},
|
201
|
+
|
202
|
+
/**
|
203
|
+
* @private
|
204
|
+
* @method _render
|
205
|
+
* @param {CanvasRenderingContext2D} ctx Context to render on
|
206
|
+
*/
|
207
|
+
_render: function(ctx) {
|
208
|
+
if (typeof Cufon === 'undefined' || this.useNative === true) {
|
209
|
+
this._renderViaNative(ctx);
|
210
|
+
}
|
211
|
+
else {
|
212
|
+
this._renderViaCufon(ctx);
|
213
|
+
}
|
214
|
+
},
|
215
|
+
|
216
|
+
/**
|
217
|
+
* @private
|
218
|
+
* @method _renderViaCufon
|
219
|
+
*/
|
220
|
+
_renderViaCufon: function(ctx) {
|
221
|
+
var o = Cufon.textOptions || (Cufon.textOptions = { });
|
222
|
+
|
223
|
+
// export options to be used by cufon.js
|
224
|
+
o.left = this.left;
|
225
|
+
o.top = this.top;
|
226
|
+
o.context = ctx;
|
227
|
+
o.color = this.fill;
|
228
|
+
|
229
|
+
var el = this._initDummyElementForCufon();
|
230
|
+
|
231
|
+
// set "cursor" to top/left corner
|
232
|
+
this.transform(ctx);
|
233
|
+
|
234
|
+
// draw text
|
235
|
+
Cufon.replaceElement(el, {
|
236
|
+
engine: 'canvas',
|
237
|
+
separate: 'none',
|
238
|
+
fontFamily: this.fontFamily,
|
239
|
+
fontWeight: this.fontWeight,
|
240
|
+
textDecoration: this.textDecoration,
|
241
|
+
textShadow: this.textShadow,
|
242
|
+
textAlign: this.textAlign,
|
243
|
+
fontStyle: this.fontStyle,
|
244
|
+
lineHeight: this.lineHeight,
|
245
|
+
strokeStyle: this.strokeStyle,
|
246
|
+
strokeWidth: this.strokeWidth,
|
247
|
+
backgroundColor: this.backgroundColor,
|
248
|
+
textBackgroundColor: this.textBackgroundColor
|
249
|
+
});
|
250
|
+
|
251
|
+
// update width, height
|
252
|
+
this.width = o.width;
|
253
|
+
this.height = o.height;
|
254
|
+
|
255
|
+
this._totalLineHeight = o.totalLineHeight;
|
256
|
+
this._fontAscent = o.fontAscent;
|
257
|
+
this._boundaries = o.boundaries;
|
258
|
+
this._shadowOffsets = o.shadowOffsets;
|
259
|
+
this._shadows = o.shadows || [ ];
|
260
|
+
|
261
|
+
el = null;
|
262
|
+
|
263
|
+
// need to set coords _after_ the width/height was retreived from Cufon
|
264
|
+
this.setCoords();
|
265
|
+
},
|
266
|
+
|
267
|
+
/**
|
268
|
+
* @private
|
269
|
+
* @method _render_native
|
270
|
+
* @param {CanvasRenderingContext2D} ctx Context to render on
|
271
|
+
*/
|
272
|
+
_renderViaNative: function(ctx) {
|
273
|
+
|
274
|
+
this.transform(ctx);
|
275
|
+
this._setTextStyles(ctx);
|
276
|
+
|
277
|
+
var textLines = this.text.split(/\r?\n/);
|
278
|
+
|
279
|
+
this.width = this._getTextWidth(ctx, textLines);
|
280
|
+
this.height = this._getTextHeight(ctx, textLines);
|
281
|
+
|
282
|
+
this._renderTextBackground(ctx, textLines);
|
283
|
+
|
284
|
+
if (this.textAlign !== 'left' && this.textAlign !== 'justify') {
|
285
|
+
ctx.save();
|
286
|
+
ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0);
|
287
|
+
}
|
288
|
+
|
289
|
+
this._setTextShadow(ctx);
|
290
|
+
this._renderTextFill(ctx, textLines);
|
291
|
+
this.textShadow && ctx.restore();
|
292
|
+
|
293
|
+
this._renderTextStroke(ctx, textLines);
|
294
|
+
if (this.textAlign !== 'left' && this.textAlign !== 'justify') {
|
295
|
+
ctx.restore();
|
296
|
+
}
|
297
|
+
|
298
|
+
this._renderTextDecoration(ctx, textLines);
|
299
|
+
this._setBoundaries(ctx, textLines);
|
300
|
+
this._totalLineHeight = 0;
|
301
|
+
|
302
|
+
this.setCoords();
|
303
|
+
},
|
304
|
+
|
305
|
+
/**
|
306
|
+
* @private
|
307
|
+
* @method _setBoundaries
|
308
|
+
*/
|
309
|
+
_setBoundaries: function(ctx, textLines) {
|
310
|
+
this._boundaries = [ ];
|
311
|
+
|
312
|
+
for (var i = 0, len = textLines.length; i < len; i++) {
|
313
|
+
|
314
|
+
var lineWidth = this._getLineWidth(ctx, textLines[i]);
|
315
|
+
var lineLeftOffset = this._getLineLeftOffset(lineWidth);
|
316
|
+
|
317
|
+
this._boundaries.push({
|
318
|
+
height: this.fontSize * this.lineHeight,
|
319
|
+
width: lineWidth,
|
320
|
+
left: lineLeftOffset
|
321
|
+
});
|
322
|
+
}
|
323
|
+
},
|
324
|
+
|
325
|
+
/**
|
326
|
+
* @private
|
327
|
+
* @method _setTextStyles
|
328
|
+
*/
|
329
|
+
_setTextStyles: function(ctx) {
|
330
|
+
ctx.fillStyle = this.fill.toLive
|
331
|
+
? this.fill.toLive(ctx)
|
332
|
+
: this.fill;
|
333
|
+
ctx.strokeStyle = this.strokeStyle;
|
334
|
+
ctx.lineWidth = this.strokeWidth;
|
335
|
+
ctx.textBaseline = 'alphabetic';
|
336
|
+
ctx.textAlign = this.textAlign;
|
337
|
+
ctx.font = this._getFontDeclaration();
|
338
|
+
},
|
339
|
+
|
340
|
+
/**
|
341
|
+
* @private
|
342
|
+
* @method _getTextHeight
|
343
|
+
*/
|
344
|
+
_getTextHeight: function(ctx, textLines) {
|
345
|
+
return this.fontSize * textLines.length * this.lineHeight;
|
346
|
+
},
|
347
|
+
|
348
|
+
/**
|
349
|
+
* @private
|
350
|
+
* @method _getTextWidth
|
351
|
+
*/
|
352
|
+
_getTextWidth: function(ctx, textLines) {
|
353
|
+
var maxWidth = ctx.measureText(textLines[0]).width;
|
354
|
+
|
355
|
+
for (var i = 1, len = textLines.length; i < len; i++) {
|
356
|
+
var currentLineWidth = ctx.measureText(textLines[i]).width;
|
357
|
+
if (currentLineWidth > maxWidth) {
|
358
|
+
maxWidth = currentLineWidth;
|
359
|
+
}
|
360
|
+
}
|
361
|
+
return maxWidth;
|
362
|
+
},
|
363
|
+
|
364
|
+
/**
|
365
|
+
* @private
|
366
|
+
* @method _setTextShadow
|
367
|
+
*/
|
368
|
+
_setTextShadow: function(ctx) {
|
369
|
+
if (this.textShadow) {
|
370
|
+
|
371
|
+
// "rgba(0,0,0,0.2) 2px 2px 10px"
|
372
|
+
// "rgb(0, 100, 0) 0 0 5px"
|
373
|
+
// "red 2px 2px 1px"
|
374
|
+
// "#f55 123 345 567"
|
375
|
+
var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/;
|
376
|
+
|
377
|
+
var shadowDeclaration = this.textShadow;
|
378
|
+
var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow);
|
379
|
+
var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, '');
|
380
|
+
|
381
|
+
ctx.save();
|
382
|
+
ctx.shadowColor = shadowColor;
|
383
|
+
ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10);
|
384
|
+
ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10);
|
385
|
+
ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10);
|
386
|
+
|
387
|
+
this._shadows = [{
|
388
|
+
blur: ctx.shadowBlur,
|
389
|
+
color: ctx.shadowColor,
|
390
|
+
offX: ctx.shadowOffsetX,
|
391
|
+
offY: ctx.shadowOffsetY
|
392
|
+
}];
|
393
|
+
|
394
|
+
this._shadowOffsets = [[
|
395
|
+
parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10)
|
396
|
+
]];
|
397
|
+
}
|
398
|
+
},
|
399
|
+
|
400
|
+
/**
|
401
|
+
* @private
|
402
|
+
* @method _drawTextLine
|
403
|
+
* @param method
|
404
|
+
* @param ctx
|
405
|
+
* @param line
|
406
|
+
* @param left
|
407
|
+
* param top
|
408
|
+
*/
|
409
|
+
_drawTextLine: function(method, ctx, line, left, top) {
|
410
|
+
|
411
|
+
// short-circuit
|
412
|
+
if (this.textAlign !== 'justify') {
|
413
|
+
ctx[method](line, left, top);
|
414
|
+
return;
|
415
|
+
}
|
416
|
+
|
417
|
+
var lineWidth = ctx.measureText(line).width;
|
418
|
+
var totalWidth = this.width;
|
419
|
+
|
420
|
+
if (totalWidth > lineWidth) {
|
421
|
+
// stretch the line
|
422
|
+
|
423
|
+
var words = line.split(/\s+/);
|
424
|
+
var wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width;
|
425
|
+
var widthDiff = totalWidth - wordsWidth;
|
426
|
+
var numSpaces = words.length - 1;
|
427
|
+
var spaceWidth = widthDiff / numSpaces;
|
428
|
+
|
429
|
+
var leftOffset = 0;
|
430
|
+
for (var i = 0, len = words.length; i < len; i++) {
|
431
|
+
ctx[method](words[i], left + leftOffset, top);
|
432
|
+
leftOffset += ctx.measureText(words[i]).width + spaceWidth;
|
433
|
+
}
|
434
|
+
}
|
435
|
+
else {
|
436
|
+
ctx[method](line, left, top);
|
437
|
+
}
|
438
|
+
},
|
439
|
+
|
440
|
+
/**
|
441
|
+
* @private
|
442
|
+
* @method _renderTextFill
|
443
|
+
*/
|
444
|
+
_renderTextFill: function(ctx, textLines) {
|
445
|
+
this._boundaries = [ ];
|
446
|
+
for (var i = 0, len = textLines.length; i < len; i++) {
|
447
|
+
this._drawTextLine(
|
448
|
+
'fillText',
|
449
|
+
ctx,
|
450
|
+
textLines[i],
|
451
|
+
-this.width / 2,
|
452
|
+
(-this.height / 2) + (i * this.fontSize * this.lineHeight) + this.fontSize
|
453
|
+
);
|
454
|
+
}
|
455
|
+
},
|
456
|
+
|
457
|
+
/**
|
458
|
+
* @private
|
459
|
+
* @method _renderTextStroke
|
460
|
+
*/
|
461
|
+
_renderTextStroke: function(ctx, textLines) {
|
462
|
+
if (this.strokeStyle) {
|
463
|
+
ctx.beginPath();
|
464
|
+
for (var i = 0, len = textLines.length; i < len; i++) {
|
465
|
+
this._drawTextLine(
|
466
|
+
'strokeText',
|
467
|
+
ctx,
|
468
|
+
textLines[i],
|
469
|
+
-this.width / 2,
|
470
|
+
(-this.height / 2) + (i * this.fontSize * this.lineHeight) + this.fontSize
|
471
|
+
);
|
472
|
+
}
|
473
|
+
ctx.closePath();
|
474
|
+
}
|
475
|
+
},
|
476
|
+
|
477
|
+
/**
|
478
|
+
* @private
|
479
|
+
* @method _renderTextBackground
|
480
|
+
*/
|
481
|
+
_renderTextBackground: function(ctx, textLines) {
|
482
|
+
this._renderTextBoxBackground(ctx);
|
483
|
+
this._renderTextLinesBackground(ctx, textLines);
|
484
|
+
},
|
485
|
+
|
486
|
+
/**
|
487
|
+
* @private
|
488
|
+
* @method _renderTextBoxBackground
|
489
|
+
*/
|
490
|
+
_renderTextBoxBackground: function(ctx) {
|
491
|
+
if (this.backgroundColor) {
|
492
|
+
ctx.save();
|
493
|
+
ctx.fillStyle = this.backgroundColor;
|
494
|
+
|
495
|
+
ctx.fillRect(
|
496
|
+
(-this.width / 2),
|
497
|
+
(-this.height / 2),
|
498
|
+
this.width,
|
499
|
+
this.height
|
500
|
+
);
|
501
|
+
|
502
|
+
ctx.restore();
|
503
|
+
}
|
504
|
+
},
|
505
|
+
|
506
|
+
/**
|
507
|
+
* @private
|
508
|
+
* @method _renderTextLinesBackground
|
509
|
+
*/
|
510
|
+
_renderTextLinesBackground: function(ctx, textLines) {
|
511
|
+
if (this.textBackgroundColor) {
|
512
|
+
ctx.save();
|
513
|
+
ctx.fillStyle = this.textBackgroundColor;
|
514
|
+
|
515
|
+
for (var i = 0, len = textLines.length; i < len; i++) {
|
516
|
+
|
517
|
+
if (textLines[i] !== '') {
|
518
|
+
|
519
|
+
var lineWidth = this._getLineWidth(ctx, textLines[i]);
|
520
|
+
var lineLeftOffset = this._getLineLeftOffset(lineWidth);
|
521
|
+
|
522
|
+
ctx.fillRect(
|
523
|
+
(-this.width / 2) + lineLeftOffset,
|
524
|
+
(-this.height / 2) + (i * this.fontSize * this.lineHeight),
|
525
|
+
lineWidth,
|
526
|
+
this.fontSize * this.lineHeight
|
527
|
+
);
|
528
|
+
}
|
529
|
+
}
|
530
|
+
ctx.restore();
|
531
|
+
}
|
532
|
+
},
|
533
|
+
|
534
|
+
/**
|
535
|
+
* @private
|
536
|
+
* @method _getLineLeftOffset
|
537
|
+
*/
|
538
|
+
_getLineLeftOffset: function(lineWidth) {
|
539
|
+
if (this.textAlign === 'center') {
|
540
|
+
return (this.width - lineWidth) / 2;
|
541
|
+
}
|
542
|
+
if (this.textAlign === 'right') {
|
543
|
+
return this.width - lineWidth;
|
544
|
+
}
|
545
|
+
return 0;
|
546
|
+
},
|
547
|
+
|
548
|
+
/**
|
549
|
+
* @private
|
550
|
+
* @method _getLineWidth
|
551
|
+
* @param ctx
|
552
|
+
* @param line
|
553
|
+
*/
|
554
|
+
_getLineWidth: function(ctx, line) {
|
555
|
+
return this.textAlign === 'justify'
|
556
|
+
? this.width
|
557
|
+
: ctx.measureText(line).width;
|
558
|
+
},
|
559
|
+
|
560
|
+
/**
|
561
|
+
* @private
|
562
|
+
* @method _renderTextDecoration
|
563
|
+
*/
|
564
|
+
_renderTextDecoration: function(ctx, textLines) {
|
565
|
+
|
566
|
+
var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2;
|
567
|
+
var _this = this;
|
568
|
+
|
569
|
+
/** @ignore */
|
570
|
+
function renderLinesAtOffset(offset) {
|
571
|
+
for (var i = 0, len = textLines.length; i < len; i++) {
|
572
|
+
|
573
|
+
var lineWidth = _this._getLineWidth(ctx, textLines[i]);
|
574
|
+
var lineLeftOffset = _this._getLineLeftOffset(lineWidth);
|
575
|
+
|
576
|
+
ctx.fillRect(
|
577
|
+
(-_this.width / 2) + lineLeftOffset,
|
578
|
+
(offset + (i * _this.fontSize * _this.lineHeight)) - halfOfVerticalBox,
|
579
|
+
lineWidth,
|
580
|
+
1);
|
581
|
+
}
|
582
|
+
}
|
583
|
+
|
584
|
+
if (this.textDecoration.indexOf('underline') > -1) {
|
585
|
+
renderLinesAtOffset(this.fontSize);
|
586
|
+
}
|
587
|
+
if (this.textDecoration.indexOf('line-through') > -1) {
|
588
|
+
renderLinesAtOffset(this.fontSize / 2);
|
589
|
+
}
|
590
|
+
if (this.textDecoration.indexOf('overline') > -1) {
|
591
|
+
renderLinesAtOffset(0);
|
592
|
+
}
|
593
|
+
},
|
594
|
+
|
595
|
+
/**
|
596
|
+
* @private
|
597
|
+
* @method _getFontDeclaration
|
598
|
+
*/
|
599
|
+
_getFontDeclaration: function() {
|
600
|
+
return [
|
601
|
+
// node-canvas needs "weight style", while browsers need "style weight"
|
602
|
+
(fabric.isLikelyNode ? this.fontWeight : this.fontStyle),
|
603
|
+
(fabric.isLikelyNode ? this.fontStyle : this.fontWeight),
|
604
|
+
this.fontSize + 'px',
|
605
|
+
(fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)
|
606
|
+
].join(' ');
|
607
|
+
},
|
608
|
+
|
609
|
+
/**
|
610
|
+
* @private
|
611
|
+
* @method _initDummyElement
|
612
|
+
*/
|
613
|
+
_initDummyElementForCufon: function() {
|
614
|
+
var el = fabric.document.createElement('pre'),
|
615
|
+
container = fabric.document.createElement('div');
|
616
|
+
|
617
|
+
// Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent
|
618
|
+
container.appendChild(el);
|
619
|
+
|
620
|
+
if (typeof G_vmlCanvasManager === 'undefined') {
|
621
|
+
el.innerHTML = this.text;
|
622
|
+
}
|
623
|
+
else {
|
624
|
+
// IE 7 & 8 drop newlines and white space on text nodes
|
625
|
+
// see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
|
626
|
+
// see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp
|
627
|
+
el.innerText = this.text.replace(/\r?\n/gi, '\r');
|
628
|
+
}
|
629
|
+
|
630
|
+
el.style.fontSize = this.fontSize + 'px';
|
631
|
+
el.style.letterSpacing = 'normal';
|
632
|
+
|
633
|
+
return el;
|
634
|
+
},
|
635
|
+
|
636
|
+
/**
|
637
|
+
* Renders text instance on a specified context
|
638
|
+
* @method render
|
639
|
+
* @param ctx {CanvasRenderingContext2D} context to render on
|
640
|
+
*/
|
641
|
+
render: function(ctx, noTransform) {
|
642
|
+
ctx.save();
|
643
|
+
this._render(ctx);
|
644
|
+
if (!noTransform && this.active) {
|
645
|
+
this.drawBorders(ctx);
|
646
|
+
this.drawControls(ctx);
|
647
|
+
}
|
648
|
+
ctx.restore();
|
649
|
+
},
|
650
|
+
|
651
|
+
/**
|
652
|
+
* Returns object representation of an instance
|
653
|
+
* @method toObject
|
654
|
+
* @param {Array} propertiesToInclude
|
655
|
+
* @return {Object} object representation of an instance
|
656
|
+
*/
|
657
|
+
toObject: function(propertiesToInclude) {
|
658
|
+
return extend(this.callSuper('toObject', propertiesToInclude), {
|
659
|
+
text: this.text,
|
660
|
+
fontSize: this.fontSize,
|
661
|
+
fontWeight: this.fontWeight,
|
662
|
+
fontFamily: this.fontFamily,
|
663
|
+
fontStyle: this.fontStyle,
|
664
|
+
lineHeight: this.lineHeight,
|
665
|
+
textDecoration: this.textDecoration,
|
666
|
+
textShadow: this.textShadow,
|
667
|
+
textAlign: this.textAlign,
|
668
|
+
path: this.path,
|
669
|
+
strokeStyle: this.strokeStyle,
|
670
|
+
strokeWidth: this.strokeWidth,
|
671
|
+
backgroundColor: this.backgroundColor,
|
672
|
+
textBackgroundColor: this.textBackgroundColor,
|
673
|
+
useNative: this.useNative
|
674
|
+
});
|
675
|
+
},
|
676
|
+
|
677
|
+
/**
|
678
|
+
* Returns SVG representation of an instance
|
679
|
+
* @method toSVG
|
680
|
+
* @return {String} svg representation of an instance
|
681
|
+
*/
|
682
|
+
toSVG: function() {
|
683
|
+
|
684
|
+
var textLines = this.text.split(/\r?\n/),
|
685
|
+
lineTopOffset = this.useNative
|
686
|
+
? this.fontSize * this.lineHeight
|
687
|
+
: (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)),
|
688
|
+
|
689
|
+
textLeftOffset = -(this.width/2),
|
690
|
+
textTopOffset = this.useNative
|
691
|
+
? this.fontSize - 1
|
692
|
+
: (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight,
|
693
|
+
|
694
|
+
textAndBg = this._getSVGTextAndBg(lineTopOffset, textLeftOffset, textLines),
|
695
|
+
shadowSpans = this._getSVGShadows(lineTopOffset, textLines);
|
696
|
+
|
697
|
+
// move top offset by an ascent
|
698
|
+
textTopOffset += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0);
|
699
|
+
|
700
|
+
return [
|
701
|
+
'<g transform="', this.getSvgTransform(), '">',
|
702
|
+
textAndBg.textBgRects.join(''),
|
703
|
+
'<text ',
|
704
|
+
(this.fontFamily ? 'font-family="\'' + this.fontFamily + '\'" ': ''),
|
705
|
+
(this.fontSize ? 'font-size="' + this.fontSize + '" ': ''),
|
706
|
+
(this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''),
|
707
|
+
(this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''),
|
708
|
+
(this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''),
|
709
|
+
'style="', this.getSvgStyles(), '" ',
|
710
|
+
/* svg starts from left/bottom corner so we normalize height */
|
711
|
+
'transform="translate(', toFixed(textLeftOffset, 2), ' ', toFixed(textTopOffset, 2), ')">',
|
712
|
+
shadowSpans.join(''),
|
713
|
+
textAndBg.textSpans.join(''),
|
714
|
+
'</text>',
|
715
|
+
'</g>'
|
716
|
+
].join('');
|
717
|
+
},
|
718
|
+
|
719
|
+
/**
|
720
|
+
* @private
|
721
|
+
* @method _getSVGShadows
|
722
|
+
*/
|
723
|
+
_getSVGShadows: function(lineTopOffset, textLines) {
|
724
|
+
var shadowSpans = [], j, i, jlen, ilen, lineTopOffsetMultiplier = 1;
|
725
|
+
|
726
|
+
if (!this._shadows || !this._boundaries) {
|
727
|
+
return shadowSpans;
|
728
|
+
}
|
729
|
+
|
730
|
+
for (j = 0, jlen = this._shadows.length; j < jlen; j++) {
|
731
|
+
for (i = 0, ilen = textLines.length; i < ilen; i++) {
|
732
|
+
if (textLines[i] !== '') {
|
733
|
+
var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0;
|
734
|
+
shadowSpans.push(
|
735
|
+
'<tspan x="',
|
736
|
+
toFixed((lineLeftOffset + lineTopOffsetMultiplier) + this._shadowOffsets[j][0], 2),
|
737
|
+
((i === 0 || this.useNative) ? '" y' : '" dy'), '="',
|
738
|
+
toFixed(this.useNative
|
739
|
+
? ((lineTopOffset * i) - this.height / 2 + this._shadowOffsets[j][1])
|
740
|
+
: (lineTopOffset + (i === 0 ? this._shadowOffsets[j][1] : 0)), 2),
|
741
|
+
'" ',
|
742
|
+
this._getFillAttributes(this._shadows[j].color), '>',
|
743
|
+
fabric.util.string.escapeXml(textLines[i]),
|
744
|
+
'</tspan>');
|
745
|
+
lineTopOffsetMultiplier = 1;
|
746
|
+
} else {
|
747
|
+
// in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
|
748
|
+
// prevents empty tspans
|
749
|
+
lineTopOffsetMultiplier++;
|
750
|
+
}
|
751
|
+
}
|
752
|
+
}
|
753
|
+
return shadowSpans;
|
754
|
+
},
|
755
|
+
|
756
|
+
/**
|
757
|
+
* @private
|
758
|
+
* @method _getSVGTextAndBg
|
759
|
+
*/
|
760
|
+
_getSVGTextAndBg: function(lineTopOffset, textLeftOffset, textLines) {
|
761
|
+
var textSpans = [ ], textBgRects = [ ], i, lineLeftOffset, len, lineTopOffsetMultiplier = 1;
|
762
|
+
|
763
|
+
// bounding-box background
|
764
|
+
if (this.backgroundColor && this._boundaries) {
|
765
|
+
textBgRects.push(
|
766
|
+
'<rect ',
|
767
|
+
this._getFillAttributes(this.backgroundColor),
|
768
|
+
' x="',
|
769
|
+
toFixed(-this.width / 2, 2),
|
770
|
+
'" y="',
|
771
|
+
toFixed(-this.height / 2, 2),
|
772
|
+
'" width="',
|
773
|
+
toFixed(this.width, 2),
|
774
|
+
'" height="',
|
775
|
+
toFixed(this.height, 2),
|
776
|
+
'"></rect>');
|
777
|
+
}
|
778
|
+
|
779
|
+
// text and text-background
|
780
|
+
for (i = 0, len = textLines.length; i < len; i++) {
|
781
|
+
if (textLines[i] !== '') {
|
782
|
+
lineLeftOffset = (this._boundaries && this._boundaries[i]) ? toFixed(this._boundaries[i].left, 2) : 0;
|
783
|
+
textSpans.push(
|
784
|
+
'<tspan x="',
|
785
|
+
lineLeftOffset, '" ',
|
786
|
+
(i === 0 || this.useNative ? 'y' : 'dy'), '="',
|
787
|
+
toFixed(this.useNative ? ((lineTopOffset * i) - this.height / 2) : (lineTopOffset * lineTopOffsetMultiplier), 2) , '" ',
|
788
|
+
// doing this on <tspan> elements since setting opacity on containing <text> one doesn't work in Illustrator
|
789
|
+
this._getFillAttributes(this.fill), '>',
|
790
|
+
fabric.util.string.escapeXml(textLines[i]),
|
791
|
+
'</tspan>'
|
792
|
+
);
|
793
|
+
lineTopOffsetMultiplier = 1;
|
794
|
+
}
|
795
|
+
else {
|
796
|
+
// in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
|
797
|
+
// prevents empty tspans
|
798
|
+
lineTopOffsetMultiplier++;
|
799
|
+
}
|
800
|
+
|
801
|
+
if (!this.textBackgroundColor || !this._boundaries) continue;
|
802
|
+
|
803
|
+
textBgRects.push(
|
804
|
+
'<rect ',
|
805
|
+
this._getFillAttributes(this.textBackgroundColor),
|
806
|
+
' x="',
|
807
|
+
toFixed(textLeftOffset + this._boundaries[i].left, 2),
|
808
|
+
'" y="',
|
809
|
+
/* an offset that seems to straighten things out */
|
810
|
+
toFixed((lineTopOffset * i) - this.height / 2, 2),
|
811
|
+
'" width="',
|
812
|
+
toFixed(this._boundaries[i].width, 2),
|
813
|
+
'" height="',
|
814
|
+
toFixed(this._boundaries[i].height, 2),
|
815
|
+
'"></rect>');
|
816
|
+
}
|
817
|
+
return {
|
818
|
+
textSpans: textSpans,
|
819
|
+
textBgRects: textBgRects
|
820
|
+
};
|
821
|
+
},
|
822
|
+
|
823
|
+
/**
|
824
|
+
* Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
|
825
|
+
* we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
|
826
|
+
*
|
827
|
+
* @private
|
828
|
+
* @method _getFillAttributes
|
829
|
+
*/
|
830
|
+
_getFillAttributes: function(value) {
|
831
|
+
var fillColor = value ? new fabric.Color(value) : '';
|
832
|
+
if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {
|
833
|
+
return 'fill="' + value + '"';
|
834
|
+
}
|
835
|
+
return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';
|
836
|
+
},
|
837
|
+
|
838
|
+
/**
|
839
|
+
* Sets "color" of an instance (alias of `set('fill', …)`)
|
840
|
+
* @method setColor
|
841
|
+
* @param {String} value
|
842
|
+
* @return {fabric.Text} thisArg
|
843
|
+
* @chainable
|
844
|
+
*/
|
845
|
+
setColor: function(value) {
|
846
|
+
this.set('fill', value);
|
847
|
+
return this;
|
848
|
+
},
|
849
|
+
|
850
|
+
/**
|
851
|
+
* Returns actual text value of an instance
|
852
|
+
* @method getText
|
853
|
+
* @return {String}
|
854
|
+
*/
|
855
|
+
getText: function() {
|
856
|
+
return this.text;
|
857
|
+
},
|
858
|
+
|
859
|
+
/**
|
860
|
+
* Sets specified property to a specified value
|
861
|
+
* @method set
|
862
|
+
* @param {String} name
|
863
|
+
* @param {Any} value
|
864
|
+
* @return {fabric.Text} thisArg
|
865
|
+
* @chainable
|
866
|
+
*/
|
867
|
+
_set: function(name, value) {
|
868
|
+
if (name === 'fontFamily' && this.path) {
|
869
|
+
this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3');
|
870
|
+
}
|
871
|
+
this.callSuper('_set', name, value);
|
872
|
+
|
873
|
+
if (name in dimensionAffectingProps) {
|
874
|
+
this._initDimensions();
|
875
|
+
this.setCoords();
|
876
|
+
}
|
877
|
+
}
|
878
|
+
});
|
879
|
+
|
880
|
+
/**
|
881
|
+
* List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement})
|
882
|
+
* @static
|
883
|
+
*/
|
884
|
+
fabric.Text.ATTRIBUTE_NAMES =
|
885
|
+
('x y fill fill-opacity opacity stroke stroke-width transform ' +
|
886
|
+
'font-family font-style font-weight font-size text-decoration').split(' ');
|
887
|
+
|
888
|
+
/**
|
889
|
+
* Returns fabric.Text instance from an object representation
|
890
|
+
* @static
|
891
|
+
* @method fromObject
|
892
|
+
* @param {Object} object to create an instance from
|
893
|
+
* @return {fabric.Text} an instance
|
894
|
+
*/
|
895
|
+
fabric.Text.fromObject = function(object) {
|
896
|
+
return new fabric.Text(object.text, clone(object));
|
897
|
+
};
|
898
|
+
|
899
|
+
/**
|
900
|
+
* Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
|
901
|
+
* @static
|
902
|
+
* @method fabric.Text.fromElement
|
903
|
+
* @param element
|
904
|
+
* @param options
|
905
|
+
* @return {fabric.Text} an instance
|
906
|
+
*/
|
907
|
+
fabric.Text.fromElement = function(element, options) {
|
908
|
+
|
909
|
+
if (!element) {
|
910
|
+
return null;
|
911
|
+
}
|
912
|
+
|
913
|
+
var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
|
914
|
+
options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);
|
915
|
+
|
916
|
+
var text = new fabric.Text(element.textContent, options);
|
917
|
+
|
918
|
+
/*
|
919
|
+
Adjust positioning:
|
920
|
+
x/y attributes in SVG correspond to the bottom-left corner of text bounding box
|
921
|
+
top/left properties in Fabric correspond to center point of text bounding box
|
922
|
+
*/
|
923
|
+
|
924
|
+
text.set({
|
925
|
+
left: text.getLeft() + text.getWidth() / 2,
|
926
|
+
top: text.getTop() - text.getHeight() / 2
|
927
|
+
});
|
928
|
+
|
929
|
+
return text;
|
930
|
+
};
|
931
|
+
|
932
|
+
fabric.util.createAccessors(fabric.Text);
|
933
|
+
|
934
|
+
})(typeof exports !== 'undefined' ? exports : this);
|