diff --git a/HISTORY.md b/HISTORY.md index b96dc105..516c44ae 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,12 +2,14 @@ ## in development: * **New Features:** + * export pen trails as SVG * **Notable Changes:** * **Notable Fixes:** * **Translation Updates:** ### 2019-12-02 * new dev version +* objects: export pen trails as SVG (under construction) ## v5.3.8: * **Notable Change:** diff --git a/snap.html b/snap.html index 0b2adecd..2ad440d0 100755 --- a/snap.html +++ b/snap.html @@ -8,7 +8,7 @@ - + diff --git a/src/objects.js b/src/objects.js index e36d2877..d61bf520 100644 --- a/src/objects.js +++ b/src/objects.js @@ -84,7 +84,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, BooleanSlotMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, VectorPaintEditorMorph, HandleMorph, AlignmentMorph, Process, XML_Element, WorldMap, copyCanvas*/ -modules.objects = '2019-November-29'; +modules.objects = '2019-December-02'; var SpriteMorph; var StageMorph; @@ -688,15 +688,20 @@ SpriteMorph.prototype.initBlocks = function () { category: 'pen', spec: 'pen trails' }, - - // Pen - experimental primitives for development mode doPasteOn: { - dev: true, type: 'command', category: 'pen', spec: 'paste on %spr' }, + // Pen - experimental primitives for development mode + reportPentrailsAsSVG: { + dev: true, + type: 'reporter', + category: 'pen', + spec: 'pen trails (SVG)' + }, + // Control receiveGo: { type: 'hat', @@ -2321,6 +2326,22 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('='); blocks.push(this.makeBlockButton(cat)); + // for debugging: /////////////// + + if (this.world().isDevMode) { + blocks.push('-'); + txt = new TextMorph(localize( + 'development mode \ndebugging primitives:' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('reportPentrailsAsSVG')); + } + + //////////////////////////////// + } else if (cat === 'control') { blocks.push(block('receiveGo')); @@ -5171,6 +5192,18 @@ SpriteMorph.prototype.drawLine = function (start, dest) { ).intersect(this.parent.visibleBounds()).spread(); if (this.isDown) { + // record for future svg conversion // +++ + this.parent.trailsLog.push( + [ + this.snapPoint(start), + this.snapPoint(dest), + this.color.copy(), + this.size, + this.useFlatLineEnds ? 'butt' : 'round' + ] + ); + + // draw on the pen-trails layer context.lineWidth = this.size; context.strokeStyle = this.color.toString(); if (this.useFlatLineEnds) { @@ -5597,6 +5630,17 @@ SpriteMorph.prototype.bounceOffEdge = function () { this.positionTalkBubble(); }; +// SpriteMorph coordinate conversion // +++ + +SpriteMorph.prototype.snapPoint = function(aPoint) { // +++ + var stage = this.parentThatIsA(StageMorph), + origin = stage.center(); + return new Point( + (aPoint.x - origin.x) / stage.scale, + (origin.y - aPoint.y) / stage.scale + ); +}; + // SpriteMorph rotation center / fixation point manipulation SpriteMorph.prototype.setRotationX = function (absoluteX) { @@ -7417,6 +7461,7 @@ StageMorph.prototype.init = function (globals) { this.activeSounds = []; // do not persist this.trailsCanvas = null; + this.trailsLog = []; // +++ each line being [p1, p2, color, width, cap] this.isThreadSafe = false; this.microphone = new Microphone(); // audio input, do not persist @@ -7622,6 +7667,7 @@ StageMorph.prototype.drawOn = function (aCanvas, aRect) { StageMorph.prototype.clearPenTrails = function () { this.cachedPenTrailsMorph = null; this.trailsCanvas = newCanvas(this.dimensions, null, this.trailsCanvas); + this.trailsLog = []; // +++ this.changed(); }; @@ -8421,6 +8467,22 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('='); blocks.push(this.makeBlockButton(cat)); + // for debugging: /////////////// + + if (this.world().isDevMode) { + blocks.push('-'); + txt = new TextMorph(localize( + 'development mode \ndebugging primitives:' + )); + txt.fontSize = 9; + txt.setColor(this.paletteTextColor); + blocks.push(txt); + blocks.push('-'); + blocks.push(block('reportPentrailsAsSVG')); + } + + ///////////////////////////////// + } else if (cat === 'control') { blocks.push(block('receiveGo')); @@ -8769,6 +8831,14 @@ StageMorph.prototype.userMenu = function () { : 'turn all pen trails and stamps\n' + 'into a new background for the stage' ); + // +++ experimental + if (this.trailsLog.length) { + menu.addItem( + 'svg...', + 'exportTrailsLogAsSVG', + 'export pen trails line segments as SVG' + ); + } return menu; }; @@ -8851,6 +8921,73 @@ StageMorph.prototype.thumbnail = function ( return trg; }; +// StageMorph - exporting the pen trails as SVG // +++ + +StageMorph.prototype.exportTrailsLogAsSVG = function () { // +++ + var ide = this.parentThatIsA(IDE_Morph); + + ide.saveFileAs( + this.trailsLogAsSVG(), + 'image/svg', // +++'image/svg+xml', + ide.projectName || this.name + ); +}; + +StageMorph.prototype.trailsLogAsSVG = function () { // +++ + var myself = this, + bottomLeft = this.trailsLog[0][0], + topRight = bottomLeft, + maxWidth = this.trailsLog[0][3], + shift, + box, + p1, p2, + svg; + + // determine bounding box and max line width + this.trailsLog.forEach(function (line) { + bottomLeft = bottomLeft.min(line[0]); + bottomLeft = bottomLeft.min(line[1]); + topRight = topRight.max(line[0]); + topRight = topRight.max(line[1]); + maxWidth = Math.max(maxWidth, line[3]); + }); + box = bottomLeft.corner(topRight).expandBy(maxWidth / 2); + shift = new Point(-bottomLeft.x, topRight.y).translateBy(maxWidth / 2); + svg = ''; + + // to do: + // * add source comment ("exported from Snap! etc." + // * add "keep aspect ratio" governance + // * catch / disable if the trails log is empty + // * catch if the trails contain non-vectorizable elements (stamp, fill) + // * decide how to handle background colors + // * consider a logging limit to prevent an overflowing queue + + // for debugging the viewBox: + // svg += '' + + this.trailsLog.forEach(function (line) { + p1 = myself.normalizePoint(line[0]).translateBy(shift); + p2 = myself.normalizePoint(line[1]).translateBy(shift); + svg += ''; + }); + svg += ''; + return svg; +}; + +StageMorph.prototype.normalizePoint = function (snapPoint) { // +++ + return new Point(snapPoint.x, -snapPoint.y); +}; + // StageMorph hiding and showing: /*