Text elements as a separated class

This commit is contained in:
abakkk 2020-07-08 11:47:51 +02:00
parent 96efac1e3f
commit e8140eae88
2 changed files with 211 additions and 144 deletions

48
area.js
View File

@ -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);

View File

@ -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 += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
row += ` C ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}, ${points[3][0]} ${points[3][1]}`;
@ -372,26 +340,6 @@ var DrawingElement = new Lang.Class({
row += ` ${points[i][0]},${points[i][1]}`;
row += `"${transAttribute}/>`;
} 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 += `<text ${attributes} x="${points[1][0] - (this.textRightAligned ? this.textWidth : 0)}" `;
row += `y="${Math.max(points[0][1], points[1][1])}"${transAttribute}>${this.text}</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 += `<text ${attributes} x="${points[1][0] - (this.textRightAligned ? this.textWidth : 0)}" `;
row += `y="${Math.max(points[0][1], points[1][1])}"${transAttribute}>${this.text}</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;
};