diff --git a/area.js b/area.js
index e800b95..c33de80 100644
--- a/area.js
+++ b/area.js
@@ -471,7 +471,7 @@ var DrawingArea = new Lang.Class({
if (duplicate) {
// deep cloning
- let copy = new Elements.DrawingElement(JSON.parse(JSON.stringify(this.grabbedElement)));
+ let copy = new this.grabbedElement.constructor(JSON.parse(JSON.stringify(this.grabbedElement)));
this.elements.push(copy);
this.grabbedElement = copy;
}
@@ -554,28 +554,32 @@ var DrawingArea = new Lang.Class({
this._stopDrawing();
});
- this.currentElement = new Elements.DrawingElement ({
- shape: this.currentTool,
- color: this.currentColor.to_string(),
- line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap },
- dash: { active: this.dashedLine, array: this.dashedLine ? [this.dashArray[0] || this.currentLineWidth, this.dashArray[1] || this.currentLineWidth * 3] : [0, 0] , offset: this.dashOffset },
- fill: this.fill,
- fillRule: this.currentFillRule,
- eraser: eraser,
- transform: { active: false, center: [0, 0], angle: 0, startAngle: 0, ratio: 1 },
- points: []
- });
-
if (this.currentTool == Shapes.TEXT) {
- this.currentElement.fill = false;
- this.currentElement.font = {
- family: (this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]),
- weight: this.currentFontWeight,
- style: this.currentFontStyle,
- stretch: this.currentFontStretch,
- variant: this.currentFontVariant };
- this.currentElement.text = _("Text");
- this.currentElement.textRightAligned = this.currentTextRightAligned;
+ this.currentElement = new Elements.DrawingElement({
+ shape: this.currentTool,
+ color: this.currentColor.to_string(),
+ eraser: eraser,
+ font: {
+ family: (this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]),
+ weight: this.currentFontWeight,
+ style: this.currentFontStyle,
+ stretch: this.currentFontStretch,
+ variant: this.currentFontVariant },
+ text: _("Text"),
+ textRightAligned: this.currentTextRightAligned,
+ points: []
+ });
+ } else {
+ this.currentElement = new Elements.DrawingElement({
+ shape: this.currentTool,
+ color: this.currentColor.to_string(),
+ eraser: eraser,
+ fill: this.fill,
+ fillRule: this.currentFillRule,
+ line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap },
+ dash: { active: this.dashedLine, array: this.dashedLine ? [this.dashArray[0] || this.currentLineWidth, this.dashArray[1] || this.currentLineWidth * 3] : [0, 0] , offset: this.dashOffset },
+ points: []
+ });
}
this.currentElement.startDrawing(startX, startY);
diff --git a/elements.js b/elements.js
index 9b4f7bc..6eb40ee 100644
--- a/elements.js
+++ b/elements.js
@@ -57,10 +57,14 @@ const MIN_TRANSLATION_DISTANCE = 1; // px
const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad
const MIN_DRAWING_SIZE = 3; // px
+var DrawingElement = function(params) {
+ return params.shape == Shapes.TEXT ? new TextElement(params) : new _DrawingElement(params);
+};
+
// DrawingElement represents a "brushstroke".
// It can be converted into a cairo path as well as a svg element.
// See DrawingArea._startDrawing() to know its params.
-var DrawingElement = new Lang.Class({
+const _DrawingElement = new Lang.Class({
Name: 'DrawOnYourScreenDrawingElement',
_init: function(params) {
@@ -69,16 +73,12 @@ var DrawingElement = new Lang.Class({
// compatibility with json generated by old extension versions
- if (params.fillRule === undefined)
- this.fillRule = Cairo.FillRule.WINDING;
if (params.transformations === undefined)
this.transformations = [];
- if (params.shape == Shapes.TEXT) {
- if (params.font && params.font.weight === 0)
- this.font.weight = 400;
- if (params.font && params.font.weight === 1)
- this.font.weight = 700;
- }
+ if (params.font && params.font.weight === 0)
+ this.font.weight = 400;
+ if (params.font && params.font.weight === 1)
+ this.font.weight = 700;
if (params.transform && params.transform.center) {
let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0);
@@ -104,10 +104,6 @@ var DrawingElement = new Lang.Class({
fillRule: this.fillRule,
eraser: this.eraser,
transformations: this.transformations,
- text: this.text,
- lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
- textRightAligned: this.textRightAligned,
- font: this.font,
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
};
},
@@ -129,9 +125,12 @@ var DrawingElement = new Lang.Class({
cr.stroke();
}
- cr.setLineCap(this.line.lineCap);
- cr.setLineJoin(this.line.lineJoin);
- cr.setLineWidth(this.line.lineWidth);
+ if (this.line) {
+ cr.setLineCap(this.line.lineCap);
+ cr.setLineJoin(this.line.lineJoin);
+ cr.setLineWidth(this.line.lineWidth);
+ }
+
if (this.fillRule)
cr.setFillRule(this.fillRule);
@@ -175,6 +174,12 @@ var DrawingElement = new Lang.Class({
}
});
+ this._drawCairo(cr, params);
+
+ cr.identityMatrix();
+ },
+
+ _drawCairo: function(cr, params) {
let [points, shape] = [this.points, this.shape];
if (shape == Shapes.LINE && points.length == 3) {
@@ -218,51 +223,10 @@ var DrawingElement = new Lang.Class({
if (shape == Shapes.POLYGON)
cr.closePath();
- } else if (shape == Shapes.TEXT && points.length == 2) {
- let layout = PangoCairo.create_layout(cr);
- let fontSize = Math.abs(points[1][1] - points[0][1]) * Pango.SCALE;
- let fontDescription = new Pango.FontDescription();
- fontDescription.set_absolute_size(fontSize);
- ['family', 'weight', 'style', 'stretch', 'variant'].forEach(attribute => {
- if (this.font[attribute] !== undefined)
- try {
- fontDescription[`set_${attribute}`](this.font[attribute]);
- } catch(e) {}
- });
- layout.set_font_description(fontDescription);
- layout.set_text(this.text, -1);
- this.textWidth = layout.get_pixel_size()[0];
- cr.moveTo(points[1][0] - (this.textRightAligned ? this.textWidth : 0), Math.max(points[0][1],points[1][1]) - layout.get_baseline() / Pango.SCALE);
- layout.set_text(this.text, -1);
- PangoCairo.show_layout(cr, layout);
-
- if (params.showTextCursor) {
- let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition;
- layout.set_text(this.text.slice(0, cursorPosition), -1);
- let width = layout.get_pixel_size()[0];
- cr.rectangle(points[1][0] - (this.textRightAligned ? this.textWidth : 0) + width, Math.max(points[0][1],points[1][1]),
- Math.abs(points[1][1] - points[0][1]) / 25, - Math.abs(points[1][1] - points[0][1]));
- cr.fill();
- }
-
- if (params.showTextRectangle || params.drawTextRectangle) {
- cr.rectangle(points[1][0] - (this.textRightAligned ? this.textWidth : 0), Math.max(points[0][1], points[1][1]),
- this.textWidth, - Math.abs(points[1][1] - points[0][1]));
- if (params.showTextRectangle)
- setDummyStroke(cr);
- else
- // Only draw the rectangle to find the element, not to show it.
- cr.setLineWidth(0);
- }
}
-
- cr.identityMatrix();
},
getContainsPoint: function(cr, x, y) {
- if (this.shape == Shapes.TEXT)
- return cr.inFill(x, y);
-
cr.save();
cr.setLineWidth(Math.max(this.line.lineWidth, 25));
cr.setDash([], 0);
@@ -274,33 +238,6 @@ var DrawingElement = new Lang.Class({
},
buildSVG: function(bgColor) {
- let row = "\n ";
- let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]);
- let color = this.eraser ? bgColor : this.color;
- let fill = this.fill && !this.isStraightLine;
- let attributes = '';
-
- if (fill) {
- attributes = `fill="${color}"`;
- if (this.fillRule)
- attributes += ` fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}"`;
- } else {
- attributes = `fill="none"`;
- }
-
- if (this.line && this.line.lineWidth) {
- attributes += ` stroke="${color}"` +
- ` stroke-width="${this.line.lineWidth}"`;
- if (this.line.lineCap)
- attributes += ` stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`;
- if (this.line.lineJoin && !this.isStraightLine)
- attributes += ` stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`;
- if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1])
- attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`;
- } else {
- attributes += ` stroke="none"`;
- }
-
let transAttribute = '';
this.transformations.slice(0).reverse().forEach(transformation => {
transAttribute += transAttribute ? ' ' : ' transform="';
@@ -328,6 +265,37 @@ var DrawingElement = new Lang.Class({
});
transAttribute += transAttribute ? '"' : '';
+ return this._drawSvg(transAttribute);
+ },
+
+ _drawSvg: function(transAttribute) {
+ let row = "\n ";
+ let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]);
+ let color = this.eraser ? bgColor : this.color;
+ let fill = this.fill && !this.isStraightLine;
+ let attributes = '';
+
+ if (fill) {
+ attributes = `fill="${color}"`;
+ if (this.fillRule)
+ attributes += ` fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}"`;
+ } else {
+ attributes = `fill="none"`;
+ }
+
+ if (this.line && this.line.lineWidth) {
+ attributes += ` stroke="${color}"` +
+ ` stroke-width="${this.line.lineWidth}"`;
+ if (this.line.lineCap)
+ attributes += ` stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`;
+ if (this.line.lineJoin && !this.isStraightLine)
+ attributes += ` stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`;
+ if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1])
+ attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`;
+ } else {
+ attributes += ` stroke="none"`;
+ }
+
if (this.shape == Shapes.LINE && points.length == 4) {
row += ``;
- } else if (this.shape == Shapes.TEXT && points.length == 2) {
- attributes = `fill="${color}" ` +
- `stroke="transparent" ` +
- `stroke-opacity="0" ` +
- `font-size="${Math.abs(points[1][1] - points[0][1])}"`;
-
- if (this.font.family)
- attributes += ` font-family="${this.font.family}"`;
- if (this.font.weight && this.font.weight != Pango.Weight.NORMAL)
- attributes += ` font-weight="${this.font.weight}"`;
- if (this.font.style && FontStyleNames[this.font.style])
- attributes += ` font-style="${FontStyleNames[this.font.style].toLowerCase()}"`;
- if (FontStretchNames[this.font.stretch] && this.font.stretch != Pango.Stretch.NORMAL)
- attributes += ` font-stretch="${FontStretchNames[this.font.stretch].toLowerCase()}"`;
- if (this.font.variant && FontVariantNames[this.font.variant])
- attributes += ` font-variant="${FontVariantNames[this.font.variant].toLowerCase()}"`;
-
- // this.textWidth is computed during Cairo building.
- row += `${this.text}`;
}
return row;
@@ -469,14 +417,6 @@ var DrawingElement = new Lang.Class({
} else if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) {
points[points.length - 1] = [x, y];
- } else if (this.shape == Shapes.TEXT && transform) {
- if (points.length < 2)
- return;
-
- let [slideX, slideY] = [x - points[1][0], y - points[1][1]];
- points[0] = [points[0][0] + slideX, points[0][1] + slideY];
- points[1] = [x, y];
-
} else {
points[1] = [x, y];
@@ -590,11 +530,6 @@ var DrawingElement = new Lang.Class({
}
},
- // When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line.
- _getLineOffset: function() {
- return (this.lineIndex || 0) * Math.abs(this.points[1][1] - this.points[0][1]);
- },
-
// The figure rotation center before transformations (original).
// this.textWidth is computed during Cairo building.
_getOriginalCenter: function() {
@@ -603,7 +538,6 @@ var DrawingElement = new Lang.Class({
this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] :
this.shape == Shapes.LINE && points.length == 4 ? getCurveCenter(points[0], points[1], points[2], points[3]) :
this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) :
- this.shape == Shapes.TEXT && this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this._getLineOffset()] :
points.length >= 3 ? getCentroid(points) :
getNaiveCenter(points);
}
@@ -648,6 +582,139 @@ var DrawingElement = new Lang.Class({
}
});
+const TextElement = new Lang.Class({
+ Name: 'DrawOnYourScreenTextElement',
+ Extends: _DrawingElement,
+
+ toJSON: function() {
+ return {
+ shape: this.shape,
+ color: this.color,
+ eraser: this.eraser,
+ transformations: this.transformations,
+ text: this.text,
+ lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
+ textRightAligned: this.textRightAligned,
+ font: this.font,
+ points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
+ };
+ },
+
+ _drawCairo: function(cr, params) {
+ let points = this.points;
+
+ if (points.length == 2) {
+ let layout = PangoCairo.create_layout(cr);
+ let fontSize = Math.abs(points[1][1] - points[0][1]) * Pango.SCALE;
+ let fontDescription = new Pango.FontDescription();
+ fontDescription.set_absolute_size(fontSize);
+ ['family', 'weight', 'style', 'stretch', 'variant'].forEach(attribute => {
+ if (this.font[attribute] !== undefined)
+ try {
+ fontDescription[`set_${attribute}`](this.font[attribute]);
+ } catch(e) {}
+ });
+ layout.set_font_description(fontDescription);
+ layout.set_text(this.text, -1);
+ this.textWidth = layout.get_pixel_size()[0];
+ cr.moveTo(points[1][0] - (this.textRightAligned ? this.textWidth : 0), Math.max(points[0][1],points[1][1]) - layout.get_baseline() / Pango.SCALE);
+ layout.set_text(this.text, -1);
+ PangoCairo.show_layout(cr, layout);
+
+ if (params.showTextCursor) {
+ let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition;
+ layout.set_text(this.text.slice(0, cursorPosition), -1);
+ let width = layout.get_pixel_size()[0];
+ cr.rectangle(points[1][0] - (this.textRightAligned ? this.textWidth : 0) + width, Math.max(points[0][1],points[1][1]),
+ Math.abs(points[1][1] - points[0][1]) / 25, - Math.abs(points[1][1] - points[0][1]));
+ cr.fill();
+ }
+
+ if (params.showTextRectangle || params.drawTextRectangle) {
+ cr.rectangle(points[1][0] - (this.textRightAligned ? this.textWidth : 0), Math.max(points[0][1], points[1][1]),
+ this.textWidth, - Math.abs(points[1][1] - points[0][1]));
+ if (params.showTextRectangle)
+ setDummyStroke(cr);
+ else
+ // Only draw the rectangle to find the element, not to show it.
+ cr.setLineWidth(0);
+ }
+ }
+ },
+
+ getContainsPoint: function(cr, x, y) {
+ return cr.inFill(x, y);
+ },
+
+ _drawSvg: function(transAttribute) {
+ let row = "\n ";
+ let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]);
+ let color = this.eraser ? bgColor : this.color;
+ let attributes = '';
+
+ if (points.length == 2) {
+ attributes = `fill="${color}" ` +
+ `stroke="transparent" ` +
+ `stroke-opacity="0" ` +
+ `font-size="${Math.abs(points[1][1] - points[0][1])}"`;
+
+ if (this.font.family)
+ attributes += ` font-family="${this.font.family}"`;
+ if (this.font.weight && this.font.weight != Pango.Weight.NORMAL)
+ attributes += ` font-weight="${this.font.weight}"`;
+ if (this.font.style && FontStyleNames[this.font.style])
+ attributes += ` font-style="${FontStyleNames[this.font.style].toLowerCase()}"`;
+ if (FontStretchNames[this.font.stretch] && this.font.stretch != Pango.Stretch.NORMAL)
+ attributes += ` font-stretch="${FontStretchNames[this.font.stretch].toLowerCase()}"`;
+ if (this.font.variant && FontVariantNames[this.font.variant])
+ attributes += ` font-variant="${FontVariantNames[this.font.variant].toLowerCase()}"`;
+
+ // this.textWidth is computed during Cairo building.
+ row += `${this.text}`;
+ }
+
+ return row;
+ },
+
+ updateDrawing: function(x, y, transform) {
+ let points = this.points;
+ if (x == points[points.length - 1][0] && y == points[points.length - 1][1])
+ return;
+
+ transform = transform || this.transformations.length >= 1;
+
+ if (transform) {
+ if (points.length < 2)
+ return;
+
+ let [slideX, slideY] = [x - points[1][0], y - points[1][1]];
+ points[0] = [points[0][0] + slideX, points[0][1] + slideY];
+ points[1] = [x, y];
+
+ } else {
+ points[1] = [x, y];
+
+ }
+ },
+
+ // When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line.
+ _getLineOffset: function() {
+ return (this.lineIndex || 0) * Math.abs(this.points[1][1] - this.points[0][1]);
+ },
+
+ _getOriginalCenter: function() {
+ if (!this._originalCenter) {
+ let points = this.points;
+ this._originalCenter = this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this._getLineOffset()] :
+ points.length >= 3 ? getCentroid(points) :
+ getNaiveCenter(points);
+ }
+
+ return this._originalCenter;
+ },
+});
+
const setDummyStroke = function(cr) {
cr.setLineWidth(2);
cr.setLineCap(0);
@@ -655,10 +722,6 @@ const setDummyStroke = function(cr) {
cr.setDash([1, 2], 0);
};
-/*
- Some geometric utils
-*/
-
const getNearness = function(pointA, pointB, distance) {
return Math.hypot(pointB[0] - pointA[0], pointB[1] - pointA[1]) < distance;
};