kopia lustrzana https://github.com/backface/turtlestitch
1445 wiersze
48 KiB
JavaScript
1445 wiersze
48 KiB
JavaScript
/*
|
|
|
|
Copyright 2012 Nathan Dinsmore
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Last changed 2017-07-04 by Jens Moenig (disabled text area overlay, introduced Sprite::isTemporary)
|
|
|
|
*/
|
|
|
|
var sb = (function (sb) {
|
|
'use strict';
|
|
|
|
function extend(o, p) {
|
|
var key;
|
|
for (key in p) if (p.hasOwnProperty(key)) {
|
|
o[key] = p[key];
|
|
}
|
|
}
|
|
sb.$extend = extend;
|
|
|
|
extend((sb.Ref = function (id) {
|
|
this.id = id;
|
|
}).prototype, {
|
|
isRef: true
|
|
});
|
|
|
|
extend((sb.Dictionary = function (keys, values) {
|
|
this.keys = keys;
|
|
this.values = values;
|
|
}).prototype, {
|
|
get: function (key) {
|
|
return this.values[this.keys.indexOf(key)];
|
|
},
|
|
set: function (key, value) {
|
|
var i = this.keys.indexOf(key);
|
|
if (i === -1) {
|
|
this.keys.push(key);
|
|
this.values.push(value);
|
|
} else {
|
|
this.values[i] = value;
|
|
}
|
|
}
|
|
});
|
|
|
|
sb.Color = function (rgb, a) {
|
|
this.r = (rgb / 0x100000 | 0) % 0x400 / (0x400 - 1);
|
|
this.g = (rgb / 0x400 | 0) % 0x400 / (0x400 - 1);
|
|
this.b = (rgb % 0x400) / (0x400 - 1);
|
|
this.a = a / (0x100 - 1);
|
|
};
|
|
sb.Point = function (x, y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
};
|
|
sb.Rectangle = function (x, y, x2, y2) {
|
|
this.origin = new sb.Point(x, y);
|
|
this.corner = new sb.Point(x2, y2);
|
|
};
|
|
|
|
sb.indexedColors = [
|
|
[1, 1, 1, 1],
|
|
[0, 0, 0, 1],
|
|
[1, 1, 1, 1],
|
|
[.5, .5, .5, 1],
|
|
[1, 0, 0, 1],
|
|
[0, 1, 0, 1],
|
|
[0, 0, 1, 1],
|
|
[0, 1, 1, 1],
|
|
[1, 1, 0, 1],
|
|
[1, 0, 1, 1],
|
|
[.125, .125, .125, 1],
|
|
[.25, .25, .25, 1],
|
|
[.375, .375, .375, 1],
|
|
[.625, .625, .625, 1],
|
|
[.75, .75, .75, 1],
|
|
[.875, .875, .875, 1]
|
|
];
|
|
(function () {
|
|
var i, r, g, b, grayVal;
|
|
for (i = 1; i <= 31; ++i) {
|
|
if (i % 4 != 0) {
|
|
grayVal = i / 32;
|
|
sb.indexedColors[i + 15] = [grayVal, grayVal, grayVal, 1];
|
|
}
|
|
}
|
|
for (r = 0; r < 6; ++r) {
|
|
for (g = 0; g < 6; ++g) {
|
|
for (b = 0; b < 6; ++b) {
|
|
i = 40 + 36 * r + 6 * b + g;
|
|
sb.indexedColors[i] = [r / 5, g / 5, b / 5, 1];
|
|
}
|
|
}
|
|
}
|
|
})();
|
|
|
|
sb.colorDepthLengths = {
|
|
1: 1,
|
|
2: 2,
|
|
4: 4,
|
|
8: 8,
|
|
15: 5,
|
|
16: 5,
|
|
12: 4,
|
|
9: 3
|
|
};
|
|
extend((sb.ColorStream = function (bitmap, depth) {
|
|
var i = 0,
|
|
l = bitmap.length,
|
|
b = this.bits = [],
|
|
n;
|
|
this.length = sb.colorDepthLengths[this.depth = depth];
|
|
this.position = 0;
|
|
while (i < l) {
|
|
n = bitmap[i++];
|
|
b.push(n / 0x80 & 1);
|
|
b.push(n / 0x40 & 1);
|
|
b.push(n / 0x20 & 1);
|
|
b.push(n / 0x10 & 1);
|
|
b.push(n / 0x8 & 1);
|
|
b.push(n / 0x4 & 1);
|
|
b.push(n / 0x2 & 1);
|
|
b.push(n & 1);
|
|
}
|
|
}).prototype, {
|
|
read: function () {
|
|
var i = this.length,
|
|
b = this.bits,
|
|
n = 0;
|
|
while (i--) {
|
|
n = n * 2 + b[this.position++];
|
|
}
|
|
return n;
|
|
}
|
|
});
|
|
|
|
extend((sb.ColorStreamIndexed = function (bitmap, depth) {
|
|
sb.ColorStream.call(this, bitmap, depth);
|
|
}).prototype = Object.create(sb.ColorStream.prototype), {
|
|
next: function (b) {
|
|
var c = sb.indexedColors[this.read()];
|
|
if (!c) return c;
|
|
b[0] = c[0];
|
|
b[1] = c[1];
|
|
b[2] = c[2];
|
|
b[3] = c[3];
|
|
}
|
|
});
|
|
|
|
extend((sb.ColorStreamRGB = function (bitmap, depth) {
|
|
sb.ColorStream.call(this, bitmap, depth);
|
|
this.max = Math.pow(2, this.length) - 1;
|
|
}).prototype = Object.create(sb.ColorStream.prototype), {
|
|
next: function (b) {
|
|
var c;
|
|
switch (this.depth) {
|
|
case 16:
|
|
++this.position;
|
|
c = [this.read() / this.max, this.read() / this.max, this.read() / this.max, 1];
|
|
if (c[0] + c[1] + c[2] === 0) {
|
|
return b[0] = b[1] = b[2] = b[3] = 0;
|
|
}
|
|
b[0] = c[0];
|
|
b[1] = c[1];
|
|
b[2] = c[2];
|
|
b[3] = c[3];
|
|
return;
|
|
case 12:
|
|
this.position += 3;
|
|
break;
|
|
}
|
|
b[0] = this.read() / this.max;
|
|
b[1] = this.read() / this.max;
|
|
b[2] = this.read() / this.max;
|
|
b[3] = 1;
|
|
}
|
|
});
|
|
|
|
extend((sb.ColorStream32 = function (bitmap, depth) {
|
|
this.bytes = bitmap;
|
|
if (depth === 32) {
|
|
this.next = this.nextAlpha;
|
|
}
|
|
this.position = 0;
|
|
}).prototype, {
|
|
next: function (b) {
|
|
b[0] = this.read() / 255;
|
|
b[1] = this.read() / 255;
|
|
b[2] = this.read() / 255;
|
|
b[3] = 1;
|
|
},
|
|
nextAlpha: function (b) {
|
|
var a = this.read() / 255;
|
|
b[0] = this.read() / 255;
|
|
b[1] = this.read() / 255;
|
|
b[2] = this.read() / 255
|
|
b[3] = a;
|
|
},
|
|
read: function () {
|
|
return this.bytes[this.position++];
|
|
}
|
|
});
|
|
|
|
sb.colorStreams = {
|
|
1: sb.ColorStreamIndexed,
|
|
2: sb.ColorStreamIndexed,
|
|
4: sb.ColorStreamIndexed,
|
|
8: sb.ColorStreamIndexed,
|
|
15: sb.ColorStreamRGB,
|
|
16: sb.ColorStreamRGB,
|
|
32: sb.ColorStream32,
|
|
24: sb.ColorStream32,
|
|
12: sb.ColorStreamRGB,
|
|
9: sb.ColorStreamRGB
|
|
};
|
|
|
|
sb.getColorStream = function (bitmap, depth) {
|
|
return new sb.colorStreams[depth](bitmap, depth);
|
|
};
|
|
|
|
extend((sb.Form = function (w, h, d, o, b) {
|
|
this.width = w;
|
|
this.height = h;
|
|
this.depth = d;
|
|
this.offset = o;
|
|
this.bits = b;
|
|
}).prototype, {
|
|
init: function (bm) {
|
|
if (this.bits.isBitmap) {
|
|
this.bitmap = this.bits;
|
|
} else {
|
|
this.decompress(bm);
|
|
}
|
|
this.image = document.createElement('canvas');
|
|
this.image.width = this.width;
|
|
this.image.height = this.height;
|
|
},
|
|
decompress: function (bm) {
|
|
var b = this.bits,
|
|
p = 0,
|
|
q = 0,
|
|
r = !!bm,
|
|
length = (i = b[p++], i <= 223 ? i : i < 255 ? (i - 224) * 256 + b[p++] : (b[p++] << 24) + (b[p++] << 16) + (b[p++] << 8) + b[p++]),
|
|
bm, i, n, d, e, f, g;
|
|
// stream = new sb.Reader().on(this.bits),
|
|
this.bitmap = bm || (bm = []);
|
|
while (p < b.length) {
|
|
i = b[p++];
|
|
if (i > 223) {
|
|
i = i < 255 ? (i - 224) * 256 + b[p++] : (b[p++] << 24) + (b[p++] << 16) + (b[p++] << 8) + b[p++];
|
|
}
|
|
n = i >> 2;
|
|
switch (i & 3) {
|
|
case 1:
|
|
d = b[p++];
|
|
n *= 4;
|
|
while (n--) {
|
|
bm[q++] = d;
|
|
}
|
|
break;
|
|
case 2:
|
|
// d = stream.readBytes(4);
|
|
d = b[p++];
|
|
e = b[p++];
|
|
f = b[p++];
|
|
g = b[p++];
|
|
while (n--) {
|
|
if (r) {
|
|
bm[q++] = e;
|
|
bm[q++] = f;
|
|
bm[q++] = g;
|
|
bm[q++] = d;
|
|
} else {
|
|
bm[q++] = d;
|
|
bm[q++] = e;
|
|
bm[q++] = f;
|
|
bm[q++] = g;
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
while (n--) {
|
|
if (r) {
|
|
d = b[p++];
|
|
bm[q++] = b[p++];
|
|
bm[q++] = b[p++];
|
|
bm[q++] = b[p++];
|
|
bm[q++] = d;
|
|
} else {
|
|
bm[q++] = b[p++];
|
|
bm[q++] = b[p++];
|
|
bm[q++] = b[p++];
|
|
bm[q++] = b[p++];
|
|
}
|
|
}
|
|
// bm.push.apply(bm, stream.readBytes(n * 4));
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
load: function () {
|
|
var w = this.width,
|
|
h = this.height,
|
|
x, y, i, imageData, data, context, colors, color, b;
|
|
if (this.depth === 32) {
|
|
this.image = document.createElement('canvas');
|
|
this.image.width = this.width;
|
|
this.image.height = this.height;
|
|
data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data;
|
|
this.decompress(data);
|
|
context.putImageData(imageData, 0, 0);
|
|
return;
|
|
}
|
|
color = [0, 0, 0, 0];
|
|
if (this.depth === 16) {
|
|
w = this.width += 1;
|
|
}
|
|
this.init();
|
|
colors = sb.getColorStream(this.bitmap, this.depth);
|
|
data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data;
|
|
for (x = 0; x < w; ++x) {
|
|
for (y = 0; y < h; ++y) {
|
|
colors.next(color);
|
|
if (!color) continue;
|
|
i = (x * h + y) * 4;
|
|
data[i] = color[0] * 255;
|
|
data[i + 1] = color[1] * 255;
|
|
data[i + 2] = color[2] * 255;
|
|
data[i + 3] = color[3] * 255;
|
|
}
|
|
}
|
|
context.putImageData(imageData, 0, 0);
|
|
}
|
|
});
|
|
|
|
extend((sb.ColorForm = function (w, h, d, o, b, c) {
|
|
this.width = w;
|
|
this.height = h;
|
|
this.depth = d;
|
|
this.offset = o;
|
|
this.bits = b;
|
|
this.colors = c;
|
|
}).prototype = Object.create(sb.Form.prototype), {
|
|
load: function () {
|
|
var w = this.width,
|
|
h = this.height,
|
|
colors = this.colors,
|
|
bits, x, y, i, imageData, data, context, color;
|
|
this.init();
|
|
data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data;
|
|
bits = this.bitmap;
|
|
for (x = 0; x < w; ++x) {
|
|
for (y = 0; y < h; ++y) {
|
|
color = colors[bits[x * h + y]];
|
|
if (!color) continue;
|
|
i = (x * h + y) * 4;
|
|
data[i] = color.r * 255;
|
|
data[i + 1] = color.g * 255;
|
|
data[i + 2] = color.b * 255;
|
|
data[i + 3] = color.a * 255;
|
|
}
|
|
}
|
|
context.putImageData(imageData, 0, 0);
|
|
}
|
|
});
|
|
|
|
sb.fields = {};
|
|
sb.classIDs = {};
|
|
sb.classNames = {};
|
|
sb.addFields = function (id, name, base, fields) {
|
|
sb.classIDs[name] = id;
|
|
sb.classNames[id] = name;
|
|
fields = fields.length ? fields.split(',') : [];
|
|
if (base) {
|
|
base = sb.fields[sb.classIDs[base]];
|
|
if (!base) throw new Error('Initialization error');
|
|
fields = base.concat(fields);
|
|
}
|
|
sb.fields[id] = fields;
|
|
};
|
|
|
|
sb.addFields(100, 'Morph', '', 'bounds,owner,submorphs,color,flags,properties');
|
|
sb.addFields(101, 'BorderedMorph', 'Morph', 'borderWidth,borderColor');
|
|
sb.addFields(102, 'RectangleMorph', 'BorderedMorph', '');
|
|
sb.addFields(103, 'EllipseMorph', 'BorderedMorph', '');
|
|
sb.addFields(104, 'AlignmentMorph', 'RectangleMorph', 'orientation,centering,hResizing,vResizing,inset');
|
|
sb.addFields(105, 'StringMorph', 'Morph', 'fontSpec,emphasis,contents');
|
|
|
|
sb.addFields(-1, 'Slider', 'BorderedMorph', 'slider,value,setValueSelector,sliderShadow,sliderColor,descending,model');
|
|
sb.addFields(-2, 'AbstractSound', '', '');
|
|
sb.addFields(-3, 'ScriptableScratchMorph', 'Morph', 'objName,vars,blocksBin,customBlocks,isTemporary,media,costume');
|
|
sb.addFields(-4, 'ArgMorph', 'BorderedMorph', 'labelMorph');
|
|
sb.addFields(-5, 'PasteUpMorph', 'BorderedMorph', '');
|
|
sb.addFields(-6, 'ScratchMedia', '', 'mediaName');
|
|
sb.addFields(-7, 'ScrollFrameMorph', 'BorderedMorph', '');
|
|
|
|
sb.addFields(106, 'UpdatingStringMorph', 'StringMorph', 'format,target,getSelector,putSelector,parameter,floatPrecision,growable,stepTime');
|
|
sb.addFields(107, 'SimpleSliderMorph', 'Slider', 'target,arguments,minVal,maxVal,truncate,sliderThickness');
|
|
sb.addFields(108, 'SimpleButtonMorph', 'RectangleMorph', 'target,actionSelector,arguments,actWhen');
|
|
sb.addFields(109, 'SampledSound', 'AbstractSound', 'envelopes,scaledVol,initialCount,samples,originalSamplingRate,samplesSize,scaledIncrement,scaledInitialIndex');
|
|
sb.addFields(110, 'ImageMorph', 'Morph', 'form,transparency');
|
|
sb.addFields(111, 'SketchMorph', 'Morph', 'originalForm,rotationCenter,rotationDegrees,rotationStyle,scalePoint,offsetWhenRotated');
|
|
sb.addFields(123, 'SensorBoardMorph', 'Morph', 'portNum');
|
|
sb.addFields(124, 'ScratchSpriteMorph', 'ScriptableScratchMorph', 'visibility,scalePoint,rotationDegrees,rotationStyle,volume,tempoBPM,draggable,sceneStates,lists,virtualScale,ownerSprite,subsprites,rotateWithOwner,refPos,prototype,deletedAttributes');
|
|
sb.addFields(125, 'ScratchStageMorph', 'ScriptableScratchMorph', 'zoom,hPan,vPan,obsoleteSavedState,sprites,volume,tempoBPM,sceneStates,lists');
|
|
sb.addFields(140, 'ChoiceArgMorph', 'ArgMorph', 'isBoolean,options,choice,getOptionsSelector');
|
|
sb.addFields(141, 'ColorArgMorph', 'ArgMorph', '');
|
|
sb.addFields(142, 'ExpressionArgMorph', 'ArgMorph', 'isNumber');
|
|
sb.addFields(145, 'SpriteArgMorph', 'ArgMorph', 'morph');
|
|
sb.addFields(147, 'BlockMorph', 'Morph', 'isSpecialForm,oldColor');
|
|
sb.addFields(148, 'CommandBlockMorph', 'BlockMorph', 'commandSpec,argMorphs,titleMorph,receiver,selector,isReporter,isTimed,wantsName,wantsPossession');
|
|
sb.addFields(149, 'CBlockMorph', 'CommandBlockMorph', 'nestedBlock,nextBlock');
|
|
sb.addFields(151, 'HatBlockMorph', 'CommandBlockMorph', 'scriptNameMorph,indicatorMorph,scriptOwner,parameters,isClickable');
|
|
sb.addFields(153, 'ScratchScriptsMorph', 'PasteUpMorph', '');
|
|
sb.addFields(154, 'ScratchSliderMorph', 'AlignmentMorph', 'slider,sliderMin,sliderMax,variable');
|
|
sb.addFields(155, 'WatcherMorph', 'AlignmentMorph', 'titleMorph,readout,readoutFrame,scratchSlider,watcher,isSpriteSpecific,unused,sliderMin,sliderMax,isLarge');
|
|
sb.addFields(157, 'SetterBlockMorph', 'CommandBlockMorph', 'variable');
|
|
sb.addFields(158, 'EventHatMorph', 'HatBlockMorph', '');
|
|
sb.addFields(170, 'ReporterBlockMorph', 'CommandBlockMorph', 'isBoolean');
|
|
sb.addFields(160, 'VariableBlockMorph', 'ReporterBlockMorph', '');
|
|
sb.addFields(162, 'ImageMedia', 'ScratchMedia', 'form,rotationCenter,textBox,jpegBytes,compositeForm');
|
|
sb.addFields(163, 'MovieMedia', 'ScratchMedia', 'fileName,fade,fadeColor,zoom,hPan,vPan,msecsPerFrame,currentFrame,moviePlaying');
|
|
sb.addFields(164, 'SoundMedia', 'ScratchMedia', 'originalSound,volume,balance,compressedSampleRate,compressedBitsPerSample,compressedData');
|
|
sb.addFields(165, 'KeyEventHatMorph', 'HatBlockMorph', '');
|
|
sb.addFields(166, 'BooleanArgMorph', 'ArgMorph', '');
|
|
sb.addFields(167, 'EventTitleMorph', 'ArgMorph', '');
|
|
sb.addFields(168, 'MouseClickEventHatMorph', 'HatBlockMorph', '');
|
|
sb.addFields(169, 'ExpressionArgMorphWithMenu', 'ExpressionArgMorph', 'menuMorph,getMenuSelector,specialValue');
|
|
sb.addFields(171, 'MultilineStringMorph', 'BorderedMorph', 'fontSpec,textColor,selectionColor,lines');
|
|
sb.addFields(172, 'ToggleButton', 'SimpleButtonMorph', 'onForm,offForm,overForm,disabledForm,isMomentary,toggleMode,isOn,isDisabled');
|
|
sb.addFields(173, 'WatcherReadoutFrameMorph', 'BorderedMorph', '');
|
|
sb.addFields(174, 'WatcherSliderMorph', 'SimpleSliderMorph', '');
|
|
sb.addFields(175, 'ScratchListMorph', 'BorderedMorph', 'listName,strings,target,complex');
|
|
sb.addFields(176, 'ScrollingStringMorph', 'BorderedMorph', 'fontSpec,showScrollbar,firstVisibleLine,textColor,selectionColor,lines');
|
|
|
|
sb.addFields(180, 'ScrollFrameMorph2', 'ScrollFrameMorph', '');
|
|
sb.addFields(181, 'ListMultilineStringMorph', 'MultilineStringMorph', '');
|
|
sb.addFields(182, 'ScratchScrollBar', 'Morph', '');
|
|
|
|
sb.addFields(200, 'CustomCommandBlockMorph', 'CommandBlockMorph', 'userSpec');
|
|
sb.addFields(201, 'CustomBlockDefinition', '', 'userSpec,blockVars,isAtomic,isReporter,isBoolean,body,answer,type,category,declarations,defaults,isGlobal');
|
|
sb.addFields(203, 'ReporterScriptBlockMorph', 'ReporterBlockMorph', '');
|
|
sb.addFields(202, 'CommandScriptBlockMorph', 'ReporterScriptBlockMorph', '');
|
|
sb.addFields(205, 'VariableFrame', '', 'vars');
|
|
sb.addFields(206, 'CustomReporterBlockMorph', 'ReporterBlockMorph', 'userSpec');
|
|
|
|
sb.addFields(207, 'CReporterSlotMorph', 'ReporterScriptBlockMorph', '');
|
|
|
|
sb.addFields(300, 'StringFieldMorph', 'BorderedMorph', '');
|
|
sb.addFields(301, 'MultiArgReporterBlockMorph', 'ReporterBlockMorph', '');
|
|
|
|
(function (C, p) {
|
|
|
|
p.on = function (bytes) {
|
|
this.bytes = bytes;
|
|
if (!bytes.subarray) bytes.subarray = bytes.slice;
|
|
this.position = 0;
|
|
return this;
|
|
};
|
|
p.readYPR = function (bytes) {
|
|
var version, infoSize, info, stage;
|
|
console.time('readSB');
|
|
this.on(bytes);
|
|
|
|
// skip header
|
|
this.matchBytes([66, 108, 111, 120, 69, 120, 112, 86]);
|
|
version = +(String.fromCharCode(this.next()) + String.fromCharCode(this.next()));
|
|
if (version < 1) {
|
|
throw new Error('Invalid version');
|
|
}
|
|
|
|
// read info
|
|
infoSize = this.uint32();
|
|
info = this.read();
|
|
|
|
this.position = infoSize + 14; // header + uint32
|
|
stage = this.read();
|
|
|
|
this.onload({
|
|
reader: this,
|
|
info: info,
|
|
stage: stage
|
|
});
|
|
console.timeEnd('readSB');
|
|
};
|
|
p.readInfo = function (bytes) {
|
|
var version, infoSize, info;
|
|
this.on(bytes);
|
|
|
|
this.matchBytes([66, 108, 111, 120, 69, 120, 112, 86]);
|
|
version = +(String.fromCharCode(this.next()) + String.fromCharCode(this.next()));
|
|
if (version < 1) {
|
|
throw new Error('Invalid version');
|
|
}
|
|
|
|
// read info
|
|
infoSize = this.uint32();
|
|
info = this.read();
|
|
|
|
this.onload({
|
|
reader: this,
|
|
info: info
|
|
});
|
|
};
|
|
p.read = function () {
|
|
var i, objectCount;
|
|
this.objects = [];
|
|
this.readHeader();
|
|
objectCount = this.uint32();
|
|
|
|
i = objectCount;
|
|
while (i--) {
|
|
this.objects.push(this.readObject());
|
|
}
|
|
i = objectCount;
|
|
while (i--) {
|
|
this.fixReferences(this.objects[i]);
|
|
}
|
|
return this.objects[0][1];
|
|
};
|
|
p.fixReferences = function (object) {
|
|
var classID = object[0],
|
|
value = object[1],
|
|
i = 0,
|
|
fields, source;
|
|
if (classID < 99) {
|
|
this.fixFixedFormat(classID, value);
|
|
} else {
|
|
this.fixArray(object[3]);
|
|
fields = sb.fields[classID];
|
|
if (!fields)
|
|
throw new Error('Invalid class ID ' + classID);
|
|
source = value.fields;
|
|
delete value.fields;
|
|
value.className = sb.classNames[classID];
|
|
i = fields.length;
|
|
while (i--) {
|
|
value[fields[i]] = source[i];
|
|
}
|
|
}
|
|
};
|
|
p.fixArray = function (a) {
|
|
var i = a.length,
|
|
o;
|
|
while (i--) {
|
|
if ((o = a[i]) && o.isRef) {
|
|
if (o.id > this.objects.length) {
|
|
throw new Error('Invalid object reference');
|
|
}
|
|
a[i] = this.objects[o.id - 1][1];
|
|
}
|
|
}
|
|
};
|
|
p.targetObjectFor = function (o) {
|
|
if (o && o.isRef) {
|
|
if (o.id > this.objects.length)
|
|
throw new Error('Invalid object reference');
|
|
return this.objects[o.id - 1][1];
|
|
}
|
|
return o;
|
|
};
|
|
p.fixFixedFormat = function (classID, object) {
|
|
switch (classID) {
|
|
case 20: // Array
|
|
case 21: // OrderedCollection
|
|
case 22: // Set
|
|
case 23: // IdentitySet
|
|
this.fixArray(object);
|
|
return object;
|
|
case 24: // Dictionary
|
|
case 25: // IdentityDictionary
|
|
this.fixArray(object.keys);
|
|
this.fixArray(object.values);
|
|
break;
|
|
case 32: // Point
|
|
object.x = this.targetObjectFor(object.x);
|
|
object.y = this.targetObjectFor(object.y);
|
|
break;
|
|
case 33: // Rectangle
|
|
object.origin.x = this.targetObjectFor(object.origin.x);
|
|
object.origin.y = this.targetObjectFor(object.origin.y);
|
|
object.corner.x = this.targetObjectFor(object.corner.x);
|
|
object.corner.y = this.targetObjectFor(object.corner.y);
|
|
break;
|
|
case 34: // Form
|
|
object.offset = this.targetObjectFor(object.offset);
|
|
object.bits = this.targetObjectFor(object.bits);
|
|
object.load();
|
|
break;
|
|
case 35: // ColorForm
|
|
object.offset = this.targetObjectFor(object.offset);
|
|
object.bits = this.targetObjectFor(object.bits);
|
|
object.colors = this.targetObjectFor(object.colors);
|
|
object.load();
|
|
break;
|
|
}
|
|
return object;
|
|
};
|
|
p.readObject = function () {
|
|
var classID = this.next(),
|
|
version, fieldCount, fields;
|
|
if (classID > 99) {
|
|
version = this.next();
|
|
fieldCount = this.next();
|
|
fields = [];
|
|
while (fieldCount--) {
|
|
fields.push(this.readField());
|
|
}
|
|
return [classID, { fields: fields }, version, fields];
|
|
}
|
|
return [classID, this.readFixedFormat(classID)];
|
|
};
|
|
p.readField = function () {
|
|
var classID = this.next();
|
|
if (classID === 99) {
|
|
return new sb.Ref(this.uint24());
|
|
}
|
|
return this.readFixedFormat(classID);
|
|
};
|
|
p.readFixedFormat = function (classID) {
|
|
var a, n;
|
|
switch (classID) {
|
|
case 1: // UndefinedObject
|
|
return null;
|
|
case 2: // True
|
|
return true;
|
|
case 3: // False
|
|
return false;
|
|
case 4: // SmallInteger
|
|
return this.int32();
|
|
case 5: // SmallInteger16
|
|
return this.int16();
|
|
case 6: // LargePositiveInteger
|
|
case 7: // LargeNegativeInteger
|
|
n = this.uint16();
|
|
a = 0;
|
|
while (n--) {
|
|
a *= 0x100;
|
|
a += this.next();
|
|
}
|
|
return a;
|
|
case 8: // Float
|
|
return this.float64();
|
|
case 9: // String
|
|
case 10: // Symbol
|
|
case 14: // UTF8
|
|
a = [].slice.call(this.readBytes(n = this.uint32()));
|
|
while (n--) {
|
|
a[n] = String.fromCharCode(a[n]);
|
|
}
|
|
return a.join('');
|
|
case 11: // ByteArray
|
|
return this.readBytes(this.uint32());
|
|
case 12: // SoundBuffer
|
|
a = [];
|
|
n = this.uint32();
|
|
while (n--) {
|
|
a.push(this.int16());
|
|
}
|
|
return a;
|
|
case 13: // Bitmap
|
|
a = [];
|
|
a.isBitmap = true;
|
|
n = this.uint32();
|
|
while (n--) {
|
|
a.push(this.uint32());
|
|
}
|
|
return a;
|
|
case 20: // Array
|
|
case 21: // OrderedCollection
|
|
case 22: // Set
|
|
case 23: // IdentitySet
|
|
a = [];
|
|
n = this.uint32();
|
|
while (n--) {
|
|
a.push(this.readField());
|
|
}
|
|
return a;
|
|
case 24: // Dictionary
|
|
case 25: // IdentityDictionary
|
|
a = new sb.Dictionary([], []);
|
|
n = this.uint32();
|
|
while (n--) {
|
|
a.keys.push(this.readField());
|
|
a.values.push(this.readField());
|
|
}
|
|
return a;
|
|
case 30: // Color
|
|
return new sb.Color(this.uint32(), 255);
|
|
case 31: // TranslucentColor
|
|
return new sb.Color(this.uint32(), this.uint8());
|
|
case 32: // Point
|
|
return new sb.Point(this.readField(), this.readField());
|
|
case 33: // Rectangle
|
|
return new sb.Rectangle(this.readField(), this.readField(), this.readField(), this.readField());
|
|
case 34: // Form
|
|
return new sb.Form(this.readField(), this.readField(), this.readField(), this.readField(), this.readField());
|
|
case 35: // ColorForm
|
|
return new sb.ColorForm(this.readField(), this.readField(), this.readField(), this.readField(), this.readField(), this.readField());
|
|
}
|
|
throw new Error('Invalid fixed-format class ID');
|
|
};
|
|
p.readHeader = function () {
|
|
this.matchBytes([79, 98, 106, 83, 1, 83, 116, 99, 104, 1]);
|
|
};
|
|
p.readBytes = function (length) {
|
|
return this.bytes.subarray(this.position, this.position += length);
|
|
};
|
|
p.matchBytes = function (bytes) {
|
|
var i = bytes.length,
|
|
r = this.readBytes(i);
|
|
while (i--) {
|
|
if (r[i] !== bytes[i]) {
|
|
throw new Error('Invalid format');
|
|
}
|
|
}
|
|
};
|
|
p.skip = function (length) {
|
|
this.position += length;
|
|
};
|
|
p.next = p.uint8 = function () {
|
|
return this.bytes[this.position++];
|
|
};
|
|
p.hasNext = function () {
|
|
return this.position < this.bytes.length;
|
|
};
|
|
p.uint16 = function () {
|
|
return this.next() * 0x100 + this.next();
|
|
};
|
|
p.uint24 = function () {
|
|
return this.next() * 0x10000 + this.next() * 0x100 + this.next();
|
|
};
|
|
p.uint32 = function () {
|
|
return this.next() * 0x1000000 + this.next() * 0x10000 + this.next() * 0x100 + this.next();
|
|
};
|
|
p.int8 = function () {
|
|
var v = this.bytes[++this.position];
|
|
return v >= 0x80 ? v - 0x100 : v;
|
|
};
|
|
p.int16 = function () {
|
|
var d = this.next(),
|
|
v = d * 0x100 + this.next();
|
|
return d >= 0x80 ? v - 0x10000 : v;
|
|
};
|
|
p.int24 = function () {
|
|
var d = this.next(),
|
|
v = d * 0x10000 + this.next() * 0x100 + this.next();
|
|
return d >= 0x80 ? v - 0x1000000 : v;
|
|
};
|
|
p.int32 = function () {
|
|
var d = this.next(),
|
|
v = d * 0x1000000 + this.next() * 0x10000 + this.next() * 0x100 + this.next();
|
|
return d >= 0x80 ? v - 0x100000000 : v;
|
|
};
|
|
p.string = function () {
|
|
var length = this.uint16(),
|
|
bytes = this.readBytes(length),
|
|
i = length;
|
|
while (i--) {
|
|
bytes[i] = String.fromCharCode(bytes[i]);
|
|
}
|
|
return bytes.join('');
|
|
};
|
|
p.float64 = function () {
|
|
return this.ieee(8, 11, 52, 1023);
|
|
};
|
|
p.ieee = function (n, ebits, mbits) {
|
|
var bias = (1 << (ebits - 1)) - 1,
|
|
string = '',
|
|
i = n,
|
|
b, sign, exponent, mantissa, result;
|
|
while (i--) {
|
|
b = this.next().toString(2);
|
|
string = string + Array(9 - b.length).join('0') + b;
|
|
}
|
|
sign = string.charAt(0) === '0' ? 1 : -1;
|
|
exponent = parseInt(string.substr(1, ebits), 2);
|
|
mantissa = parseInt(string.substr(ebits + 1), 2);
|
|
if (exponent === 0) {
|
|
return mantissa === 0 ? sign * 0 : sign * Math.pow(2, 1 - bias) * mantissa / Math.pow(2, mbits);
|
|
}
|
|
if (exponent === (1 << ebits) - 1) {
|
|
return mantissa === 0 ? sign / 0 : NaN;
|
|
}
|
|
return sign * Math.pow(2, exponent - bias) * (1 + mantissa / Math.pow(2, mbits));
|
|
};
|
|
|
|
})(sb.Reader = function () {}, sb.Reader.prototype);
|
|
|
|
(function (C, p) {
|
|
|
|
var rotationStyles = {
|
|
normal: 1,
|
|
leftRight: 2,
|
|
none: 0
|
|
}, customBlockInputs = {
|
|
object: '%obj',
|
|
objectList: '%mult%obj',
|
|
number: '%n',
|
|
numberList: '%mult%n',
|
|
text: '%txt',
|
|
textList: '%mult%txt',
|
|
list: '%l',
|
|
listList: '%mult%l',
|
|
any: '%s',
|
|
anyList: '%mult%s',
|
|
'boolean': '%b',
|
|
booleanList: '%mult%b',
|
|
command: '%cmdRing',
|
|
commandList: '%mult%cmdRing',
|
|
reporter: '%repRing',
|
|
reporterList: '%mult%repRing',
|
|
predicate: '%predRing',
|
|
predicateList: '%mult%predRing',
|
|
loop: '%cs',
|
|
loopList: '%mult%cs',
|
|
unevaluated: '%anyUE',
|
|
unevaluatedList: '%mult%anyUE',
|
|
unevaluatedBoolean: '%boolUE',
|
|
unevaluatedBooleanList: '%mult%boolUE',
|
|
template: '%upvar'
|
|
}, blockSelectors = {
|
|
// Motion': '',
|
|
'forward:': 'forward',
|
|
'turnLeft:': 'turnLeft',
|
|
'turnRight:': 'turn',
|
|
'heading:': 'setHeading',
|
|
'pointTowards:': 'doFaceTowards',
|
|
'gotoX:y:': 'gotoXY',
|
|
'gotoSpriteOrMouse:': 'doGotoObject',
|
|
'glideSecs:toX:y:elapsed:from:': 'doGlide',
|
|
'changeXposBy:': 'changeXPosition',
|
|
'xpos:': 'setXPosition',
|
|
'changeYposBy:': 'changeYPosition',
|
|
'ypos:': 'setYPosition',
|
|
'bounceOffEdge': 'bounceOffEdge',
|
|
'xpos': 'xPosition',
|
|
'ypos': 'yPosition',
|
|
'heading': 'direction',
|
|
|
|
// Looks
|
|
'lookLike:': 'doSwitchToCostume',
|
|
'showBackground:': 'doSwitchToCostume',
|
|
'nextCostume': 'doWearNextCostume',
|
|
'nextBackground': 'doWearNextCostume',
|
|
'costumeIndex': 'getCostumeIdx',
|
|
'say:duration:elapsed:from:': 'doSayFor',
|
|
'say:': 'bubble',
|
|
'think:duration:elapsed:from:': 'doThinkFor',
|
|
'think:': 'doThink',
|
|
'changeGraphicEffect:by:': 'changeEffect',
|
|
'setGraphicEffect:to:': 'setEffect',
|
|
'filterReset': 'clearEffects',
|
|
'setSizeTo:': 'setScale',
|
|
'changeSizeBy:': 'changeScale',
|
|
'scale': 'getScale',
|
|
'show': 'show',
|
|
'hide': 'hide',
|
|
'comeToFront': 'comeToFront',
|
|
'goBackByLayers:': 'goBack',
|
|
|
|
// Sound
|
|
'playSound:': 'playSound',
|
|
'doPlaySoundAndWait': 'doPlaySoundUntilDone',
|
|
'stopAllSounds': 'doStopAllSounds',
|
|
// 'drum:duration:elapsed:from:': '',
|
|
'rest:elapsed:from:': 'doRest',
|
|
'noteOn:duration:elapsed:from:': 'doPlayNote',
|
|
// 'midiInstrument:': '',
|
|
// 'changeVolumeBy:': '',
|
|
// 'setVolumeTo:': '',
|
|
// 'volume': '',
|
|
'changeTempoBy:': 'doChangeTempo',
|
|
'setTempoTo:': 'doSetTempo',
|
|
'tempo': 'getTempo',
|
|
|
|
// Pen
|
|
'clearPenTrails': 'clear',
|
|
'putPenDown': 'down',
|
|
'putPenUp': 'up',
|
|
'penColor:': 'setColor',
|
|
'changePenHueBy:': 'changeHue',
|
|
'setPenHueTo:': 'setHue',
|
|
'_changePenHueBy:': 'changeHue',
|
|
'_setPenHueTo:': 'setHue',
|
|
'changePenShadeBy:': 'changeBrightness',
|
|
'setPenShadeTo:': 'setBrightness',
|
|
'changePenSizeBy:': 'changeSize',
|
|
'penSize:': 'setSize',
|
|
'stampCostume': 'doStamp',
|
|
|
|
// Control
|
|
// 'whenStartClicked': '',
|
|
// 'whenKeyPressed:': '',
|
|
// 'whenSpriteClicked': '',
|
|
'wait:elapsed:from:': 'doWait',
|
|
'doForever': 'doForever',
|
|
'doRepeat': 'doRepeat',
|
|
'broadcast:': 'doBroadcast',
|
|
'doBroadcastAndWait': 'doBroadcastAndWait',
|
|
// 'whenMessageReceived:': '',
|
|
// TODO 'doForeverIf': '',
|
|
'doIf': 'doIf',
|
|
'doIfElse': 'doIfElse',
|
|
'doWaitUntil': 'doWaitUntil',
|
|
'doUntil': 'doUntil',
|
|
'doReturn': 'doStop',
|
|
'stopAll': 'doStopAll',
|
|
'doRun': 'doRun',
|
|
'doRunBlockWithArgs': 'doRun',
|
|
'doRunBlockWithArgList': 'doRun',
|
|
'doFork': 'fork',
|
|
'doForkBlockWithArgs': 'fork',
|
|
'doForkBlockWithArgList': 'fork',
|
|
'doReport': 'evaluate',
|
|
'doCallBlockWithArgs': 'evaluate',
|
|
'doCallBlockWithArgList': 'evaluate',
|
|
'doAnswer': 'doReport',
|
|
'doStopBlock': 'doStopBlock',
|
|
// 'doPauseThread': '',
|
|
// 'doPauseThreadReporter': '',
|
|
|
|
// Sensing
|
|
'touching:': 'reportTouchingObject',
|
|
'touchingColor:': 'reportTouchingColor',
|
|
'color:sees:': 'reportColorIsTouchingColor',
|
|
'doAsk': 'doAsk',
|
|
'answer': 'reportLastAnswer',
|
|
'mouseX': 'reportMouseX',
|
|
'mouseY': 'reportMouseY',
|
|
'mousePressed': 'reportMouseDown',
|
|
'keyPressed:': 'reportKeyPressed',
|
|
'distanceTo:': 'reportDistanceTo',
|
|
'timerReset': 'doResetTimer',
|
|
'timer': 'reportTimer',
|
|
'getAttribute:of:': 'reportAttributeOf',
|
|
// 'attribute:of:': '',
|
|
// 'soundLevel': '',
|
|
// 'isLoud': '',
|
|
// 'sensor:': '',
|
|
// 'sensorPressed:': '',
|
|
// 'getObject:': '',
|
|
// 'get:': '',
|
|
|
|
// Operators
|
|
'+': 'reportSum',
|
|
'-': 'reportDifference',
|
|
'*': 'reportProduct',
|
|
'/': 'reportQuotient',
|
|
'randomFrom:to:': 'reportRandom',
|
|
'<': 'reportLessThan',
|
|
'=': 'reportEquals',
|
|
'>': 'reportGreaterThan',
|
|
'&': 'reportAnd',
|
|
'|': 'reportOr',
|
|
'not': 'reportNot',
|
|
'getTrue': 'reportTrue',
|
|
'getFalse': 'reportFalse',
|
|
'concatenate:with:': 'reportJoinWords',
|
|
'letter:of:': 'reportLetter',
|
|
'stringLength:': 'reportStringSize',
|
|
'asciiCodeOf:': 'reportUnicode',
|
|
'asciiLetter:': 'reportUnicodeAsLetter',
|
|
'\\\\': 'reportModulus',
|
|
'rounded': 'reportRound',
|
|
'computeFunction:of:': 'reportMonadic',
|
|
'isObject:type:': 'reportIsA',
|
|
// 'procedure': 'reifyScript',
|
|
// 'procedureWithArgs': 'reifyScript',
|
|
// 'function': 'reifyReporter',
|
|
// 'functionWithArgs': 'reifyReporter',
|
|
// 'spawn': '',
|
|
|
|
// Variables
|
|
'setVar:to:': 'doSetVar',
|
|
'changeVar:by:': 'doChangeVar',
|
|
// 'deleteObject:': '',
|
|
'showVariable:': 'doShowVar',
|
|
'hideVariable:': 'doHideVar',
|
|
'doDeclareVariables': 'doDeclareVariables',
|
|
'newList:': 'reportNewList',
|
|
'append:toList:': 'doAddToList',
|
|
'deleteLine:ofList:': 'doDeleteFromList',
|
|
'insert:at:ofList:': 'doInsertInList',
|
|
'setLine:ofList:to:': 'doReplaceInList',
|
|
'getLine:ofList:': 'reportListItem',
|
|
'lineCountOfList:': 'reportListLength',
|
|
'list:contains:': 'reportListContainsItem',
|
|
// 'contentsOfList:': '',
|
|
// 'copyOfList:': ''
|
|
|
|
// Kludge
|
|
_warp: 'doWarp'
|
|
};
|
|
|
|
function escapeXML(string) {
|
|
// over-cautious due to some morphic bugs
|
|
return string.replace(/&/g, '&')/*.replace(/[\n\r]|\r\n/g, ' ')*/.replace(/</g, '<').replace(/"/g, '"');
|
|
}
|
|
function costumeIndex(object) {
|
|
return object.media.filter(function (m) { return m.className === 'ImageMedia' }).indexOf(object.costume) + 1;
|
|
}
|
|
|
|
function Text(value) {
|
|
this.value = '' + value;
|
|
}
|
|
Text.prototype.toXML = function (string) {
|
|
string.push(escapeXML(this.value));
|
|
};
|
|
Text.prototype.toDOM = function () {
|
|
return document.createTextNode(this.value);
|
|
};
|
|
|
|
function Element(tagName) {
|
|
this.tagName = tagName;
|
|
}
|
|
Element.prototype.toXML = function (string) {
|
|
var attributes = this.attributes, key;
|
|
string.push('<', this.tagName);
|
|
for (key in attributes) if (attributes.hasOwnProperty(key)) {
|
|
string.push(' ', key, '="', escapeXML('' + attributes[key]), '"');
|
|
}
|
|
if (this.children.length) {
|
|
string.push('>');
|
|
this.children.forEach(function (child) {
|
|
if (!child.toXML) {
|
|
console.error('Invalid', child);
|
|
}
|
|
child.toXML(string);
|
|
});
|
|
string.push('</', this.tagName, '>');
|
|
} else {
|
|
string.push('/>');
|
|
}
|
|
};
|
|
Element.prototype.toDOM = function () {
|
|
var el = document.createElement(this.tagName),
|
|
attributes = this.attributes, key,
|
|
children = this.children, i = 0, child;
|
|
for (key in attributes) if (attributes.hasOwnProperty(key)) {
|
|
el.setAttribute(key, attributes[key]);
|
|
}
|
|
while (child = children[i++]) {
|
|
el.appendChild(child.toDOM());
|
|
}
|
|
return el;
|
|
};
|
|
|
|
function n(tagName, attributes, children) {
|
|
var el = new Element(tagName), key;
|
|
el.attributes = attributes || {};
|
|
el.children = children || [];
|
|
return el;
|
|
}
|
|
function t(value) {
|
|
return new Text(value);
|
|
}
|
|
|
|
p.write = function (projectName, project) {
|
|
var a, xml, out, objectID = 0, customBlocks = {};
|
|
function id(n) {
|
|
return n.attributes.id ? n('ref', { id: n.attributes.id }) : n.attributes.id = ++objectID, n;
|
|
}
|
|
function preCustomBlocks(scope, blocks) {
|
|
var sc = customBlocks[scope] = {};
|
|
if (!blocks) return;
|
|
blocks.forEach(function (ypr) {
|
|
sc[ypr.userSpec] = ypr;
|
|
ypr.userSpec = ypr.userSpec.replace(/^\s+|\s+$/g, '').replace(/\s{2,}/g, ' ').replace(/'/g, '');
|
|
ypr.__blockSpec__ = ypr.userSpec.replace(/%(\S+)/g, function (_, name) {
|
|
var type = customBlockInputs[ypr.declarations.get(name)];
|
|
if (type === undefined) {
|
|
if (ypr.declarations.get(name) === undefined) {
|
|
type = '%s';
|
|
} else {
|
|
console.warn('Missing input type ' + ypr.declarations.get(name));
|
|
}
|
|
}
|
|
return type;
|
|
});
|
|
ypr.__types__ = (ypr.userSpec.match(/%\S+/g) || []).map(function (arg) {
|
|
return ypr.declarations.get(arg.substr(1)) || 'any';
|
|
});
|
|
});
|
|
}
|
|
function nsCustomBlocks(blocks, globals) {
|
|
if (!blocks) return [];
|
|
return (globals ? blocks : blocks.filter(function (b) { return !b.isGlobal })).map(function (ypr) {
|
|
var args = (ypr.userSpec.match(/%\S+/g) || []).map(function (arg) {
|
|
var name = arg.substr(1),
|
|
type = customBlockInputs[ypr.declarations.get(name)];
|
|
if (type === undefined) {
|
|
type = '%s';
|
|
}
|
|
return n('input', {
|
|
type: type
|
|
}, [t(ypr.defaults.get(name))]);
|
|
}),
|
|
spec = ypr.userSpec.replace(/%(\S+)/g, '%\'$1\'');
|
|
ypr.body = ypr.body || [];
|
|
if (ypr.answer && ypr.answer.pop) {
|
|
ypr.body.push(['byob', '', 'doAnswer', ypr.answer[0]]);
|
|
}
|
|
if (ypr.blockVars.length) {
|
|
ypr.body.unshift(['byob', '', 'doDeclareVariables'].concat(ypr.blockVars.map(function (n) {
|
|
return [0, 0, 0, n];
|
|
})));
|
|
}
|
|
if (ypr.isAtomic) {
|
|
ypr.body = [['byob', '', '_warp', ypr.body]];
|
|
}
|
|
return n('block-definition', {
|
|
s: spec,
|
|
// type: ypr.isBoolean ? 'predicate' : ypr.isReporter ? 'reporter' : 'command',
|
|
type: ypr.type === 'boolean' ? 'predicate' : ypr.answer !== null ? 'reporter' : 'command',
|
|
category:
|
|
ypr.category === 'none' ? 'other' :
|
|
ypr.category === 'list' ? 'lists' : ypr.category
|
|
}, [
|
|
n('inputs', {}, args),
|
|
n('script', {}, nsScript(ypr.body))
|
|
]);
|
|
});
|
|
}
|
|
function nsScript(script) {
|
|
return script.map(function (block) {
|
|
return nBlock(block);
|
|
});
|
|
}
|
|
function nBlock(block) {
|
|
var node, a;
|
|
node = n('block');
|
|
switch (block[2]) {
|
|
case 'EventHatMorph':
|
|
if (block[3] === 'Scratch-StartClicked') {
|
|
node.attributes.s = 'receiveGo';
|
|
} else {
|
|
node.attributes.s = 'receiveMessage';
|
|
node.children.push(n('l', {}, [t(block[3])]));
|
|
}
|
|
return node;
|
|
case 'MouseClickEventHatMorph':
|
|
node.attributes.s = 'receiveClick';
|
|
return node;
|
|
case 'KeyEventHatMorph':
|
|
node.attributes.s = 'receiveKey';
|
|
node.children.push(n('l', {}, [t(block[3])]));
|
|
return node;
|
|
case 'doRunBlockWithArgs':
|
|
case 'doCallBlockWithArgs':
|
|
case 'doForkBlockWithArgs':
|
|
a = block.length - 5;
|
|
while (a--) {
|
|
if (block[5 + a].pop) {
|
|
block[5 + a] = ['block', '', [block[5 + a]]];
|
|
}
|
|
}
|
|
a = {
|
|
className: 'ScratchListMorph',
|
|
complex: block.slice(5)
|
|
};
|
|
block = block.slice(0, 4);
|
|
block.push(a);
|
|
break;
|
|
case 'doRunBlockWithArgList':
|
|
case 'doCallBlockWithArgList':
|
|
case 'doForkBlockWithArgList':
|
|
a = block[5];
|
|
block = block.slice(0, 4);
|
|
block.push(a);
|
|
break;
|
|
case 'doDeclareVariables':
|
|
block = ['byob', '', 'doDeclareVariables', {
|
|
className: 'ScratchListMorph',
|
|
complex: block.slice(3).map(function (block) {
|
|
return block[3];
|
|
})
|
|
}];
|
|
break;
|
|
case 'getLine:ofList:':
|
|
case 'append:toList:':
|
|
case 'deleteLine:ofList:':
|
|
case 'setLine:ofList:to:':
|
|
case 'getLine:ofList:':
|
|
case 'insert:at:ofList:':
|
|
case 'lineCountOfList:':
|
|
case 'list:contains:':
|
|
a = block[2] === 'insert:at:ofList:' ? 5 :
|
|
block[2] === 'lineCountOfList:' || block[2] === 'list:contains:' ? 3 : 4;
|
|
if (typeof block[a] === 'string') {
|
|
block[a] = block[a] === '' ? null : ['byob', '', 'readVariable', block[a]];
|
|
}
|
|
break;
|
|
case 'readBlockVariable':
|
|
case 'readVariable':
|
|
case 'listNamed:':
|
|
node.attributes['var'] = block[3];
|
|
return node;
|
|
case 'function':
|
|
case 'functionWithArgs':
|
|
node.attributes.s = 'reifyReporter';
|
|
block.pop();
|
|
node.children.push(n('autolambda', {}, [nBlock(block[8])]));
|
|
if (block[2] === 'functionWithArgs') {
|
|
node.children.push(n('list', {}, block.slice(9).map(function (arg) {
|
|
return n('l', {}, [t(arg[3])]);
|
|
})));
|
|
}
|
|
return node;
|
|
case 'procedure':
|
|
case 'procedureWithArgs':
|
|
node.attributes.s = 'reifyScript';
|
|
node.children.push(n('script', {}, nsScript(block.pop())));
|
|
node.children.push(n('list', {}, block.slice(8).map(function (arg) {
|
|
return n('l', {}, [t(arg[3])]);
|
|
})));
|
|
return node;
|
|
case 'autoBlock':
|
|
case 'autoPredicate':
|
|
node.attributes.s = block[2] === 'autoPredicate' ? 'reifyPredicate' : 'reifyReporter';
|
|
node.children.push(n('autolambda', {}, block[3] ? [nBlock(block[3][0])] : []));
|
|
return node;
|
|
case 'concatenate:with:':
|
|
block = block.slice(0, 3).concat({
|
|
className: 'ScratchListMorph',
|
|
complex: block.slice(3)
|
|
});
|
|
a = 2;
|
|
while (a--) {
|
|
if (block[3].complex[a].pop) {
|
|
block[3].complex[a] = ['block', '', [block[3].complex[a]]];
|
|
}
|
|
}
|
|
break;
|
|
case 'loopLambda':
|
|
return n('script', {}, block[8] ? nsScript(block[8]) : []);
|
|
case 'autoLambda':
|
|
// node.attributes.s = block[2] === 'loopLambda' ? 'reifyScript' : 'reifyReporter';
|
|
node.attributes.s = 'reifyScript';
|
|
node.children.push(n('script', {}, block[8] ? nsScript(block[8]) : []));
|
|
return node;
|
|
case 'changeVariable':
|
|
case 'changeBlockVariable':
|
|
block = [0, 0, block[4], block[3], block[block[2] === 'changeBlockVariable' ? 6 : 5]];
|
|
break;
|
|
case 'distanceTo:':
|
|
case 'gotoSpriteOrMouse:':
|
|
case 'pointTowards:':
|
|
case 'touching:':
|
|
if (block[3] === 'mouse') {
|
|
block[3] = 'mouse-pointer';
|
|
} else {
|
|
block[3] = block[3].objName;
|
|
}
|
|
break;
|
|
case 'doForeverIf':
|
|
block = [0, 0, 'doForever', [[0, 0, 'doIf', block[3], block[4]]]]
|
|
break;
|
|
case '\\\\':
|
|
case '+':
|
|
case '-':
|
|
case '*':
|
|
case '/':
|
|
if (block[3] === ' ') {
|
|
block[3] = '';
|
|
}
|
|
if (block[4] === ' ') {
|
|
block[4] = '';
|
|
}
|
|
break;
|
|
case 'setPenHueTo:':
|
|
case 'changePenHueBy:':
|
|
block[3] = ['byob', 0, '/', block[3], '2'];
|
|
break;
|
|
}
|
|
if (block[2] === 'doCustomBlock') {
|
|
a = customBlocks[block[1] === 'Stage' ? '__global__' : block[1]][block[3]];
|
|
node = n('custom-block', {
|
|
s: a.__blockSpec__
|
|
});
|
|
if (block[1] !== 'Stage' && !a.isGlobal) {
|
|
node.attributes.scope = block[1];
|
|
}
|
|
a.__types__.forEach(function (type, i) {
|
|
if (type === 'template') {
|
|
block[4 + i] = block[4 + i][3];
|
|
}
|
|
});
|
|
block.shift();
|
|
} else {
|
|
if ((node.attributes.s = blockSelectors[block[2]]) === undefined) {
|
|
console.warn('Missing selector mapping for "' + block[2] + '"', block);
|
|
}
|
|
}
|
|
block.slice(3).forEach(function (arg) {
|
|
if (arg && arg.pop) {
|
|
if (typeof arg[0] === 'string') {
|
|
node.children.push(nBlock(arg));
|
|
} else {
|
|
node.children.push(n('script', {}, nsScript(arg)));
|
|
}
|
|
} else {
|
|
a = nValue(arg);
|
|
delete a.attributes.id;
|
|
node.children.push(a);
|
|
}
|
|
});
|
|
return node;
|
|
}
|
|
function nsVariables(vars, lists) {
|
|
return vars.keys.map(function (key, i) {
|
|
return n('variable', { name: key }, [nValue(vars.values[i], true)]);
|
|
}).concat(lists.keys.map(function (key, i) {
|
|
return n('variable', { name: key }, [nValue(lists.values[i], true)]);
|
|
}));
|
|
}
|
|
function nValue(value, listHasItems) {
|
|
return typeof value === 'string' || typeof value === 'number' ? n('l', {}, [t(value)]) :
|
|
value == null || typeof value === 'boolean' ? n('l') :
|
|
value.className === 'ScratchListMorph' ? id(n('list', {}, value.complex.map(function (object, i) {
|
|
var item = object == null ? n('l', {}, [t((item = value.strings[i]) === 'nil' ? '' : item)]) :
|
|
typeof object === 'string' ? nValue(object) :
|
|
object && object[0] === 'block' ? nsScript(object[2])[0] : n('l');
|
|
return listHasItems ? n('item', {}, [item]) : item;
|
|
}))) :
|
|
// (function () { throw 'Unimplemented' })();
|
|
value.constructor === sb.Color ? n('color', {}, [t([value.r * 255, value.g * 255, value.b * 255, value.a * 255])]) :
|
|
value.constructor === sb.Dictionary ? id(n('list')) :
|
|
(function () { console.warn('No serializer for', value); return n('__undefined__') })();
|
|
}
|
|
function nsCostumes(media) {
|
|
return [id(n('list', {}, media.filter(function (m) { return m.className === 'ImageMedia' }).map(function (c) {
|
|
return n('item', {}, [
|
|
n('costume', {
|
|
name: c.mediaName,
|
|
'center-x': c.rotationCenter.x,
|
|
'center-y': c.rotationCenter.y,
|
|
image: c.form.image.toDataURL()
|
|
})
|
|
]);
|
|
})))];
|
|
}
|
|
function nsSounds(media) {
|
|
if (media.filter(function (m) { return m.className === 'SoundMedia' }).length > 0) {
|
|
console.warn('Sounds are unimplemented');
|
|
}
|
|
return [id(n('list'))];
|
|
}
|
|
function nsScripts(scripts) {
|
|
return scripts.map(function (script) {
|
|
if (script[1][0] && script[1][0][2] === 'scratchComment') {
|
|
return n('comment', {
|
|
x: script[0].x,
|
|
y: script[0].y,
|
|
w: script[1][0][5],
|
|
collapsed: !script[1][0][4]
|
|
}, [t(script[1][0][3])]);
|
|
}
|
|
return n('script', {
|
|
x: script[0].x,
|
|
y: script[0].y
|
|
}, nsScript(script[1]));
|
|
});
|
|
}
|
|
function nsSprites(sprites, vars, lists) {
|
|
return sprites.map(function (sprite, i) {
|
|
return id(n('sprite', {
|
|
name: sprite.objName,
|
|
x: (sprite.bounds.origin.x + sprite.bounds.corner.x) / 2 - 240,
|
|
y: 180 - (sprite.bounds.origin.y + sprite.bounds.corner.y) / 2,
|
|
hidden: sprite.flags === 1,
|
|
heading: sprite.rotationDegrees + 90,
|
|
scale: sprite.scalePoint.x,
|
|
rotation: rotationStyles[sprite.rotationStyle],
|
|
draggable: sprite.draggable,
|
|
costume: costumeIndex(sprite),
|
|
color: (sprite.color.r * 255 | 0) + ',' + (sprite.color.g * 255 | 0) + ',' + (sprite.color.b * 255 | 0),
|
|
pen: 'tip',
|
|
idx: i
|
|
}, [
|
|
n('variables', {}, nsVariables(sprite.vars, sprite.lists)),
|
|
n('costumes', {}, nsCostumes(sprite.media)),
|
|
n('sounds', {}, nsSounds(sprite.media)),
|
|
n('blocks', {}, nsCustomBlocks(sprite.customBlocks, false)),
|
|
n('scripts', {}, nsScripts(sprite.blocksBin))
|
|
]));
|
|
}).concat(vars.keys.concat(lists.keys).map(function (name) {
|
|
return n('watcher', {
|
|
'var': name,
|
|
style: 'normal',
|
|
x: 10,
|
|
y: 10,
|
|
color: '243,118,29',
|
|
hidden: true
|
|
});
|
|
}));
|
|
}
|
|
|
|
console.log(project);
|
|
preCustomBlocks('__global__', project.stage.customBlocks);
|
|
project.stage.sprites.forEach(function (sprite) {
|
|
preCustomBlocks(sprite.objName, sprite.customBlocks);
|
|
});
|
|
xml = n('project', {
|
|
name: projectName,
|
|
app: 'Snap! 4.0, http://snap.berkeley.edu; serialized by yprxml, http://dl.dropbox.com/u/10715865/Web/snap/app/yprxml.html',
|
|
version: '1'
|
|
}, [
|
|
n('notes', {}, [t(project.info.get('comment') || '')]),
|
|
n('thumbnail', {}, [t((a = project.info.get('thumbnail')) ? a.image.toDataURL() : '')]),
|
|
id(n('stage', {
|
|
name: 'Stage',
|
|
tempo: project.stage.tempoBPM,
|
|
costume: costumeIndex(project.stage),
|
|
threadsafe: 'false',
|
|
scheduled: true
|
|
}, [
|
|
n('pentrails', {}, [t((a = project.info.get('penTrails')) ? a.image.toDataURL() : '')]),
|
|
n('costumes', {}, nsCostumes(project.stage.media)),
|
|
n('sounds', {}, nsSounds(project.stage.media)),
|
|
n('blocks', {}, []),
|
|
n('variables', {}, []),
|
|
n('scripts', {}, nsScripts(project.stage.blocksBin)),
|
|
n('sprites', {}, nsSprites(project.stage.sprites, project.stage.vars, project.stage.lists))
|
|
])),
|
|
n('variables', {}, nsVariables(project.stage.vars, project.stage.lists)),
|
|
n('blocks', {}, nsCustomBlocks(project.stage.customBlocks, true))
|
|
]);
|
|
xml.toXML(out = []);
|
|
// var d = document.createElement('textarea');
|
|
// d.style.position = 'absolute';
|
|
// document.body.appendChild(d).value = out.join('');
|
|
return out.join('');
|
|
};
|
|
|
|
})(sb.XMLWriter = function () {}, sb.XMLWriter.prototype);
|
|
|
|
return sb;
|
|
|
|
})({});
|