(function () { 'use strict'; /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ // Create Squeak VM namespace if (!self.Squeak) self.Squeak = {}; // Setup a storage for settings if (!Squeak.Settings) { // Try (a working) localStorage and fall back to regular dictionary otherwise var settings; try { // fails in restricted iframe settings = self.localStorage; settings["squeak-foo:"] = "bar"; if (settings["squeak-foo:"] !== "bar") throw Error(); delete settings["squeak-foo:"]; } catch(e) { settings = {}; } Squeak.Settings = settings; } if (!Object.extend) { // Extend object by adding specified properties Object.extend = function(obj /* + more args */ ) { // skip arg 0, copy properties of other args to obj for (var i = 1; i < arguments.length; i++) if (typeof arguments[i] == 'object') for (var name in arguments[i]) obj[name] = arguments[i][name]; }; } // This mimics the Lively Kernel's subclassing scheme. // When running there, Lively's subclasses and modules are used. // Modules serve as namespaces in Lively. SqueakJS uses a flat namespace // named "Squeak", but the code below still supports hierarchical names. if (!Function.prototype.subclass) { // Create subclass using specified class path and given properties Function.prototype.subclass = function(classPath /* + more args */ ) { // create subclass var subclass = function() { if (this.initialize) { var result = this.initialize.apply(this, arguments); if (result !== undefined) return result; } return this; }; // set up prototype var protoclass = function() { }; protoclass.prototype = this.prototype; subclass.prototype = new protoclass(); // skip arg 0, copy properties of other args to prototype for (var i = 1; i < arguments.length; i++) Object.extend(subclass.prototype, arguments[i]); // add class to namespace var path = classPath.split("."), className = path.pop(), // Walk path starting at the global namespace (self) // creating intermediate namespaces if necessary namespace = path.reduce(function(namespace, path) { if (!namespace[path]) namespace[path] = {}; return namespace[path]; }, self); namespace[className] = subclass; return subclass; }; } /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, "version", { // system attributes vmVersion: "SqueakJS 1.3.3", vmDate: "2025-06-03", // Maybe replace at build time? vmBuild: "unknown", // this too? vmPath: "unknown", // Replaced at runtime vmFile: "vm.js", vmMakerVersion: "[VMMakerJS-bf.17 VMMaker-bf.353]", // for Smalltalk vmVMMakerVersion vmInterpreterVersion: "JSInterpreter VMMaker.js-codefrau.1", // for Smalltalk interpreterVMMakerVersion platformName: "JS", platformSubtype: "unknown", // Replaced at runtime osVersion: "unknown", // Replaced at runtime windowSystem: "unknown", // Replaced at runtime defaultCORSProxy: "https://cors.codefrau.workers.dev/", }, "object header", { // object headers HeaderTypeMask: 3, HeaderTypeSizeAndClass: 0, //3-word header HeaderTypeClass: 1, //2-word header HeaderTypeFree: 2, //free block HeaderTypeShort: 3, //1-word header }, "special objects", { // Indices into SpecialObjects array splOb_NilObject: 0, splOb_FalseObject: 1, splOb_TrueObject: 2, splOb_SchedulerAssociation: 3, splOb_ClassBitmap: 4, splOb_ClassInteger: 5, splOb_ClassString: 6, splOb_ClassArray: 7, splOb_SmalltalkDictionary: 8, splOb_ClassFloat: 9, splOb_ClassMethodContext: 10, splOb_ClassBlockContext: 11, splOb_ClassPoint: 12, splOb_ClassLargePositiveInteger: 13, splOb_TheDisplay: 14, splOb_ClassMessage: 15, splOb_ClassCompiledMethod: 16, splOb_TheLowSpaceSemaphore: 17, splOb_ClassSemaphore: 18, splOb_ClassCharacter: 19, splOb_SelectorDoesNotUnderstand: 20, splOb_SelectorCannotReturn: 21, // splOb_TheInputSemaphore: 22, // old? unused in SqueakJS splOb_ProcessSignalingLowSpace: 22, splOb_SpecialSelectors: 23, splOb_CharacterTable: 24, splOb_SelectorMustBeBoolean: 25, splOb_ClassByteArray: 26, splOb_ClassProcess: 27, splOb_CompactClasses: 28, splOb_TheTimerSemaphore: 29, splOb_TheInterruptSemaphore: 30, splOb_FloatProto: 31, splOb_SelectorCannotInterpret: 34, splOb_MethodContextProto: 35, splOb_ClassBlockClosure: 36, splOb_ClassFullBlockClosure: 37, splOb_ExternalObjectsArray: 38, splOb_ClassPseudoContext: 39, splOb_ClassTranslatedMethod: 40, splOb_TheFinalizationSemaphore: 41, splOb_ClassLargeNegativeInteger: 42, splOb_ClassExternalAddress: 43, splOb_ClassExternalStructure: 44, splOb_ClassExternalData: 45, splOb_ClassExternalFunction: 46, splOb_ClassExternalLibrary: 47, splOb_SelectorAboutToReturn: 48, splOb_SelectorRunWithIn: 49, splOb_SelectorAttemptToAssign: 50, splOb_PrimErrTableIndex: 51, splOb_ClassAlien: 52, splOb_InvokeCallbackSelector: 53, splOb_ClassUnsafeAlien: 54, splOb_ClassWeakFinalizer: 55, }, "known classes", { // AdditionalMethodState layout: AdditionalMethodState_selector: 1, // Class layout: Class_superclass: 0, Class_mdict: 1, Class_format: 2, Class_instVars: null, // 3 or 4 depending on image, see instVarNames() Class_name: 6, // ClassBinding layout: ClassBinding_value: 1, // Context layout: Context_sender: 0, Context_instructionPointer: 1, Context_stackPointer: 2, Context_method: 3, Context_closure: 4, Context_receiver: 5, Context_tempFrameStart: 6, Context_smallFrameSize: 16, Context_largeFrameSize: 56, BlockContext_caller: 0, BlockContext_argumentCount: 3, BlockContext_initialIP: 4, BlockContext_home: 5, // Closure layout: Closure_outerContext: 0, Closure_startpc: 1, Closure_numArgs: 2, Closure_firstCopiedValue: 3, ClosureFull_method: 1, ClosureFull_receiver: 3, ClosureFull_firstCopiedValue: 4, // Stream layout: Stream_array: 0, Stream_position: 1, Stream_limit: 2, //ProcessorScheduler layout: ProcSched_processLists: 0, ProcSched_activeProcess: 1, //Link layout: Link_nextLink: 0, //LinkedList layout: LinkedList_firstLink: 0, LinkedList_lastLink: 1, //Semaphore layout: Semaphore_excessSignals: 2, //Mutex layout: Mutex_owner: 2, //Process layout: Proc_suspendedContext: 1, Proc_priority: 2, Proc_myList: 3, // Association layout: Assn_key: 0, Assn_value: 1, // MethodDict layout: MethodDict_array: 1, MethodDict_selectorStart: 2, // Message layout Message_selector: 0, Message_arguments: 1, Message_lookupClass: 2, // Point layout: Point_x: 0, Point_y: 1, // LargeInteger layout: LargeInteger_bytes: 0, LargeInteger_neg: 1, // WeakFinalizationList layout: WeakFinalizationList_first: 0, // WeakFinalizerItem layout: WeakFinalizerItem_list: 0, WeakFinalizerItem_next: 1, }, "constants", { MinSmallInt: -1073741824, MaxSmallInt: 0x3FFFFFFF, NonSmallInt: -1342177280, // non-small and neg (so non pos32 too) MillisecondClockMask: 0x1FFFFFFF, }, "error codes", { PrimNoErr: 0, PrimErrGenericFailure: 1, PrimErrBadReceiver: 2, PrimErrBadArgument: 3, PrimErrBadIndex: 4, PrimErrBadNumArgs: 5, PrimErrInappropriate: 6, PrimErrUnsupported: 7, PrimErrNoModification: 8, PrimErrNoMemory: 9, PrimErrNoCMemory: 10, PrimErrNotFound: 11, PrimErrBadMethod: 12, PrimErrNamedInternal: 13, PrimErrObjectMayMove: 14, PrimErrLimitExceeded: 15, PrimErrObjectIsPinned: 16, PrimErrWritePastObject: 17, }, "modules", { // don't clobber registered modules externalModules: Squeak.externalModules || {}, registerExternalModule: function(name, module) { this.externalModules[name] = module; }, }, "time", { Epoch: Date.UTC(1901,0,1) + (new Date()).getTimezoneOffset()*60000, // local timezone EpochUTC: Date.UTC(1901,0,1), totalSeconds: function() { // seconds since 1901-01-01, local time return Math.floor((Date.now() - Squeak.Epoch) / 1000); }, }, "utils", { bytesAsString: function(bytes) { var chars = []; for (var i = 0; i < bytes.length; ) chars.push(String.fromCharCode.apply( null, bytes.subarray(i, i += 16348))); return chars.join(''); }, word64FromUint32: function(hi, lo) { // Max safe integer as Uint64: 001FFFFF_FFFFFFFF // Min safe integer as Uint64: FFE00000_00000001 if (hi < 0x00200000) { // positive, <= 53 bits return hi * 0x100000000 + lo; } else if (hi > 0xFFE00000) { // negative, <= 53 bits return (hi>>0) * 0x100000000 + lo; } else return [hi, lo]; // probably SmallFloat }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.Object', 'initialization', { initInstanceOf: function(aClass, indexableSize, hash, nilObj) { this.sqClass = aClass; this.hash = hash; var instSpec = aClass.pointers[Squeak.Class_format], instSize = ((instSpec>>1) & 0x3F) + ((instSpec>>10) & 0xC0) - 1; //0-255 this._format = (instSpec>>7) & 0xF; //This is the 0-15 code if (this._format < 8) { if (this._format != 6) { if (instSize + indexableSize > 0) this.pointers = this.fillArray(instSize + indexableSize, nilObj); } else // Words if (indexableSize > 0) if (aClass.isFloatClass) { this.isFloat = true; this.float = 0.0; } else this.words = new Uint32Array(indexableSize); } else // Bytes if (indexableSize > 0) { // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(indexableSize); //Methods require further init of pointers } // Definition of Squeak's format code... // // Pointers only... // 0 no fields // 1 fixed fields only (all containing pointers) // 2 indexable fields only (all containing pointers) // 3 both fixed and indexable fields (all containing pointers) // 4 both fixed and indexable weak fields (all containing pointers). // 5 unused // Bits only... // 6 indexable word fields only (no pointers) // 7 unused // 8-11 indexable byte fields only (no pointers) (low 2 bits are low 2 bits of size) // Pointer and bits (CompiledMethods only)... // 12-15 compiled methods: // # of literal oops specified in method header, // followed by indexable bytes (same interpretation of low 2 bits as above) }, initAsClone: function(original, hash) { this.sqClass = original.sqClass; this.hash = hash; this._format = original._format; if (original.isFloat) { this.isFloat = original.isFloat; this.float = original.float; } else { if (original.pointers) this.pointers = original.pointers.slice(0); // copy if (original.words) this.words = new Uint32Array(original.words); // copy if (original.bytes) this.bytes = new Uint8Array(original.bytes); // copy } }, initFromImage: function(oop, cls, fmt, hsh) { // initial creation from Image, with unmapped data this.oop = oop; this.sqClass = cls; this._format = fmt; this.hash = hsh; }, classNameFromImage: function(oopMap, rawBits) { var name = oopMap.get(rawBits.get(this.oop)[Squeak.Class_name]); if (name && name._format >= 8 && name._format < 12) { var bits = rawBits.get(name.oop), bytes = name.decodeBytes(bits.length, bits, 0, name._format & 3); return Squeak.bytesAsString(bytes); } return "Class"; }, renameFromImage: function(oopMap, rawBits, ccArray) { var classObj = this.sqClass < 32 ? oopMap.get(ccArray[this.sqClass-1]) : oopMap.get(this.sqClass); if (!classObj) return this; var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromImage(oopMap, rawBits)); if (!instProto) return this; var renamedObj = new instProto; // Squeak.Object renamedObj.oop = this.oop; renamedObj.sqClass = this.sqClass; renamedObj._format = this._format; renamedObj.hash = this.hash; return renamedObj; }, installFromImage: function(oopMap, rawBits, ccArray, floatClass, littleEndian, nativeFloats) { //Install this object by decoding format, and rectifying pointers var ccInt = this.sqClass; // map compact classes if ((ccInt>0) && (ccInt<32)) this.sqClass = oopMap.get(ccArray[ccInt-1]); else this.sqClass = oopMap.get(ccInt); var bits = rawBits.get(this.oop), nWords = bits.length; if (this._format < 5) { //Formats 0...4 -- Pointer fields if (nWords > 0) { var oops = bits; // endian conversion was already done this.pointers = this.decodePointers(nWords, oops, oopMap); } } else if (this._format >= 12) { //Formats 12-15 -- CompiledMethods both pointers and bits var methodHeader = this.decodeWords(1, bits, littleEndian)[0], numLits = (methodHeader>>10) & 255, oops = this.decodeWords(numLits+1, bits, littleEndian); this.pointers = this.decodePointers(numLits+1, oops, oopMap); //header+lits this.bytes = this.decodeBytes(nWords-(numLits+1), bits, numLits+1, this._format & 3); } else if (this._format >= 8) { //Formats 8..11 -- ByteArrays (and ByteStrings) if (nWords > 0) this.bytes = this.decodeBytes(nWords, bits, 0, this._format & 3); } else if (this.sqClass == floatClass) { //These words are actually a Float this.isFloat = true; this.float = this.decodeFloat(bits, littleEndian, nativeFloats); } else { if (nWords > 0) this.words = this.decodeWords(nWords, bits, littleEndian); } this.mark = false; // for GC }, decodePointers: function(nWords, theBits, oopMap) { //Convert small ints and look up object pointers in oopMap var ptrs = new Array(nWords); for (var i = 0; i < nWords; i++) { var oop = theBits[i]; if ((oop & 1) === 1) { // SmallInteger ptrs[i] = oop >> 1; } else { // Object ptrs[i] = oopMap.get(oop) || 42424242; // when loading a context from image segment, there is // garbage beyond its stack pointer, resulting in the oop // not being found in oopMap. We just fill in an arbitrary // SmallInteger - it's never accessed anyway } } return ptrs; }, decodeWords: function(nWords, theBits, littleEndian) { var data = new DataView(theBits.buffer, theBits.byteOffset), words = new Uint32Array(nWords); for (var i = 0; i < nWords; i++) words[i] = data.getUint32(i*4, littleEndian); return words; }, decodeBytes: function (nWords, theBits, wordOffset, fmtLowBits) { // Adjust size for low bits and make a copy var nBytes = (nWords * 4) - fmtLowBits, wordsAsBytes = new Uint8Array(theBits.buffer, theBits.byteOffset + wordOffset * 4, nBytes), bytes = new Uint8Array(nBytes); bytes.set(wordsAsBytes); return bytes; }, decodeFloat: function(theBits, littleEndian, nativeFloats) { var data = new DataView(theBits.buffer, theBits.byteOffset); // it's either big endian ... if (!littleEndian) return data.getFloat64(0, false); // or real little endian if (nativeFloats) return data.getFloat64(0, true); // or little endian, but with swapped words var buffer = new ArrayBuffer(8), swapped = new DataView(buffer); swapped.setUint32(0, data.getUint32(4)); swapped.setUint32(4, data.getUint32(0)); return swapped.getFloat64(0, true); }, fillArray: function(length, filler) { for (var array = [], i = 0; i < length; i++) array[i] = filler; return array; }, }, 'testing', { isWords: function() { return this._format === 6; }, isBytes: function() { var fmt = this._format; return fmt >= 8 && fmt <= 11; }, isWordsOrBytes: function() { var fmt = this._format; return fmt == 6 || (fmt >= 8 && fmt <= 11); }, isPointers: function() { return this._format <= 4; }, isWeak: function() { return this._format === 4; }, isMethod: function() { return this._format >= 12; }, sameFormats: function(a, b) { return a < 8 ? a === b : (a & 0xC) === (b & 0xC); }, sameFormatAs: function(obj) { return this.sameFormats(this._format, obj._format); }, }, 'printing', { toString: function() { return this.sqInstName(); }, bytesAsString: function() { if (!this.bytes) return ''; return Squeak.bytesAsString(this.bytes); }, bytesAsNumberString: function(negative) { if (!this.bytes) return ''; var hex = '0123456789ABCDEF', digits = [], value = 0; for (var i = this.bytes.length - 1; i >= 0; i--) { digits.push(hex[this.bytes[i] >> 4]); digits.push(hex[this.bytes[i] & 15]); value = value * 256 + this.bytes[i]; } var sign = negative ? '-' : '', approx = value > 0x1FFFFFFFFFFFFF ? '≈' : ''; return sign + '16r' + digits.join('') + ' (' + approx + sign + value + 'L)'; }, assnKeyAsString: function() { return this.pointers[Squeak.Assn_key].bytesAsString(); }, slotNameAt: function(index) { // one-based index var instSize = this.instSize(); if (index <= instSize) return this.sqClass.allInstVarNames()[index - 1] || 'ivar' + (index - 1); else return (index - instSize).toString(); }, sqInstName: function() { if (this.isNil) return "nil"; if (this.isTrue) return "true"; if (this.isFalse) return "false"; if (this.isFloat) {var str = this.float.toString(); if (!/\./.test(str)) str += '.0'; return str; } var className = this.sqClass.className(); if (/ /.test(className)) return 'the ' + className; switch (className) { case 'String': case 'ByteString': return "'" + this.bytesAsString() + "'"; case 'Symbol': case 'ByteSymbol': return "#" + this.bytesAsString(); case 'Point': return this.pointers.join("@"); case 'Rectangle': return this.pointers.join(" corner: "); case 'Association': case 'ReadOnlyVariableBinding': return this.pointers.join("->"); case 'LargePositiveInteger': return this.bytesAsNumberString(false); case 'LargeNegativeInteger': return this.bytesAsNumberString(true); case 'Character': var unicode = this.pointers ? this.pointers[0] : this.hash; // Spur return "$" + String.fromCharCode(unicode) + " (" + unicode.toString() + ")"; case 'CompiledMethod': return this.methodAsString(); case 'CompiledBlock': return "[] in " + this.blockOuterCode().sqInstName(); } return /^[aeiou]/i.test(className) ? 'an' + className : 'a' + className; }, }, 'accessing', { pointersSize: function() { return this.pointers ? this.pointers.length : 0; }, bytesSize: function() { return this.bytes ? this.bytes.length : 0; }, wordsSize: function() { return this.isFloat ? 2 : this.words ? this.words.length : 0; }, instSize: function() {//same as class.classInstSize, but faster from format var fmt = this._format; if (fmt > 4 || fmt === 2) return 0; //indexable fields only if (fmt < 2) return this.pointersSize(); //fixed fields only return this.sqClass.classInstSize(); }, indexableSize: function(primHandler) { var fmt = this._format; if (fmt < 2) return -1; //not indexable if (fmt === 3 && primHandler.vm.isContext(this) && !primHandler.allowAccessBeyondSP) return this.pointers[Squeak.Context_stackPointer]; // no access beyond top of stacks if (fmt < 6) return this.pointersSize() - this.instSize(); // pointers if (fmt < 8) return this.wordsSize(); // words if (fmt < 12) return this.bytesSize(); // bytes return this.bytesSize() + (4 * this.pointersSize()); // methods }, floatData: function() { var buffer = new ArrayBuffer(8); var data = new DataView(buffer); data.setFloat64(0, this.float, false); //1st word is data.getUint32(0, false); //2nd word is data.getUint32(4, false); return data; }, wordsAsFloat32Array: function() { return this.float32Array || (this.words && (this.float32Array = new Float32Array(this.words.buffer))); }, wordsAsFloat64Array: function() { return this.float64Array || (this.words && (this.float64Array = new Float64Array(this.words.buffer))); }, wordsAsInt32Array: function() { return this.int32Array || (this.words && (this.int32Array = new Int32Array(this.words.buffer))); }, wordsAsInt16Array: function() { return this.int16Array || (this.words && (this.int16Array = new Int16Array(this.words.buffer))); }, wordsAsUint16Array: function() { return this.uint16Array || (this.words && (this.uint16Array = new Uint16Array(this.words.buffer))); }, wordsAsUint8Array: function() { return this.uint8Array || (this.words && (this.uint8Array = new Uint8Array(this.words.buffer))); }, wordsOrBytes: function() { if (this.words) return this.words; if (this.uint32Array) return this.uint32Array; if (!this.bytes) return null; return this.uint32Array = new Uint32Array(this.bytes.buffer, 0, this.bytes.length >>> 2); }, setAddr: function(addr) { // Move this object to addr by setting its oop. Answer address after this object. // Used to assign an oop for the first time when tenuring this object during GC. // When compacting, the oop is adjusted directly, since header size does not change. var words = this.snapshotSize(); this.oop = addr + words.header * 4; return addr + (words.header + words.body) * 4; }, snapshotSize: function() { // words of extra object header and body this object would take up in image snapshot // body size includes one header word that is always present var nWords = this.isFloat ? 2 : this.words ? this.words.length : this.pointers ? this.pointers.length : 0; // methods have both pointers and bytes if (this.bytes) nWords += (this.bytes.length + 3) >>> 2; nWords++; // one header word always present var extraHeader = nWords > 63 ? 2 : this.sqClass.isCompact ? 0 : 1; return {header: extraHeader, body: nWords}; }, addr: function() { // start addr of this object in a snapshot return this.oop - this.snapshotSize().header * 4; }, totalBytes: function() { // size in bytes this object would take up in image snapshot var words = this.snapshotSize(); return (words.header + words.body) * 4; }, writeTo: function(data, pos, image) { // Write 1 to 3 header words encoding type, class, and size, then instance data if (this.bytes) this._format |= -this.bytes.length & 3; var beforePos = pos, size = this.snapshotSize(), formatAndHash = ((this._format & 15) << 8) | ((this.hash & 4095) << 17); // write header words first switch (size.header) { case 2: data.setUint32(pos, size.body << 2 | Squeak.HeaderTypeSizeAndClass); pos += 4; data.setUint32(pos, this.sqClass.oop | Squeak.HeaderTypeSizeAndClass); pos += 4; data.setUint32(pos, formatAndHash | Squeak.HeaderTypeSizeAndClass); pos += 4; break; case 1: data.setUint32(pos, this.sqClass.oop | Squeak.HeaderTypeClass); pos += 4; data.setUint32(pos, formatAndHash | size.body << 2 | Squeak.HeaderTypeClass); pos += 4; break; case 0: var classIndex = image.compactClasses.indexOf(this.sqClass) + 1; data.setUint32(pos, formatAndHash | classIndex << 12 | size.body << 2 | Squeak.HeaderTypeShort); pos += 4; } // now write body, if any if (this.isFloat) { data.setFloat64(pos, this.float); pos += 8; } else if (this.words) { for (var i = 0; i < this.words.length; i++) { data.setUint32(pos, this.words[i]); pos += 4; } } else if (this.pointers) { for (var i = 0; i < this.pointers.length; i++) { data.setUint32(pos, image.objectToOop(this.pointers[i])); pos += 4; } } // no "else" because CompiledMethods have both pointers and bytes if (this.bytes) { for (var i = 0; i < this.bytes.length; i++) data.setUint8(pos++, this.bytes[i]); // skip to next word pos += -this.bytes.length & 3; } // done if (pos !== beforePos + this.totalBytes()) throw Error("written size does not match"); return pos; }, }, 'as class', { classInstFormat: function() { return (this.pointers[Squeak.Class_format] >> 7) & 0xF; }, classInstSize: function() { // this is a class, answer number of named inst vars var spec = this.pointers[Squeak.Class_format]; return ((spec >> 10) & 0xC0) + ((spec >> 1) & 0x3F) - 1; }, classInstIsBytes: function() { var fmt = this.classInstFormat(); return fmt >= 8 && fmt <= 11; }, classInstIsPointers: function() { return this.classInstFormat() <= 4; }, instVarNames: function() { // index changed from 4 to 3 in newer images for (var index = 3; index <= 4; index++) { var varNames = this.pointers[index].pointers; if (varNames && varNames.length && varNames[0].bytes) { return varNames.map(function(each) { return each.bytesAsString(); }); } } return []; }, allInstVarNames: function() { var superclass = this.superclass(); if (superclass.isNil) return this.instVarNames(); else return superclass.allInstVarNames().concat(this.instVarNames()); }, superclass: function() { return this.pointers[0]; }, className: function() { if (!this.pointers) return "_NOTACLASS_"; for (var nameIdx = 6; nameIdx <= 7; nameIdx++) { var name = this.pointers[nameIdx]; if (name && name.bytes) return name.bytesAsString(); } // must be meta class for (var clsIndex = 5; clsIndex <= 6; clsIndex++) { var cls = this.pointers[clsIndex]; if (cls && cls.pointers) { for (var nameIdx = 6; nameIdx <= 7; nameIdx++) { var name = cls.pointers[nameIdx]; if (name && name.bytes) return name.bytesAsString() + " class"; } } } return "_SOMECLASS_"; }, defaultInst: function() { return Squeak.Object; }, classInstProto: function(className) { if (this.instProto) return this.instProto; var proto = this.defaultInst(); // in case below fails try { if (!className) className = this.className(); var safeName = className.replace(/[^A-Za-z0-9]/g,'_'); if (safeName === "UndefinedObject") safeName = "nil"; else if (safeName === "True") safeName = "true_"; else if (safeName === "False") safeName = "false_"; else safeName = ((/^[AEIOU]/.test(safeName)) ? 'an' : 'a') + safeName; // fail okay if no eval() proto = new Function("return function " + safeName + "() {};")(); proto.prototype = this.defaultInst().prototype; } catch(e) {} Object.defineProperty(this, 'instProto', { value: proto }); return proto; }, }, 'as method', { methodSignFlag: function() { return false; }, methodNumLits: function() { return (this.pointers[0]>>9) & 0xFF; }, methodNumArgs: function() { return (this.pointers[0]>>24) & 0xF; }, methodPrimitiveIndex: function() { var primBits = this.pointers[0] & 0x300001FF; if (primBits > 0x1FF) return (primBits & 0x1FF) + (primBits >> 19); else return primBits; }, methodClassForSuper: function() {//assn found in last literal var assn = this.pointers[this.methodNumLits()]; return assn.pointers[Squeak.Assn_value]; }, methodNeedsLargeFrame: function() { return (this.pointers[0] & 0x20000) > 0; }, methodAddPointers: function(headerAndLits) { this.pointers = headerAndLits; }, methodTempCount: function() { return (this.pointers[0]>>18) & 63; }, methodGetLiteral: function(zeroBasedIndex) { return this.pointers[1+zeroBasedIndex]; // step over header }, methodGetSelector: function(zeroBasedIndex) { return this.pointers[1+zeroBasedIndex]; // step over header }, methodAsString: function() { return 'aCompiledMethod'; }, }, 'as context', { contextHome: function() { return this.contextIsBlock() ? this.pointers[Squeak.BlockContext_home] : this; }, contextIsBlock: function() { return typeof this.pointers[Squeak.BlockContext_argumentCount] === 'number'; }, contextMethod: function() { return this.contextHome().pointers[Squeak.Context_method]; }, contextSender: function() { return this.pointers[Squeak.Context_sender]; }, contextSizeWithStack: function(vm) { // Actual context size is inst vars + stack size. Slots beyond that may contain garbage. // If passing in a VM, and this is the activeContext, use the VM's current value. if (vm && vm.activeContext === this) return vm.sp + 1; // following is same as decodeSqueakSP() but works without vm ref var sp = this.pointers[Squeak.Context_stackPointer]; return Squeak.Context_tempFrameStart + (typeof sp === "number" ? sp : 0); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Squeak.Object.subclass('Squeak.ObjectSpur', 'initialization', { initInstanceOf: function(aClass, indexableSize, hash, nilObj) { this.sqClass = aClass; this.hash = hash; var instSpec = aClass.pointers[Squeak.Class_format], instSize = instSpec & 0xFFFF, format = (instSpec>>16) & 0x1F; this._format = format; if (format < 12) { if (format < 10) { if (instSize + indexableSize > 0) this.pointers = this.fillArray(instSize + indexableSize, nilObj); } else // Words if (indexableSize > 0) if (aClass.isFloatClass) { this.isFloat = true; this.float = 0.0; } else this.words = new Uint32Array(indexableSize); } else // Bytes if (indexableSize > 0) { // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(indexableSize); //Methods require further init of pointers } // Definition of Spur's format code... // // 0 = 0 sized objects (UndefinedObject True False et al) // 1 = non-indexable objects with inst vars (Point et al) // 2 = indexable objects with no inst vars (Array et al) // 3 = indexable objects with inst vars (MethodContext AdditionalMethodState et al) // 4 = weak indexable objects with inst vars (WeakArray et al) // 5 = weak non-indexable objects with inst vars (ephemerons) (Ephemeron) // 6 = unused // 7 = immediates (SmallInteger, Character) // 8 = unused // 9 = 64-bit indexable // 10-11 = 32-bit indexable (Bitmap) (plus one odd bit, unused in 32-bits) // 12-15 = 16-bit indexable (plus two odd bits, one unused in 32-bits) // 16-23 = 8-bit indexable (plus three odd bits, one unused in 32-bits) // 24-31 = compiled methods (CompiledMethod) (plus three odd bits, one unused in 32-bits) }, installFromImage: function(oopMap, rawBits, classTable, floatClass, littleEndian, getCharacter, is64Bit) { //Install this object by decoding format, and rectifying pointers var classID = this.sqClass; if (classID < 32) throw Error("Invalid class ID: " + classID); this.sqClass = classTable[classID]; if (!this.sqClass) throw Error("Class ID not in class table: " + classID); var bits = rawBits.get(this.oop), nWords = bits.length; switch (this._format) { case 0: // zero sized object // Pharo bug: Pharo 6.0 still has format 0 objects that actually do have inst vars // https://pharo.fogbugz.com/f/cases/19010/ImmediateLayout-and-EphemeronLayout-have-wrong-object-format // so we pretend these are regular objects and rely on nWords case 1: // only inst vars case 2: // only indexed vars case 3: // inst vars and indexed vars case 4: // only indexed vars (weak) case 5: // only inst vars (weak) if (nWords > 0) { var oops = bits; // endian conversion was already done this.pointers = this.decodePointers(nWords, oops, oopMap, getCharacter, is64Bit); } break; case 11: // 32 bit array (odd length in 64 bits) nWords--; this._format = 10; case 10: // 32 bit array if (this.sqClass === floatClass) { //These words are actually a Float this.isFloat = true; this.float = this.decodeFloat(bits, littleEndian, true); } else if (nWords > 0) { this.words = this.decodeWords(nWords, bits, littleEndian); } break; case 12: // 16 bit array case 13: // 16 bit array (odd length) throw Error("16 bit arrays not supported yet"); case 20: // 8 bit array, length-4 (64 bit image) case 21: // ... length-5 case 22: // ... length-6 case 23: // ... length-7 nWords--; this._format -= 4; // fall through case 16: // 8 bit array case 17: // ... length-1 case 18: // ... length-2 case 19: // ... length-3 if (nWords > 0) this.bytes = this.decodeBytes(nWords, bits, 0, this._format & 3); break; case 28: // CompiledMethod, length-4 (64 bit image) case 29: // ... length-5 case 30: // ... length-6 case 31: // ... length-7 nWords--; this._format -= 4; // fall through case 24: // CompiledMethod case 25: // ... length-1 case 26: // ... length-2 case 27: // ... length-3 var rawHeader = this.decodeWords(1, bits, littleEndian)[0]; var intHeader = rawHeader >> (is64Bit ? 3 : 1); var numLits = intHeader & 0x7FFF, oops = is64Bit ? this.decodeWords64(numLits+1, bits, littleEndian) : this.decodeWords(numLits+1, bits, littleEndian), ptrWords = is64Bit ? (numLits + 1) * 2 : numLits + 1; this.pointers = this.decodePointers(numLits+1, oops, oopMap, getCharacter, is64Bit); //header+lits this.bytes = this.decodeBytes(nWords-ptrWords, bits, ptrWords, this._format & 3); if (is64Bit) this.pointers[0] = (bits[1] & 0x80000000) | intHeader; // fix header break; default: throw Error("Unknown object format: " + this._format); } this.mark = false; // for GC }, decodeWords64: function(nWords, theBits, littleEndian) { // we assume littleEndian for now var words = new Array(nWords); for (var i = 0; i < nWords; i++) { var lo = theBits[i*2], hi = theBits[i*2+1]; words[i] = Squeak.word64FromUint32(hi, lo); } return words; }, decodePointers: function(nWords, theBits, oopMap, getCharacter, is64Bit) { //Convert immediate objects and look up object pointers in oopMap var ptrs = new Array(nWords); for (var i = 0; i < nWords; i++) { var oop = theBits[i]; // in 64 bits, oops > 53 bits are read as [hi, lo] if (typeof oop !== "number") { if ((oop[1] & 7) === 4) { ptrs[i] = this.decodeSmallFloat(oop[0], oop[1], is64Bit); } else if ((oop[1] & 7) === 1) { ptrs[i] = is64Bit.makeLargeFromSmall(oop[0], oop[1]); } else if ((oop[1] & 7) === 2) { throw Error("Large Immediate Characters not implemented yet"); } else { throw Error("Large OOPs not implemented yet"); } } else if ((oop & 1) === 1) { // SmallInteger if (is64Bit) { // if it fits in a 31 bit SmallInt ... ptrs[i] = (oop >= 0 ? oop <= 0x1FFFFFFFF : oop >= -8589934592) ? oop / 4 >> 1 // ... then convert directly, otherwise make large : is64Bit.makeLargeFromSmall((oop - (oop >>> 0)) / 0x100000000 >>> 0, oop >>> 0); } else ptrs[i] = oop >> 1; } else if ((oop & 3) === 2) { // Character if (oop < 0 || oop > 0x1FFFFFFFF) throw Error("Large Immediate Characters not implemented yet"); ptrs[i] = getCharacter(oop >>> (is64Bit ? 3 : 2)); } else if (is64Bit && (oop & 7) === 4) { // SmallFloat ptrs[i] = this.decodeSmallFloat((oop - (oop >>> 0)) / 0x100000000 >>> 0, oop >>> 0, is64Bit); } else { // Object ptrs[i] = oopMap.get(oop) || 42424242; // when loading a context from image segment, there is // garbage beyond its stack pointer, resulting in the oop // not being found in oopMap. We just fill in an arbitrary // SmallInteger - it's never accessed anyway // until 64 bit is working correctly, leave this here as a check ... if (ptrs[i] === 42424242) debugger; } } return ptrs; }, decodeSmallFloat: function(hi, lo, is64Bit) { // SmallFloats are stored with full 52 bit mantissa, but shortened exponent. // The lowest 3 bits are tags, the next is the sign bit var newHi = 0, newLo = 0, sign = (lo & 8) << (32-4), // shift sign bit to msb isZero = (hi | (lo & 0xFFFFFFF0)) === 0; // ignore sign and tag bits if (isZero) { // zero is special - can be positive or negative newHi = sign; } else { // shift everything right by 4, fix exponent, add sign newHi = (hi >>> 4) + 0x38000000 | sign; newLo = (lo >>> 4) | (hi & 0xF) << (32-4); // 1023 is the bias of the 11-bit exponent in an IEEE 754 64-bit float, // and 127 is the bias of our 8-bit exponent. 1023-127 == 0x380 } return is64Bit.makeFloat(new Uint32Array([newLo, newHi])); }, overhead64: function(bits) { // the number of bytes this object is larger in 64 bits than in 32 bits // (due to 8-byte alignment even in 32 bits this only affects pointer objects) var overhead = 0; var words32 = 0; var words64 = 0; if (this._format <= 5) { // pointer objects overhead = bits.length & -2; // each oop occupied 2 words instead of 1 ... // ... but odd lengths get padded so we subtract 1 // words32 === words64 because same number of oops } else if (this._format >= 24) { // compiled methods var numLits = (bits[0] >> 3) & 0x7FFF; // assumes 64 bit little endian var overhead = numLits + 1; // each oop occupied 2 words instead of 1 ... var oddOops = (overhead & 1) === 1; var oddBytes = this._format >= 28; // ... odd-word lengths would get padded so we subtract 1, // but if there is also odd-word bytecodes it cancels out so we save 1 word instead if (oddOops) overhead += oddBytes ? 1 : -1; words64 = bits.length / 2; words32 = bits.length - overhead; } else { // non-pointer objects have no oop overhead words32 = bits.length; words64 = words32 / 2; } // we need an extra header in 32 bits if we now use more words than before return { bytes: overhead * 4, sizeHeader: words32 >= 255 && words64 < 255, } }, initInstanceOfChar: function(charClass, unicode) { this.oop = (unicode << 2) | 2; this.sqClass = charClass; this.hash = unicode; this._format = 7; this.mark = true; // stays always marked so not traced by GC }, initInstanceOfFloat: function(floatClass, bits) { this.sqClass = floatClass; this.hash = 0; this._format = 10; this.isFloat = true; this.float = this.decodeFloat(bits, true, true); }, initInstanceOfLargeInt: function(largeIntClass, size) { this.sqClass = largeIntClass; this.hash = 0; this._format = 16; // this._format |= -indexableSize & 3; //deferred to writeTo() this.bytes = new Uint8Array(size); }, classNameFromImage: function(oopMap, rawBits) { var name = oopMap.get(rawBits.get(this.oop)[Squeak.Class_name]); if (name && name._format >= 16 && name._format < 24) { var bits = rawBits.get(name.oop), bytes = name.decodeBytes(bits.length, bits, 0, name._format & 7); return Squeak.bytesAsString(bytes); } return "Class"; }, renameFromImage: function(oopMap, rawBits, classTable) { var classObj = classTable[this.sqClass]; if (!classObj) return this; var instProto = classObj.instProto || classObj.classInstProto(classObj.classNameFromImage(oopMap, rawBits)); if (!instProto) return this; var renamedObj = new instProto; // Squeak.SpurObject renamedObj.oop = this.oop; renamedObj.sqClass = this.sqClass; renamedObj._format = this._format; renamedObj.hash = this.hash; return renamedObj; }, }, 'accessing', { instSize: function() {//same as class.classInstSize, but faster from format if (this._format < 2) return this.pointersSize(); //fixed fields only return this.sqClass.classInstSize(); }, indexableSize: function(primHandler) { var fmt = this._format; if (fmt < 2) return -1; //not indexable if (fmt === 3 && primHandler.vm.isContext(this)) return this.pointers[Squeak.Context_stackPointer]; // no access beyond top of stacks if (fmt < 6) return this.pointersSize() - this.instSize(); // pointers if (fmt < 12) return this.wordsSize(); // words if (fmt < 16) return this.shortsSize(); // shorts if (fmt < 24) return this.bytesSize(); // bytes return 4 * this.pointersSize() + this.bytesSize(); // methods }, snapshotSize: function() { // words of extra object header and body this object would take up in image snapshot // body size includes header size that is always present var nWords = this.isFloat ? 2 : this.words ? this.words.length : this.pointers ? this.pointers.length : 0; // methods have both pointers and bytes if (this.bytes) nWords += (this.bytes.length + 3) >>> 2; var extraHeader = nWords >= 255 ? 2 : 0; nWords += nWords & 1; // align to 8 bytes nWords += 2; // one 64 bit header always present if (nWords < 4) nWords = 4; // minimum object size return {header: extraHeader, body: nWords}; }, writeTo: function(data, pos, littleEndian, objToOop) { var nWords = this.isFloat ? 2 : this.words ? this.words.length : this.pointers ? this.pointers.length : 0; if (this.bytes) { nWords += (this.bytes.length + 3) >>> 2; this._format |= -this.bytes.length & 3; } var beforePos = pos, formatAndClass = (this._format << 24) | (this.sqClass.hash & 0x003FFFFF), sizeAndHash = (nWords << 24) | (this.hash & 0x003FFFFF); // write extra header if needed if (nWords >= 255) { data.setUint32(pos, nWords, littleEndian); pos += 4; sizeAndHash = (255 << 24) | (this.hash & 0x003FFFFF); data.setUint32(pos, sizeAndHash, littleEndian); pos += 4; } // write regular header data.setUint32(pos, formatAndClass, littleEndian); pos += 4; data.setUint32(pos, sizeAndHash, littleEndian); pos += 4; // now write body, if any if (this.isFloat) { data.setFloat64(pos, this.float, littleEndian); pos += 8; } else if (this.words) { for (var i = 0; i < this.words.length; i++) { data.setUint32(pos, this.words[i], littleEndian); pos += 4; } } else if (this.pointers) { var startIndex = 0; if (this._format >= 24) { // preserve signFlag in method header var mask = this.methodSignFlag() ? 0x80000000 : 0; var taggedHeader = this.pointers[0] << 1 | 1 | mask; data.setUint32(pos, taggedHeader, littleEndian); pos += 4; startIndex = 1; } for (var i = startIndex; i < this.pointers.length; i++) { data.setUint32(pos, objToOop(this.pointers[i]), littleEndian); pos += 4; } } // no "else" because CompiledMethods have both pointers and bytes if (this.bytes) { for (var i = 0; i < this.bytes.length; i++) data.setUint8(pos++, this.bytes[i]); // skip to next word pos += -this.bytes.length & 3; } // minimum object size is 16, align to 8 bytes if (nWords === 0) pos += 8; else pos += (nWords & 1) * 4; // done if (pos !== beforePos + this.totalBytes()) throw Error("written size does not match"); return pos; }, }, 'testing', { isBytes: function() { var fmt = this._format; return fmt >= 16 && fmt <= 23; }, isPointers: function() { return this._format <= 6; }, isWords: function() { return this._format === 10; }, isWordsOrBytes: function() { var fmt = this._format; return fmt === 10 || (fmt >= 16 && fmt <= 23); }, isWeak: function() { return this._format === 4; }, isMethod: function() { return this._format >= 24; }, sameFormats: function(a, b) { return a < 16 ? a === b : (a & 0xF8) === (b & 0xF8); }, }, 'as class', { defaultInst: function() { return Squeak.ObjectSpur; }, classInstFormat: function() { return (this.pointers[Squeak.Class_format] >> 16) & 0x1F; }, classInstSize: function() { // this is a class, answer number of named inst vars return this.pointers[Squeak.Class_format] & 0xFFFF; }, classInstIsBytes: function() { var fmt = this.classInstFormat(); return fmt >= 16 && fmt <= 23; }, classInstIsPointers: function() { return this.classInstFormat() <= 6; }, classByteSizeOfInstance: function(nElements) { var format = this.classInstFormat(), nWords = this.classInstSize(); if (format < 9) nWords += nElements; // 32 bit else if (format >= 16) nWords += (nElements + 3) / 4 | 0; // 8 bit else if (format >= 12) nWords += (nElements + 1) / 2 | 0; // 16 bit else if (format >= 10) nWords += nElements; // 32 bit else nWords += nElements * 2; // 64 bit nWords += nWords & 1; // align to 64 bits nWords += nWords >= 255 ? 4 : 2; // header words if (nWords < 4) nWords = 4; // minimum object size return nWords * 4; }, }, 'as compiled block', { blockOuterCode: function() { return this.pointers[this.pointers.length - 1]; }, }, 'as method', { methodSignFlag: function() { return this.pointers[0] < 0; }, methodNumLits: function() { return this.pointers[0] & 0x7FFF; }, methodPrimitiveIndex: function() { if ((this.pointers[0] & 0x10000) === 0) return 0; return this.bytes[1] + 256 * this.bytes[2]; }, methodAsString: function() { var cls = this.pointers[this.pointers.length - 1].pointers[Squeak.ClassBinding_value]; var selector = this.pointers[this.pointers.length - 2]; if (selector.pointers) selector = selector.pointers[Squeak.AdditionalMethodState_selector]; return cls.className() + ">>" + selector.bytesAsString(); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.Image', 'about', { about: function() { /* Object Format ============= Each Squeak object is a Squeak.Object instance, only SmallIntegers are JS numbers. Instance variables/fields reference other objects directly via the "pointers" property. A Spur image uses Squeak.ObjectSpur instances instead. Characters are not immediate, but made identical using a character table. They are created with their mark bit set to true, so are ignored by the GC. { sqClass: reference to class object format: format integer as in Squeak oop header hash: identity hash integer pointers: (optional) Array referencing inst vars + indexable fields words: (optional) Array of numbers (words) bytes: (optional) Array of numbers (bytes) float: (optional) float value if this is a Float object isNil: (optional) true if this is the nil object isTrue: (optional) true if this is the true object isFalse: (optional) true if this is the false object isFloat: (optional) true if this is a Float object isFloatClass: (optional) true if this is the Float class isCompact: (optional) true if this is a compact class oop: identifies this object in a snapshot (assigned on GC, new space object oops are negative) mark: boolean (used only during GC, otherwise false) dirty: boolean (true when an object may have a ref to a new object, set on every write, reset on GC) nextObject: linked list of objects in old space and young space (newly created objects do not have this yet) } Object Memory ============= Objects in old space are a linked list (firstOldObject). When loading an image, all objects are old. Objects are tenured to old space during a full GC. New objects are only referenced by other objects' pointers, and thus can be garbage-collected at any time by the Javascript GC. A partial GC creates a linked list of new objects reachable from old space. We call this list "young space". It is not stored, but only created by primitives like nextObject, nextInstance, or become to support enumeration of new space. To efficiently find potential young space roots, any write to an instance variable sets the "dirty" flag of the object, allowing to skip clean objects. Weak references are finalized by a full GC. A partial GC only finalizes young weak references. */ } }, 'initializing', { initialize: function(name) { this.headRoom = 100000000; // TODO: pass as option this.totalMemory = 0; this.headerFlags = 0; this.name = name; this.gcCount = 0; this.gcMilliseconds = 0; this.pgcCount = 0; this.pgcMilliseconds = 0; this.gcTenured = 0; this.allocationCount = 0; this.oldSpaceCount = 0; this.youngSpaceCount = 0; this.newSpaceCount = 0; this.hasNewInstances = {}; }, readFromBuffer: function(arraybuffer, thenDo, progressDo) { console.log('squeak: reading ' + this.name + ' (' + arraybuffer.byteLength + ' bytes)'); this.startupTime = Date.now(); var data = new DataView(arraybuffer), littleEndian = false, pos = 0; var readWord32 = function() { var int = data.getUint32(pos, littleEndian); pos += 4; return int; }; var readWord64 = function() { // we assume littleEndian for now var lo = data.getUint32(pos, true), hi = data.getUint32(pos+4, true); pos += 8; return Squeak.word64FromUint32(hi, lo); }; var readWord = readWord32; var wordSize = 4; var readBits = function(nWords, isPointers) { if (isPointers) { // do endian conversion var oops = []; while (oops.length < nWords) oops.push(readWord()); return oops; } else { // words (no endian conversion yet) var bits = new Uint32Array(arraybuffer, pos, nWords * wordSize / 4); pos += nWords * wordSize; return bits; } }; // read version and determine endianness var baseVersions = [6501, 6502, 6504, 68000, 68002, 68004], baseVersionMask = 0x119EE, version = 0, fileHeaderSize = 0; while (true) { // try all four endianness + header combos littleEndian = !littleEndian; pos = fileHeaderSize; version = readWord(); if (baseVersions.indexOf(version & baseVersionMask) >= 0) break; if (!littleEndian) fileHeaderSize += 512; if (fileHeaderSize > 512) throw Error("bad image version"); // we tried all combos } this.version = version; var nativeFloats = (version & 1) !== 0; this.hasClosures = !([6501, 6502, 68000].indexOf(version) >= 0); this.isSpur = (version & 16) !== 0; // var multipleByteCodeSetsActive = (version & 256) !== 0; // not used var is64Bit = version >= 68000; if (is64Bit && !this.isSpur) throw Error("64 bit non-spur images not supported yet"); if (is64Bit) { readWord = readWord64; wordSize = 8; } // parse image header var imageHeaderSize = readWord32(); // always 32 bits var objectMemorySize = readWord(); //first unused location in heap var oldBaseAddr = readWord(); //object memory base address of image var specialObjectsOopInt = readWord(); //oop of array of special oops var lastHash = readWord32(); if (is64Bit) readWord32(); // not used var savedWindowSize = readWord(); // not used this.headerFlags = readWord(); // vm attribute 48 this.savedHeaderWords = [lastHash, savedWindowSize, this.headerFlags]; for (var i = 0; i < 4; i++) { this.savedHeaderWords.push(readWord32()); } var firstSegSize = readWord(); var prevObj; var oopMap = new Map(); var rawBits = new Map(); var headerSize = fileHeaderSize + imageHeaderSize; pos = headerSize; if (!this.isSpur) { // read traditional object memory while (pos < headerSize + objectMemorySize) { var nWords = 0; var classInt = 0; var header = readWord(); switch (header & Squeak.HeaderTypeMask) { case Squeak.HeaderTypeSizeAndClass: nWords = header >>> 2; classInt = readWord(); header = readWord(); break; case Squeak.HeaderTypeClass: classInt = header - Squeak.HeaderTypeClass; header = readWord(); nWords = (header >>> 2) & 63; break; case Squeak.HeaderTypeShort: nWords = (header >>> 2) & 63; classInt = (header >>> 12) & 31; //compact class index //Note classInt<32 implies compact class index break; case Squeak.HeaderTypeFree: throw Error("Unexpected free block"); } nWords--; //length includes base header which we have already read var oop = pos - 4 - headerSize, //0-rel byte oop of this object (base header) format = (header>>>8) & 15, hash = (header>>>17) & 4095, bits = readBits(nWords, format < 5); var object = new Squeak.Object(); object.initFromImage(oop, classInt, format, hash); if (classInt < 32) object.hash |= 0x10000000; // see fixCompactOops() if (prevObj) prevObj.nextObject = object; this.oldSpaceCount++; prevObj = object; //oopMap is from old oops to actual objects oopMap.set(oldBaseAddr + oop, object); //rawBits holds raw content bits for objects rawBits.set(oop, bits); } this.firstOldObject = oopMap.get(oldBaseAddr+4); this.lastOldObject = object; this.lastOldObject.nextObject = null; // Add next object pointer as indicator this is in fact an old object this.oldSpaceBytes = objectMemorySize; } else { // Read all Spur object memory segments this.oldSpaceBytes = firstSegSize - 16; var segmentEnd = pos + firstSegSize, addressOffset = 0, classPages = null, skippedBytes = 0, oopAdjust = {}; while (pos < segmentEnd) { while (pos < segmentEnd - 16) { // read objects in segment var objPos = pos, formatAndClass = readWord32(), sizeAndHash = readWord32(), size = sizeAndHash >>> 24; if (size === 255) { // this was the extended size header, read actual header size = formatAndClass; // In 64 bit images the size can actually be 56 bits. LOL. Nope. // if (is64Bit) size += (sizeAndHash & 0x00FFFFFF) * 0x100000000; formatAndClass = readWord32(); sizeAndHash = readWord32(); } var oop = addressOffset + pos - 8 - headerSize, format = (formatAndClass >>> 24) & 0x1F, classID = formatAndClass & 0x003FFFFF, hash = sizeAndHash & 0x003FFFFF; var bits = readBits(size, format < 10 && classID > 0); // align on 8 bytes, min size 16 bytes pos += is64Bit ? (size < 1 ? 1 - size : 0) * 8 : (size < 2 ? 2 - size : size & 1) * 4; // low class ids are internal to Spur if (classID >= 32) { var object = new Squeak.ObjectSpur(); object.initFromImage(oop, classID, format, hash); if (prevObj) prevObj.nextObject = object; this.oldSpaceCount++; prevObj = object; //oopMap is from old oops to actual objects oopMap.set(oldBaseAddr + oop, object); //rawBits holds raw content bits for objects rawBits.set(oop, bits); oopAdjust[oop] = skippedBytes; // account for size difference of 32 vs 64 bit oops if (is64Bit) { var overhead = object.overhead64(bits); skippedBytes += overhead.bytes; // OTOH, in 32 bits we need the extra size header sooner // so in some cases 64 bits has 2 words less overhead if (overhead.sizeHeader) { oopAdjust[oop] -= 8; skippedBytes -= 8; } } } else { skippedBytes += pos - objPos; if (classID === 16 && !classPages) classPages = bits; if (classID) oopMap.set(oldBaseAddr + oop, bits); // used in spurClassTable() } } if (pos !== segmentEnd - 16) throw Error("invalid segment"); // last 16 bytes in segment is a bridge object var deltaWords = readWord32(), deltaWordsHi = readWord32(), segmentBytes = readWord32(); readWord32(); // if segmentBytes is zero, the end of the image has been reached if (segmentBytes !== 0) { var deltaBytes = deltaWordsHi & 0xFF000000 ? (deltaWords & 0x00FFFFFF) * 4 : 0; segmentEnd += segmentBytes; addressOffset += deltaBytes; skippedBytes += 16 + deltaBytes; this.oldSpaceBytes += deltaBytes + segmentBytes; } } this.oldSpaceBytes -= skippedBytes; this.firstOldObject = oopMap.get(oldBaseAddr); this.lastOldObject = object; this.lastOldObject.nextObject = null; // Add next object pointer as indicator this is in fact an old object } this.totalMemory = this.oldSpaceBytes + this.headRoom; this.totalMemory = Math.ceil(this.totalMemory / 1000000) * 1000000; { // For debugging: re-create all objects from named prototypes var _splObs = oopMap.get(specialObjectsOopInt), cc = this.isSpur ? this.spurClassTable(oopMap, rawBits, classPages, _splObs) : rawBits.get(oopMap.get(rawBits.get(_splObs.oop)[Squeak.splOb_CompactClasses]).oop); var renamedObj = null; object = this.firstOldObject; prevObj = null; while (object) { prevObj = renamedObj; renamedObj = object.renameFromImage(oopMap, rawBits, cc); if (prevObj) prevObj.nextObject = renamedObj; else this.firstOldObject = renamedObj; oopMap.set(oldBaseAddr + object.oop, renamedObj); object = object.nextObject; } this.lastOldObject = renamedObj; this.lastOldObject.nextObject = null; // Add next object pointer as indicator this is in fact an old object } // properly link objects by mapping via oopMap var splObs = oopMap.get(specialObjectsOopInt); var compactClasses = rawBits.get(oopMap.get(rawBits.get(splObs.oop)[Squeak.splOb_CompactClasses]).oop); var floatClass = oopMap.get(rawBits.get(splObs.oop)[Squeak.splOb_ClassFloat]); // Spur needs different arguments for installFromImage() if (this.isSpur) { this.initImmediateClasses(oopMap, rawBits, splObs); compactClasses = this.spurClassTable(oopMap, rawBits, classPages, splObs); nativeFloats = this.getCharacter.bind(this); this.initSpurOverrides(); } var obj = this.firstOldObject, done = 0; var mapSomeObjects = function() { if (obj) { var stop = done + (this.oldSpaceCount / 20 | 0); // do it in 20 chunks while (obj && done < stop) { obj.installFromImage(oopMap, rawBits, compactClasses, floatClass, littleEndian, nativeFloats, is64Bit && { makeFloat: function makeFloat(bits) { return this.instantiateFloat(bits); }.bind(this), makeLargeFromSmall: function makeLargeFromSmall(hi, lo) { return this.instantiateLargeFromSmall(hi, lo); }.bind(this), }); obj = obj.nextObject; done++; } if (progressDo) progressDo(done / this.oldSpaceCount); return true; // do more } else { // done this.specialObjectsArray = splObs; this.decorateKnownObjects(); if (this.isSpur) { this.fixSkippedOops(oopAdjust); if (is64Bit) this.fixPCs(); this.ensureFullBlockClosureClass(this.specialObjectsArray, compactClasses); } else { this.fixCompiledMethods(); this.fixCompactOops(); } return false; // don't do more } }.bind(this); function mapSomeObjectsAsync() { if (mapSomeObjects()) { self.setTimeout(mapSomeObjectsAsync, 0); } else { if (thenDo) thenDo(); } } if (!progressDo) { while (mapSomeObjects()) {} if (thenDo) thenDo(); } else { self.setTimeout(mapSomeObjectsAsync, 0); } }, decorateKnownObjects: function() { var splObjs = this.specialObjectsArray.pointers; splObjs[Squeak.splOb_NilObject].isNil = true; splObjs[Squeak.splOb_TrueObject].isTrue = true; splObjs[Squeak.splOb_FalseObject].isFalse = true; splObjs[Squeak.splOb_ClassFloat].isFloatClass = true; if (!this.isSpur) { this.compactClasses = this.specialObjectsArray.pointers[Squeak.splOb_CompactClasses].pointers; for (var i = 0; i < this.compactClasses.length; i++) if (!this.compactClasses[i].isNil) this.compactClasses[i].isCompact = true; } if (!Number.prototype.sqInstName) Object.defineProperty(Number.prototype, 'sqInstName', { enumerable: false, value: function() { return this.toString() } }); }, fixCompactOops: function() { // instances of compact classes might have been saved with a non-compact header // fix their oops here so validation succeeds later if (this.isSpur) return; var obj = this.firstOldObject, adjust = 0; while (obj) { var hadCompactHeader = obj.hash > 0x0FFFFFFF, mightBeCompact = !!obj.sqClass.isCompact; if (hadCompactHeader !== mightBeCompact) { var isCompact = obj.snapshotSize().header === 0; if (hadCompactHeader !== isCompact) { adjust += isCompact ? -4 : 4; } } obj.hash &= 0x0FFFFFFF; obj.oop += adjust; obj = obj.nextObject; } this.oldSpaceBytes += adjust; }, fixCompiledMethods: function() { // in the 6501 pre-release image, some CompiledMethods // do not have the proper class if (this.version >= 6502) return; var obj = this.firstOldObject, compiledMethodClass = this.specialObjectsArray.pointers[Squeak.splOb_ClassCompiledMethod]; while (obj) { if (obj.isMethod()) obj.sqClass = compiledMethodClass; obj = obj.nextObject; } }, fixSkippedOops: function(oopAdjust) { // reading Spur skips some internal objects // we adjust the oops of following objects here // this is like the compaction phase of our GC var obj = this.firstOldObject; while (obj) { obj.oop -= oopAdjust[obj.oop]; obj = obj.nextObject; } // do a sanity check obj = this.lastOldObject; if (obj.addr() + obj.totalBytes() !== this.oldSpaceBytes) throw Error("image size doesn't match object sizes") }, fixPCs: function() { // In 64 bits literals take up twice as much space // The pc starts after the last literal. Fix it. var clsMethodContext = this.specialObjectsArray.pointers[Squeak.splOb_ClassMethodContext], pc = Squeak.Context_instructionPointer, method = Squeak.Context_method, clsBlockClosure = this.specialObjectsArray.pointers[Squeak.splOb_ClassBlockClosure], startpc = Squeak.Closure_startpc, outerContext = Squeak.Closure_outerContext, obj = this.firstOldObject; while (obj) { if (obj.sqClass === clsMethodContext) { obj.pointers[pc] -= obj.pointers[method].pointers.length * 4; } else if (obj.sqClass === clsBlockClosure) { obj.pointers[startpc] -= obj.pointers[outerContext].pointers[method].pointers.length * 4; } obj = obj.nextObject; } }, ensureFullBlockClosureClass: function(splObs, compactClasses) { // Read FullBlockClosure class from compactClasses if not yet present in specialObjectsArray. if (splObs.pointers[Squeak.splOb_ClassFullBlockClosure].isNil && compactClasses[38]) { splObs.pointers[Squeak.splOb_ClassFullBlockClosure] = compactClasses[38]; } }, }, 'garbage collection - full', { fullGC: function(reason) { // Collect garbage and return first tenured object (to support object enumeration) // Old space is a linked list of objects - each object has an "nextObject" reference. // New space objects do not have that pointer, they are garbage-collected by JavaScript. // But they have an allocation id so the survivors can be ordered on tenure. // The "nextObject" references are created by collecting all new objects, // sorting them by id, and then linking them into old space. this.vm.addMessage("fullGC: " + reason); var start = Date.now(); var previousNew = this.newSpaceCount; // includes young and newly allocated var previousOld = this.oldSpaceCount; var newObjects = this.markReachableObjects(); // technically these are young objects this.removeUnmarkedOldObjects(); this.appendToOldObjects(newObjects); this.finalizeWeakReferences(); this.allocationCount += this.newSpaceCount; this.newSpaceCount = 0; this.youngSpaceCount = 0; this.hasNewInstances = {}; this.gcCount++; this.gcMilliseconds += Date.now() - start; var delta = previousOld - this.oldSpaceCount; // absolute change var survivingNew = newObjects.length; var survivingOld = this.oldSpaceCount - survivingNew; var gcedNew = previousNew - survivingNew; var gcedOld = previousOld - survivingOld; console.log("Full GC (" + reason + "): " + (Date.now() - start) + " ms;" + " before: " + previousOld.toLocaleString() + " old objects;" + " allocated " + previousNew.toLocaleString() + " new;" + " surviving " + survivingOld.toLocaleString() + " old;" + " tenuring " + survivingNew.toLocaleString() + " new;" + " gc'ed " + gcedOld.toLocaleString() + " old and " + gcedNew.toLocaleString() + " new;" + " total now: " + this.oldSpaceCount.toLocaleString() + " (" + (delta > 0 ? "+" : "") + delta.toLocaleString() + ", " + this.oldSpaceBytes.toLocaleString() + " bytes)" ); return newObjects.length > 0 ? newObjects[0] : null; }, gcRoots: function() { // the roots of the system this.vm.storeContextRegisters(); // update active context return [this.specialObjectsArray, this.vm.activeContext]; }, markReachableObjects: function() { // FullGC: Visit all reachable objects and mark them. // Return surviving new objects (young objects to be tenured). // Contexts are handled specially: they have garbage beyond the stack pointer // which must not be traced, and is cleared out here // In weak objects, only the inst vars are traced var todo = this.gcRoots(); var newObjects = []; this.weakObjects = []; while (todo.length > 0) { var object = todo.pop(); if (object.mark) continue; // objects are added to todo more than once if (object.oop < 0) // it's a new object newObjects.push(object); object.mark = true; // mark it if (!object.sqClass.mark) // trace class if not marked todo.push(object.sqClass); var body = object.pointers; if (body) { // trace all unmarked pointers var n = body.length; if (object.isWeak()) { n = object.sqClass.classInstSize(); // do not trace weak fields this.weakObjects.push(object); } if (this.vm.isContext(object)) { // contexts have garbage beyond SP n = object.contextSizeWithStack(); for (var i = n; i < body.length; i++) // clean up that garbage body[i] = this.vm.nilObj; } for (var i = 0; i < n; i++) if (typeof body[i] === "object" && !body[i].mark) // except immediates todo.push(body[i]); // Note: "immediate" character objects in Spur always stay marked } } // pre-spur sort by oop to preserve creation order return this.isSpur ? newObjects : newObjects.sort(function(a,b){return b.oop - a.oop}); }, removeUnmarkedOldObjects: function() { // FullGC: Unlink unmarked old objects from the nextObject linked list // Reset marks of remaining objects, and adjust their oops // Set this.lastOldObject to last old object var removedCount = 0, removedBytes = 0, obj = this.firstOldObject; obj.mark = false; // we know the first object (nil) was marked while (true) { var next = obj.nextObject; if (!next) {// we're done this.lastOldObject = obj; this.lastOldObject.nextObject = null; // Add next object pointer as indicator this is in fact an old object this.oldSpaceBytes -= removedBytes; this.oldSpaceCount -= removedCount; return; } // reset partial GC flag if (next.dirty) next.dirty = false; // if marked, continue with next object if (next.mark) { obj = next; obj.mark = false; // unmark for next GC obj.oop -= removedBytes; // compact oops } else { // otherwise, remove it var corpse = next; obj.nextObject = corpse.nextObject; // drop from old-space list corpse.oop = -(++this.newSpaceCount); // move to new-space for finalizing removedBytes += corpse.totalBytes(); removedCount++; //console.log("removing " + removedCount + " " + removedBytes + " " + corpse.totalBytes() + " " + corpse.toString()) } } }, appendToOldObjects: function(newObjects) { // FullGC: append new objects to linked list of old objects // and unmark them var oldObj = this.lastOldObject; //var oldBytes = this.oldSpaceBytes; for (var i = 0; i < newObjects.length; i++) { var newObj = newObjects[i]; newObj.mark = false; this.oldSpaceBytes = newObj.setAddr(this.oldSpaceBytes); // add at end of memory oldObj.nextObject = newObj; oldObj = newObj; //console.log("tenuring " + (i+1) + " " + (this.oldSpaceBytes - oldBytes) + " " + newObj.totalBytes() + " " + newObj.toString()); } oldObj.nextObject = null; // might have been in young space this.lastOldObject = oldObj; this.lastOldObject.nextObject = null; // Add next object pointer as indicator this is in fact an old object this.oldSpaceCount += newObjects.length; this.gcTenured += newObjects.length; // this is the only place that increases oldSpaceBytes / decreases bytesLeft this.vm.signalLowSpaceIfNecessary(this.bytesLeft()); // TODO: keep track of newSpaceBytes and youngSpaceBytes, and signal low space if necessary // basically, add obj.totalBytes() to newSpaceBytes when instantiating, // trigger partial GC if newSpaceBytes + lowSpaceThreshold > totalMemory - (youngSpaceBytes + oldSpaceBytes) // which would set newSpaceBytes to 0 and youngSpaceBytes to the actual survivors. // for efficiency, only compute object size once per object and store? test impact on GC speed }, tenureIfYoung: function(object) { if (object.oop < 0) { this.appendToOldObjects([object]); } }, finalizeWeakReferences: function() { // nil out all weak fields that did not survive GC var weakObjects = this.weakObjects; this.weakObjects = null; for (var o = 0; o < weakObjects.length; o++) { var weakObj = weakObjects[o], pointers = weakObj.pointers, firstWeak = weakObj.sqClass.classInstSize(), finalized = false; for (var i = firstWeak; i < pointers.length; i++) { if (pointers[i].oop < 0) { // ref is not in old-space pointers[i] = this.vm.nilObj; finalized = true; } } if (finalized) { this.vm.pendingFinalizationSignals++; if (firstWeak >= 2) { // check if weak obj is a finalizer item var list = weakObj.pointers[Squeak.WeakFinalizerItem_list]; if (list.sqClass == this.vm.specialObjects[Squeak.splOb_ClassWeakFinalizer]) { // add weak obj as first in the finalization list var items = list.pointers[Squeak.WeakFinalizationList_first]; weakObj.pointers[Squeak.WeakFinalizerItem_next] = items; list.pointers[Squeak.WeakFinalizationList_first] = weakObj; } } } } if (this.vm.pendingFinalizationSignals > 0) { this.vm.forceInterruptCheck(); // run finalizer asap } }, }, 'garbage collection - partial', { partialGC: function(reason) { // make a linked list of young objects // and finalize weak refs this.vm.addMessage("partialGC: " + reason); var start = Date.now(); var previous = this.newSpaceCount; var young = this.findYoungObjects(); this.appendToYoungSpace(young); this.finalizeWeakReferences(); this.cleanupYoungSpace(young); this.allocationCount += this.newSpaceCount - young.length; this.youngSpaceCount = young.length; this.newSpaceCount = this.youngSpaceCount; this.pgcCount++; this.pgcMilliseconds += Date.now() - start; console.log("Partial GC (" + reason+ "): " + (Date.now() - start) + " ms, " + "found " + this.youngRootsCount.toLocaleString() + " roots in " + this.oldSpaceCount.toLocaleString() + " old, " + "kept " + this.youngSpaceCount.toLocaleString() + " young (" + (previous - this.youngSpaceCount).toLocaleString() + " gc'ed)"); return young[0]; }, youngRoots: function() { // PartialGC: Find new objects directly pointed to by old objects. // For speed we only scan "dirty" objects that have been written to var roots = this.gcRoots().filter(function(obj){return obj.oop < 0;}), object = this.firstOldObject; while (object) { if (object.dirty) { var body = object.pointers, dirty = false; for (var i = 0; i < body.length; i++) { var child = body[i]; if (typeof child === "object" && child.oop < 0) { // if child is new roots.push(child); dirty = true; } } if (!dirty) object.dirty = false; } object = object.nextObject; } return roots; }, findYoungObjects: function() { // PartialGC: find new objects transitively reachable from old objects var todo = this.youngRoots(), // direct pointers from old space newObjects = []; this.youngRootsCount = todo.length; this.weakObjects = []; while (todo.length > 0) { var object = todo.pop(); if (object.mark) continue; // objects are added to todo more than once newObjects.push(object); object.mark = true; // mark it if (object.sqClass.oop < 0) // trace class if new todo.push(object.sqClass); var body = object.pointers; if (body) { // trace all unmarked pointers var n = body.length; if (object.isWeak()) { n = object.sqClass.classInstSize(); // do not trace weak fields this.weakObjects.push(object); } if (this.vm.isContext(object)) { // contexts have garbage beyond SP n = object.contextSizeWithStack(); for (var i = n; i < body.length; i++) // clean up that garbage body[i] = this.vm.nilObj; } for (var i = 0; i < n; i++) { var child = body[i]; if (typeof child === "object" && child.oop < 0) todo.push(child); } } } // pre-spur sort by oop to preserve creation order return this.isSpur ? newObjects : newObjects.sort(function(a,b){return b.oop - a.oop}); }, appendToYoungSpace: function(objects) { // PartialGC: link new objects into young list // and give them positive oops temporarily so finalization works var tempOop = this.lastOldObject.oop + 1; for (var i = 0; i < objects.length; i++) { var obj = objects[i]; if (this.hasNewInstances[obj.oop]) { delete this.hasNewInstances[obj.oop]; this.hasNewInstances[tempOop] = true; } obj.oop = tempOop; obj.nextObject = objects[i + 1]; tempOop++; } }, cleanupYoungSpace: function(objects) { // PartialGC: After finalizing weak refs, make oops // in young space negative again var obj = objects[0], youngOop = -1; while (obj) { if (this.hasNewInstances[obj.oop]) { delete this.hasNewInstances[obj.oop]; this.hasNewInstances[youngOop] = true; } obj.oop = youngOop; obj.mark = false; obj = obj.nextObject; youngOop--; } }, }, 'creating', { registerObject: function(obj) { // We don't actually register the object yet, because that would prevent // it from being garbage-collected by the Javascript collector obj.oop = -(++this.newSpaceCount); // temp oops are negative. Real oop assigned when surviving GC this.lastHash = (13849 + (27181 * this.lastHash)) & 0xFFFFFFFF; return this.lastHash & 0xFFF; }, registerObjectSpur: function(obj) { // We don't actually register the object yet, because that would prevent // it from being garbage-collected by the Javascript collector obj.oop = -(++this.newSpaceCount); // temp oops are negative. Real oop assigned when surviving GC return 0; // actual hash created on demand }, instantiateClass: function(aClass, indexableSize, filler) { var newObject = new (aClass.classInstProto()); // Squeak.Object var hash = this.registerObject(newObject); newObject.initInstanceOf(aClass, indexableSize, hash, filler); this.hasNewInstances[aClass.oop] = true; // need GC to find all instances return newObject; }, clone: function(object) { var newObject = new (object.sqClass.classInstProto()); // Squeak.Object var hash = this.registerObject(newObject); newObject.initAsClone(object, hash); this.hasNewInstances[newObject.sqClass.oop] = true; // need GC to find all instances return newObject; }, }, 'operations', { bulkBecome: function(fromArray, toArray, twoWay, copyHash) { if (!fromArray) return !toArray; var n = fromArray.length; if (n !== toArray.length) return false; // need to visit all objects: find young objects now // so oops do not change later var firstYoungObject = null; if (this.newSpaceCount > 0) firstYoungObject = this.partialGC("become"); // does update context else this.vm.storeContextRegisters(); // still need to update active context // obj.oop used as dict key here is why we store them // rather than just calculating at image snapshot time var mutations = {}; for (var i = 0; i < n; i++) { var obj = fromArray[i]; if (!obj.sqClass) return false; //non-objects in from array if (mutations[obj.oop]) return false; //repeated oops in from array else mutations[obj.oop] = toArray[i]; } if (twoWay) for (var i = 0; i < n; i++) { var obj = toArray[i]; if (!obj.sqClass) return false; //non-objects in to array if (mutations[obj.oop]) return false; //repeated oops in to array else mutations[obj.oop] = fromArray[i]; } // unless copyHash is false, make hash stay with the reference, not with the object if (copyHash) for (var i = 0; i < n; i++) { if (!toArray[i].sqClass) return false; //cannot change hash of non-objects var fromHash = fromArray[i].hash; fromArray[i].hash = toArray[i].hash; toArray[i].hash = fromHash; // Spur class table is not part of the object memory in SqueakJS // so won't be updated below, we have to update it manually if (this.isSpur && this.classTable[fromHash] === fromArray[i]) { this.classTable[fromHash] = toArray[i]; } } // temporarily append young objects to old space this.lastOldObject.nextObject = firstYoungObject; // Now, for every object... var obj = this.firstOldObject; while (obj) { // mutate the class var mut = mutations[obj.sqClass.oop]; if (mut) { obj.sqClass = mut; if (mut.oop < 0) obj.dirty = true; } // and mutate body pointers var body = obj.pointers; if (body) for (var j = 0; j < body.length; j++) { mut = mutations[body[j].oop]; if (mut) { body[j] = mut; if (mut.oop < 0) obj.dirty = true; } } obj = obj.nextObject; } // separate old / young space again this.lastOldObject.nextObject = null; this.vm.flushMethodCacheAfterBecome(mutations); return true; }, objectAfter: function(obj) { // if this was the last old object, continue with young objects return obj.nextObject || this.nextObjectWithGC("nextObject", obj); }, someInstanceOf: function(clsObj) { var obj = this.firstOldObject; while (obj) { if (obj.sqClass === clsObj) return obj; obj = obj.nextObject || this.nextObjectWithGCFor(obj, clsObj); } return null; }, nextInstanceAfter: function(obj) { var clsObj = obj.sqClass; while (true) { obj = obj.nextObject || this.nextObjectWithGCFor(obj, clsObj); if (!obj) return null; if (obj.sqClass === clsObj) return obj; } }, nextObjectWithGC: function(reason, obj) { // obj is either the last object in old space (after enumerating it) // or young space (after enumerating the list returned by partialGC) // or a random new object var limit = obj.oop > 0 ? 0 : this.youngSpaceCount; if (this.newSpaceCount <= limit) return null; // no more objects if (obj.oop < 0) this.fullGC(reason); // found a non-young new object return this.partialGC(reason); }, nextObjectWithGCFor: function(obj, clsObj) { // this is nextObjectWithGC but avoids GC if no instances in new space if (!this.hasNewInstances[clsObj.oop]) return null; return this.nextObjectWithGC("instance of " + clsObj.className(), obj); }, allInstancesOf: function(clsObj) { var obj = this.firstOldObject, result = []; while (obj) { if (obj.sqClass === clsObj) result.push(obj); obj = obj.nextObject || this.nextObjectWithGCFor(obj, clsObj); } return result; }, writeToBuffer: function() { var headerSize = 64, data = new DataView(new ArrayBuffer(headerSize + this.oldSpaceBytes)), pos = 0; var writeWord = function(word) { data.setUint32(pos, word); pos += 4; }; writeWord(this.formatVersion()); // magic number writeWord(headerSize); writeWord(this.oldSpaceBytes); // end of memory writeWord(this.firstOldObject.addr()); // base addr (0) writeWord(this.objectToOop(this.specialObjectsArray)); writeWord(this.lastHash); writeWord((800 << 16) + 600); // window size while (pos < headerSize) writeWord(0); // objects var obj = this.firstOldObject, n = 0; while (obj) { pos = obj.writeTo(data, pos, this); obj = obj.nextObject; n++; } if (pos !== data.byteLength) throw Error("wrong image size"); if (n !== this.oldSpaceCount) throw Error("wrong object count"); return data.buffer; }, objectToOop: function(obj) { // unsigned word for use in snapshot if (typeof obj === "number") return obj << 1 | 1; // add tag bit if (obj.oop < 0) throw Error("temporary oop"); return obj.oop; }, bytesLeft: function() { return this.totalMemory - this.oldSpaceBytes; }, formatVersion: function() { return this.isSpur ? 6521 : this.hasClosures ? 6504 : 6502; }, segmentVersion: function() { // a more complex version that tells both the word reversal and the endianness // of the machine it came from. Low half of word is 6502. Top byte is top byte // of #doesNotUnderstand: ($d on big-endian or $s on little-endian). // In SqueakJS we write non-Spur images and segments as big-endian, Spur as little-endian // (TODO: write non-Spur as little-endian too since that matches all modern platforms) var dnuFirstWord = this.isSpur ? 'seod' : 'does'; return this.formatVersion() | (dnuFirstWord.charCodeAt(0) << 24); }, storeImageSegment: function(segmentWordArray, outPointerArray, arrayOfRoots) { // This primitive will store a binary image segment (in the same format as the Squeak image file) of the receiver and every object in its proper tree of subParts (ie, that is not refered to from anywhere else outside the tree). Note: all elements of the receiver are treated as roots determining the extent of the tree. All pointers from within the tree to objects outside the tree will be copied into the array of outpointers. In their place in the image segment will be an oop equal to the offset in the outpointer array (the first would be 4). but with the high bit set. // The primitive expects the array and wordArray to be more than adequately long. In this case it returns normally, and truncates the two arrays to exactly the right size. If either array is too small, the primitive will fail, but in no other case. // use a DataView to access the segment as big-endian words var segment = new DataView(segmentWordArray.words.buffer), pos = 0, // write position in segment in bytes outPointers = outPointerArray.pointers, outPos = 0; // write position in outPointers in words // write header segment.setUint32(pos, this.segmentVersion()); pos += 4; // we don't want to deal with new space objects this.fullGC("storeImageSegment"); // First mark the root array and all root objects arrayOfRoots.mark = true; for (var i = 0; i < arrayOfRoots.pointers.length; i++) if (typeof arrayOfRoots.pointers[i] === "object") arrayOfRoots.pointers[i].mark = true; // Then do a mark pass over all objects. This will stop at our marked roots, // thus leaving our segment unmarked in their shadow this.markReachableObjects(); // Finally unmark the rootArray and all root objects arrayOfRoots.mark = false; for (var i = 0; i < arrayOfRoots.pointers.length; i++) if (typeof arrayOfRoots.pointers[i] === "object") arrayOfRoots.pointers[i].mark = false; // helpers for mapping objects to segment oops var segmentOops = {}, // map from object oop to segment oop todo = []; // objects that were added to the segment but still need to have their oops mapped // if an object does not yet have a segment oop, write it to the segment or outPointers function addToSegment(object) { var oop = segmentOops[object.oop]; if (!oop) { if (object.mark) { // object is outside segment, add to outPointers if (outPos >= outPointers.length) return 0; // fail if outPointerArray is too small oop = 0x80000004 + outPos * 4; outPointers[outPos++] = object; // no need to mark outPointerArray dirty, all objects are in old space } else { // add object to segment. if (pos + object.totalBytes() > segment.byteLength) return 0; // fail if segment is too small oop = pos + (object.snapshotSize().header + 1) * 4; // addr plus extra headers + base header pos = object.writeTo(segment, pos, this); // the written oops inside the object still need to be mapped to segment oops todo.push(object); } segmentOops[object.oop] = oop; } return oop; } addToSegment = addToSegment.bind(this); // if we have to bail out, clean up what we modified function cleanUp() { // unmark all objects var obj = this.firstOldObject; while (obj) { obj.mark = false; obj = obj.nextObject; } // forget weak objects collected by markReachableObjects() this.weakObjects = null; // return code for failure return false; } cleanUp = cleanUp.bind(this); // All external objects, and only they, are now marked. // Write the array of roots into the segment addToSegment(arrayOfRoots); // Now fix the oops inside written objects. // This will add more objects to the segment (if they are unmarked), // or to outPointers (if they are marked). while (todo.length > 0) { var obj = todo.shift(), oop = segmentOops[obj.oop], headerSize = obj.snapshotSize().header, objBody = obj.pointers, hasClass = headerSize > 0; if (hasClass) { var classOop = addToSegment(obj.sqClass); if (!classOop) return cleanUp(); // ran out of space var headerType = headerSize === 1 ? Squeak.HeaderTypeClass : Squeak.HeaderTypeSizeAndClass; segment.setUint32(oop - 8, classOop | headerType); } if (!objBody) continue; for (var i = 0; i < objBody.length; i++) { var child = objBody[i]; if (typeof child !== "object") continue; var childOop = addToSegment(child); if (!childOop) return cleanUp(); // ran out of space segment.setUint32(oop + i * 4, childOop); } } // Truncate image segment and outPointerArray to actual size var obj = segmentWordArray.oop < outPointerArray.oop ? segmentWordArray : outPointerArray, removedBytes = 0; while (obj) { obj.oop -= removedBytes; if (obj === segmentWordArray) { removedBytes += (obj.words.length * 4) - pos; obj.words = new Uint32Array(obj.words.buffer.slice(0, pos)); } else if (obj === outPointerArray) { removedBytes += (obj.pointers.length - outPos) * 4; obj.pointers.length = outPos; } obj = obj.nextObject; } this.oldSpaceBytes -= removedBytes; // unmark all objects etc cleanUp(); return true; }, loadImageSegment: function(segmentWordArray, outPointerArray) { // The C VM creates real objects from the segment in-place. // We do the same, inserting the new objects directly into old-space // between segmentWordArray and its following object (endMarker). // This only increases oldSpaceCount but not oldSpaceBytes. // The code below is almost the same as readFromBuffer() ... should unify if (segmentWordArray.words.length === 1) { // segment already loaded return segmentWordArray.nextObject; } var segment = new DataView(segmentWordArray.words.buffer), littleEndian = false, nativeFloats = false, pos = 0; var readWord = function() { var word = segment.getUint32(pos, littleEndian); pos += 4; return word; }; var readBits = function(nWords, format) { if (format < 5) { // pointers (do endian conversion) var oops = []; while (oops.length < nWords) oops.push(readWord()); return oops; } else { // words (no endian conversion yet) var bits = new Uint32Array(segment.buffer, pos, nWords); pos += nWords * 4; return bits; } }; // check version var version = readWord(); if (version & 0xFFFF !== 6502) { littleEndian = true; pos = 0; version = readWord(); if (version & 0xFFFF !== 6502) { console.error("image segment format not supported"); return null; } } // read objects this.tenureIfYoung(segmentWordArray); var prevObj = segmentWordArray, endMarker = prevObj.nextObject, oopOffset = segmentWordArray.oop, oopMap = new Map(), rawBits = new Map(); while (pos < segment.byteLength) { var nWords = 0, classInt = 0, header = readWord(); switch (header & Squeak.HeaderTypeMask) { case Squeak.HeaderTypeSizeAndClass: nWords = header >>> 2; classInt = readWord(); header = readWord(); break; case Squeak.HeaderTypeClass: classInt = header - Squeak.HeaderTypeClass; header = readWord(); nWords = (header >>> 2) & 63; break; case Squeak.HeaderTypeShort: nWords = (header >>> 2) & 63; classInt = (header >>> 12) & 31; //compact class index //Note classInt<32 implies compact class index break; case Squeak.HeaderTypeFree: throw Error("Unexpected free block"); } nWords--; //length includes base header which we have already read var oop = pos, //0-rel byte oop of this object (base header) format = (header>>>8) & 15, hash = (header>>>17) & 4095, bits = readBits(nWords, format); var object = new Squeak.Object(); object.initFromImage(oop + oopOffset, classInt, format, hash); prevObj.nextObject = object; this.oldSpaceCount++; prevObj = object; oopMap.set(oop, object); rawBits.set(oop + oopOffset, bits); } object.nextObject = endMarker; // add outPointers to oopMap for (var i = 0; i < outPointerArray.pointers.length; i++) oopMap.set(0x80000004 + i * 4, outPointerArray.pointers[i]); // add compactClasses to oopMap var compactClasses = this.specialObjectsArray.pointers[Squeak.splOb_CompactClasses].pointers, fakeClsOop = 0, // make up a compact-classes array with oops, as if loading an image compactClassOops = compactClasses.map(function(cls) { oopMap.set(--fakeClsOop, cls); return fakeClsOop; }); // truncate segmentWordArray array to one element segmentWordArray.words = new Uint32Array([segmentWordArray.words[0]]); delete segmentWordArray.uint8Array; // in case it was a view onto words // map objects using oopMap var roots = segmentWordArray.nextObject, floatClass = this.specialObjectsArray.pointers[Squeak.splOb_ClassFloat], obj = roots; do { obj.installFromImage(oopMap, rawBits, compactClassOops, floatClass, littleEndian, nativeFloats); obj = obj.nextObject; } while (obj !== endMarker); return roots; }, }, 'spur support', { initSpurOverrides: function() { this.registerObject = this.registerObjectSpur; this.writeToBuffer = this.writeToBufferSpur; this.storeImageSegment = this.storeImageSegmentSpur; this.loadImageSegment = this.loadImageSegmentSpur; }, spurClassTable: function(oopMap, rawBits, classPages, splObjs) { var classes = {}, nil = this.firstOldObject; // read class table pages for (var p = 0; p < 4096; p++) { var page = oopMap.get(classPages[p]); if (page.oop) page = rawBits.get(page.oop); // page was not properly hidden if (page.length === 1024) for (var i = 0; i < 1024; i++) { var entry = oopMap.get(page[i]); if (!entry) throw Error("Invalid class table entry (oop " + page[i] + ")"); if (entry !== nil) { var classIndex = p * 1024 + i; classes[classIndex] = entry; } } } // add known classes which may not be in the table for (var key in Squeak) { if (/^splOb_Class/.test(key)) { var knownClass = oopMap.get(rawBits.get(splObjs.oop)[Squeak[key]]); if (knownClass !== nil) { var classIndex = knownClass.hash; if (classIndex > 0 && classIndex < 1024) classes[classIndex] = knownClass; } } } classes[3] = classes[1]; // SmallInteger needs two entries this.classTable = classes; this.classTableIndex = 1024; // first page is special return classes; }, enterIntoClassTable: function(newClass) { var index = this.classTableIndex, table = this.classTable; while (index <= 0x3FFFFF) { if (!table[index]) { table[index] = newClass; newClass.hash = index; this.classTableIndex = index; return index; } index++; } console.error("class table full?"); // todo: clean out old class table entries return null; }, initImmediateClasses: function(oopMap, rawBits, splObs) { var special = rawBits.get(splObs.oop); this.characterClass = oopMap.get(special[Squeak.splOb_ClassCharacter]); this.floatClass = oopMap.get(special[Squeak.splOb_ClassFloat]); this.largePosIntClass = oopMap.get(special[Squeak.splOb_ClassLargePositiveInteger]); this.largeNegIntClass = oopMap.get(special[Squeak.splOb_ClassLargeNegativeInteger]); // init named prototypes this.characterClass.classInstProto("Character"); this.floatClass.classInstProto("BoxedFloat64"); this.largePosIntClass.classInstProto("LargePositiveInteger"); this.largeNegIntClass.classInstProto("LargeNegativeInteger"); this.characterTable = {}; }, getCharacter: function(unicode) { var char = this.characterTable[unicode]; if (!char) { char = new this.characterClass.instProto; char.initInstanceOfChar(this.characterClass, unicode); this.characterTable[unicode] = char; } return char; }, instantiateFloat: function(bits) { var float = new this.floatClass.instProto; this.registerObjectSpur(float); this.hasNewInstances[this.floatClass.oop] = true; float.initInstanceOfFloat(this.floatClass, bits); return float; }, instantiateLargeFromSmall: function(hi, lo) { // get rid of 3 tag bits lo = hi << 29 | lo >>> 3 ; // shift 3 bits from hi to lo hi = hi >> 3; // shift by 3 with sign extension // value is always positive, class determines sign var negative = hi < 0; if (negative) { hi = -hi; lo = -lo; if (lo !== 0) hi--; } var size = hi === 0 ? 4 : hi <= 0xFF ? 5 : hi <= 0xFFFF ? 6 : hi <= 0xFFFFFF ? 7 : 8; var largeIntClass = negative ? this.largeNegIntClass : this.largePosIntClass; var largeInt = new largeIntClass.instProto; this.registerObjectSpur(largeInt); this.hasNewInstances[largeIntClass.oop] = true; largeInt.initInstanceOfLargeInt(largeIntClass, size); var bytes = largeInt.bytes; for (var i = 0; i < 4; i++) { bytes[i] = lo & 255; lo >>= 8; } for (var i = 4; i < size; i++) { bytes[i] = hi & 255; hi >>= 8; } return largeInt; }, ensureClassesInTable: function() { // make sure all classes are in class table // answer number of class pages var obj = this.firstOldObject; var maxIndex = 1024; // at least one page while (obj) { var cls = obj.sqClass; if (cls.hash === 0) this.enterIntoClassTable(cls); if (cls.hash > maxIndex) maxIndex = cls.hash; if (this.classTable[cls.hash] !== cls) throw Error("Class not in class table"); obj = obj.nextObject; } return (maxIndex >> 10) + 1; }, classTableBytes: function(numPages) { // space needed for master table and minor pages return (4 + 4104 + numPages * (4 + 1024)) * 4; }, writeFreeLists: function(data, pos, littleEndian, oopOffset) { // we fake an empty free lists object data.setUint32(pos, 0x0A000012, littleEndian); pos += 4; data.setUint32(pos, 0x20000000, littleEndian); pos += 4; pos += 32 * 4; // 32 zeros return pos; }, writeClassTable: function(data, pos, littleEndian, objToOop, numPages) { // write class tables as Spur expects them, faking their oops var nilFalseTrueBytes = 3 * 16, freeListBytes = 8 + 32 * 4, majorTableSlots = 4096 + 8, // class pages plus 8 hiddenRootSlots minorTableSlots = 1024, majorTableBytes = 16 + majorTableSlots * 4, minorTableBytes = 16 + minorTableSlots * 4, firstPageOop = nilFalseTrueBytes + freeListBytes + majorTableBytes + 8; // major table data.setUint32(pos, majorTableSlots, littleEndian); pos += 4; data.setUint32(pos, 0xFF000000, littleEndian); pos += 4; data.setUint32(pos, 0x02000010, littleEndian); pos += 4; data.setUint32(pos, 0xFF000000, littleEndian); pos += 4; for (var p = 0; p < numPages; p++) { data.setUint32(pos, firstPageOop + p * minorTableBytes, littleEndian); pos += 4; } pos += (majorTableSlots - numPages) * 4; // rest is nil // minor tables var classID = 0; for (var p = 0; p < numPages; p++) { data.setUint32(pos, minorTableSlots, littleEndian); pos += 4; data.setUint32(pos, 0xFF000000, littleEndian); pos += 4; data.setUint32(pos, 0x02000010, littleEndian); pos += 4; data.setUint32(pos, 0xFF000000, littleEndian); pos += 4; for (var i = 0; i < minorTableSlots; i++) { var classObj = this.classTable[classID]; if (classObj && classObj.pointers) { if (!classObj.hash) throw Error("class without id"); if (classObj.hash !== classID && classID >= 32 || classObj.oop < 0) { console.warn("freeing class index " + classID + " " + classObj.className()); classObj = null; delete this.classTable[classID]; } } if (classObj) data.setUint32(pos, objToOop(classObj), littleEndian); pos += 4; classID++; } } return pos; }, writeToBufferSpur: function() { var headerSize = 64, trailerSize = 16, freeListsSize = 136, numPages = this.ensureClassesInTable(), hiddenSize = freeListsSize + this.classTableBytes(numPages), data = new DataView(new ArrayBuffer(headerSize + hiddenSize + this.oldSpaceBytes + trailerSize)), littleEndian = true, start = Date.now(), pos = 0; function writeWord(word) { data.setUint32(pos, word, littleEndian); pos += 4; } function objToOop(obj) { if (typeof obj === "number") return obj << 1 | 1; // add tag bit if (obj._format === 7) { if (obj.hash !== (obj.oop >> 2) || (obj.oop & 3) !== 2) throw Error("Bad immediate char"); return obj.oop; } if (obj.oop < 0) throw Error("temporary oop"); // oops after nil/false/true are shifted by size of hidden objects return obj.oop < 48 ? obj.oop : obj.oop + hiddenSize; } writeWord(this.formatVersion()); // magic number writeWord(headerSize); writeWord(hiddenSize + this.oldSpaceBytes + trailerSize); // end of memory writeWord(this.firstOldObject.addr()); // base addr (0) writeWord(objToOop(this.specialObjectsArray)); this.savedHeaderWords.forEach(writeWord); writeWord(hiddenSize + this.oldSpaceBytes + trailerSize); //first segment size while (pos < headerSize) writeWord(0); // write objects var obj = this.firstOldObject, n = 0; pos = obj.writeTo(data, pos, littleEndian, objToOop); obj = obj.nextObject; n++; // write nil pos = obj.writeTo(data, pos, littleEndian, objToOop); obj = obj.nextObject; n++; // write false pos = obj.writeTo(data, pos, littleEndian, objToOop); obj = obj.nextObject; n++; // write true pos = this.writeFreeLists(data, pos, littleEndian, objToOop); // write hidden free list pos = this.writeClassTable(data, pos, littleEndian, objToOop, numPages); // write hidden class table while (obj) { pos = obj.writeTo(data, pos, littleEndian, objToOop); obj = obj.nextObject; n++; } // write segement trailer writeWord(0x4A000003); writeWord(0x00800000); writeWord(0); writeWord(0); // done if (pos !== data.byteLength) throw Error("wrong image size"); if (n !== this.oldSpaceCount) throw Error("wrong object count"); var time = Date.now() - start; console.log("Wrote " + n + " objects in " + time + " ms, image size " + pos + " bytes"); return data.buffer; }, storeImageSegmentSpur: function(segmentWordArray, outPointerArray, arrayOfRoots) { // see comment in segmentVersion() if you implement this // also see markReachableObjects() about immediate chars this.vm.warnOnce("not implemented for Spur yet: primitive 98 (primitiveStoreImageSegment)"); return false; }, loadImageSegmentSpur: function(segmentWordArray, outPointerArray) { this.vm.warnOnce("not implemented for Spur yet: primitive 99 (primitiveLoadImageSegment)"); return null; }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.Interpreter', 'initialization', { initialize: function(image, display, options) { console.log('squeak: initializing interpreter ' + Squeak.vmVersion + ' (' + Squeak.vmDate + ')'); this.Squeak = Squeak; // store locally to avoid dynamic lookup in Lively this.image = image; this.image.vm = this; this.options = options || {}; this.primHandler = new Squeak.Primitives(this, display); this.loadImageState(); this.initVMState(); this.loadInitialContext(); this.hackImage(); this.initCompiler(); console.log('squeak: ready'); }, loadImageState: function() { this.specialObjects = this.image.specialObjectsArray.pointers; this.specialSelectors = this.specialObjects[Squeak.splOb_SpecialSelectors].pointers; this.nilObj = this.specialObjects[Squeak.splOb_NilObject]; this.falseObj = this.specialObjects[Squeak.splOb_FalseObject]; this.trueObj = this.specialObjects[Squeak.splOb_TrueObject]; this.hasClosures = this.image.hasClosures; this.getGlobals = this.globalsGetter(); // hack for old image that does not support Unix files if (!this.hasClosures && !this.findMethod("UnixFileDirectory class>>pathNameDelimiter")) this.primHandler.emulateMac = true; // pre-release image has inverted colors if (this.image.version == 6501) this.primHandler.reverseDisplay = true; }, initVMState: function() { this.byteCodeCount = 0; this.sendCount = 0; this.interruptCheckCounter = 0; this.interruptCheckCounterFeedBackReset = 1000; this.interruptChecksEveryNms = 3; this.lowSpaceThreshold = 1000000; this.signalLowSpace = false; this.nextPollTick = 0; this.nextWakeupTick = 0; this.lastTick = 0; this.interruptKeycode = 2094; //"cmd-." this.interruptPending = false; this.pendingFinalizationSignals = 0; this.freeContexts = this.nilObj; this.freeLargeContexts = this.nilObj; this.reclaimableContextCount = 0; this.nRecycledContexts = 0; this.nAllocatedContexts = 0; this.methodCacheSize = 1024; this.methodCacheMask = this.methodCacheSize - 1; this.methodCacheRandomish = 0; this.methodCache = []; for (var i = 0; i < this.methodCacheSize; i++) this.methodCache[i] = {lkupClass: null, selector: null, method: null, primIndex: 0, argCount: 0, mClass: null}; this.breakOutOfInterpreter = false; this.breakOutTick = 0; this.breakOnMethod = null; // method to break on this.breakOnNewMethod = false; this.breakOnMessageNotUnderstood = false; this.breakOnContextChanged = false; this.breakOnContextReturned = null; // context to break on this.messages = {}; this.startupTime = Date.now(); // base for millisecond clock }, loadInitialContext: function() { var schedAssn = this.specialObjects[Squeak.splOb_SchedulerAssociation]; var sched = schedAssn.pointers[Squeak.Assn_value]; var proc = sched.pointers[Squeak.ProcSched_activeProcess]; this.activeContext = proc.pointers[Squeak.Proc_suspendedContext]; this.activeContext.dirty = true; this.fetchContextRegisters(this.activeContext); this.reclaimableContextCount = 0; }, globalsGetter: function() { // Globals (more specifically the pointers we are interested in) might // change during execution, because a Dictionary needs growing for example. // Therefore answer a getter function to access the actual globals (pointers). // This getter can be used, even if the Dictionary has grown (and thereby the // underlying Array is replaced by a larger one), because it uses the reference // to the 'outer' Dictionary instead of the pointers to the values. var smalltalk = this.specialObjects[Squeak.splOb_SmalltalkDictionary], smalltalkClass = smalltalk.sqClass.className(); if (smalltalkClass === "Association") { smalltalk = smalltalk.pointers[1]; smalltalkClass = smalltalk.sqClass.className(); } if (smalltalkClass === "SystemDictionary") return function() { return smalltalk.pointers[1].pointers; }; if (smalltalkClass === "SmalltalkImage") { var globals = smalltalk.pointers[0], globalsClass = globals.sqClass.className(); if (globalsClass === "SystemDictionary") return function() { return globals.pointers[1].pointers; }; if (globalsClass === "Environment") return function() { return globals.pointers[2].pointers[1].pointers; }; } console.warn("cannot find global dict"); return function() { return []; }; }, initCompiler: function() { if (!Squeak.Compiler) return console.warn("Squeak.Compiler not loaded, using interpreter only"); // some JS environments disallow creating functions at runtime (e.g. FireFox OS apps) try { if (new Function("return 42")() !== 42) return console.warn("function constructor not working, disabling JIT"); } catch (e) { return console.warn("disabling JIT: " + e); } // disable JIT on slow machines, which are likely memory-limited var kObjPerSec = this.image.oldSpaceCount / (this.startupTime - this.image.startupTime); if (kObjPerSec < 10) return console.warn("Slow machine detected (loaded " + (kObjPerSec*1000|0) + " objects/sec), using interpreter only"); // compiler might decide to not handle current image try { console.log("squeak: initializing JIT compiler"); var compiler = new Squeak.Compiler(this); if (compiler.compile) this.compiler = compiler; } catch(e) { console.warn("Compiler: " + e); } if (!this.compiler) { console.warn("SqueakJS will be running in interpreter mode only (slow)"); } }, hackImage: function() { // hack methods to make work / speed up var sista = this.method.methodSignFlag(); [ // Etoys fallback for missing translation files is hugely inefficient. // This speeds up opening a viewer by 10x (!) // Remove when we added translation files. //{method: "String>>translated", primitive: returnSelf, enabled: true}, //{method: "String>>translatedInAllDomains", primitive: returnSelf, enabled: true}, // 64 bit Squeak does not flush word size on snapshot {method: "SmalltalkImage>>wordSize", literal: {index: 1, old: 8, hack: 4, skip: this.nilObj}, enabled: true}, // Squeak 5.3 disable wizard by replacing #open send with pop {method: "ReleaseBuilder class>>prepareEnvironment", bytecode: {pc: 28, old: 0xD8, hack: 0x87}, enabled: !sista & this.options.wizard===false}, // Squeak 6.0 disable wizard by replacing #openWelcomeWorkspacesWith: send with pop {method: "ReleaseBuilder class>>prepareEnvironment", bytecode: {closure: 9, pc: 5, old: 0x81, hack: 0xD8}, enabled: sista & this.options.wizard===false}, // Squeak 6.0 disable welcome workspace by replacing #open send with pop {method: "ReleaseBuilder class>>prepareEnvironment", bytecode: {closure: 9, pc: 2, old: 0x90, hack: 0xD8}, enabled: sista & this.options.welcome===false}, // Squeak source file should use UTF8 not MacRoman (both V3 and Sista) {method: "Latin1Environment class>>systemConverterClass", bytecode: {pc: 53, old: 0x45, hack: 0x49}, enabled: !this.image.isSpur}, {method: "Latin1Environment class>>systemConverterClass", bytecode: {pc: 38, old: 0x16, hack: 0x13}, enabled: this.image.isSpur && sista}, {method: "Latin1Environment class>>systemConverterClass", bytecode: {pc: 50, old: 0x44, hack: 0x48}, enabled: this.image.isSpur && !sista}, // New FFI can't detect platform – pretend to be 32 bit intel {method: "FFIPlatformDescription>>abi", literal: { index: 21, old_str: 'UNKNOWN_ABI', new_str: 'IA32'}, enabled: sista}, ].forEach(function(each) { try { var m = each.enabled && this.findMethod(each.method); if (m) { var prim = each.primitive, byte = each.bytecode, lit = each.literal, hacked = true; if (byte && byte.closure) m = m.pointers[byte.closure]; if (prim) m.pointers[0] |= prim; else if (byte && m.bytes[byte.pc] === byte.old) m.bytes[byte.pc] = byte.hack; else if (byte && m.bytes[byte.pc] === byte.hack) hacked = false; // already there else if (lit && lit.old_str && m.pointers[lit.index].bytesAsString() === lit.old_str) m.pointers[lit.index] = this.primHandler.makeStString(lit.new_str); else if (lit && m.pointers[lit.index].pointers?.[1] === lit.skip) hacked = false; // not needed else if (lit && m.pointers[lit.index].pointers?.[1] === lit.old) m.pointers[lit.index].pointers[1] = lit.hack; else if (lit && m.pointers[lit.index].pointers?.[1] === lit.hack) hacked = false; // already there else { hacked = false; console.warn("Not hacking " + each.method); } if (hacked) console.warn("Hacking " + each.method); } } catch (error) { console.error("Failed to hack " + each.method + " with error " + error); } }, this); }, }, 'interpreting', { interpretOne: function(singleStep) { if (this.method.compiled) { if (singleStep) { if (!this.compiler.enableSingleStepping(this.method)) { this.method.compiled = null; return this.interpretOne(singleStep); } this.breakNow(); } this.method.compiled(this); return; } if (this.method.methodSignFlag()) { return this.interpretOneSistaWithExtensions(singleStep, 0, 0); } var Squeak = this.Squeak; // avoid dynamic lookup of "Squeak" in Lively var b, b2; this.byteCodeCount++; b = this.nextByte(); switch (b) { /* The Main V3 Bytecode Dispatch Loop */ // load receiver variable case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: this.push(this.receiver.pointers[b&0xF]); return; // load temporary variable case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&0xF)]); return; // loadLiteral case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: this.push(this.method.methodGetLiteral(b&0x1F)); return; // loadLiteralIndirect case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: this.push((this.method.methodGetLiteral(b&0x1F)).pointers[Squeak.Assn_value]); return; // storeAndPop rcvr, temp case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: this.receiver.dirty = true; this.receiver.pointers[b&7] = this.pop(); return; case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&7)] = this.pop(); return; // Quick push case 0x70: this.push(this.receiver); return; case 0x71: this.push(this.trueObj); return; case 0x72: this.push(this.falseObj); return; case 0x73: this.push(this.nilObj); return; case 0x74: this.push(-1); return; case 0x75: this.push(0); return; case 0x76: this.push(1); return; case 0x77: this.push(2); return; // Quick return case 0x78: this.doReturn(this.receiver); return; case 0x79: this.doReturn(this.trueObj); return; case 0x7A: this.doReturn(this.falseObj); return; case 0x7B: this.doReturn(this.nilObj); return; case 0x7C: this.doReturn(this.pop()); return; case 0x7D: this.doReturn(this.pop(), this.activeContext.pointers[Squeak.BlockContext_caller]); return; // blockReturn case 0x7E: this.nono(); return; case 0x7F: this.nono(); return; // Sundry case 0x80: this.extendedPush(this.nextByte()); return; case 0x81: this.extendedStore(this.nextByte()); return; case 0x82: this.extendedStorePop(this.nextByte()); return; // singleExtendedSend case 0x83: b2 = this.nextByte(); this.send(this.method.methodGetSelector(b2&31), b2>>5, false); return; case 0x84: this.doubleExtendedDoAnything(this.nextByte()); return; // singleExtendedSendToSuper case 0x85: b2= this.nextByte(); this.send(this.method.methodGetSelector(b2&31), b2>>5, true); return; // secondExtendedSend case 0x86: b2= this.nextByte(); this.send(this.method.methodGetSelector(b2&63), b2>>6, false); return; case 0x87: this.pop(); return; // pop case 0x88: this.push(this.top()); return; // dup // thisContext case 0x89: this.push(this.exportThisContext()); return; // Closures case 0x8A: this.pushNewArray(this.nextByte()); // create new temp vector return; case 0x8B: this.callPrimBytecode(0x81); return; case 0x8C: b2 = this.nextByte(); // remote push from temp vector this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()].pointers[b2]); return; case 0x8D: b2 = this.nextByte(); // remote store into temp vector var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; vec.pointers[b2] = this.top(); vec.dirty = true; return; case 0x8E: b2 = this.nextByte(); // remote store and pop into temp vector var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; vec.pointers[b2] = this.pop(); vec.dirty = true; return; case 0x8F: this.pushClosureCopy(); return; // Short jmp case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: this.pc += (b&7)+1; return; // Short conditional jump on false case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: this.jumpIfFalse((b&7)+1); return; // Long jump, forward and back case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: b2 = this.nextByte(); this.pc += (((b&7)-4)*256 + b2); if ((b&7)<4) // check for process switch on backward jumps (loops) if (this.interruptCheckCounter-- <= 0) this.checkForInterrupts(); return; // Long conditional jump on true case 0xA8: case 0xA9: case 0xAA: case 0xAB: this.jumpIfTrue((b&3)*256 + this.nextByte()); return; // Long conditional jump on false case 0xAC: case 0xAD: case 0xAE: case 0xAF: this.jumpIfFalse((b&3)*256 + this.nextByte()); return; // Arithmetic Ops... + - < > <= >= = ~= * / @ lshift: lxor: land: lor: case 0xB0: this.success = true; this.resultIsFloat = false; if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) + this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // PLUS + case 0xB1: this.success = true; this.resultIsFloat = false; if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) - this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // MINUS - case 0xB2: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) < this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LESS < case 0xB3: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) > this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GRTR > case 0xB4: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) <= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LEQ <= case 0xB5: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) >= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GEQ >= case 0xB6: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) === this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // EQU = case 0xB7: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) !== this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // NEQ ~= case 0xB8: this.success = true; this.resultIsFloat = false; if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) * this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // TIMES * case 0xB9: this.success = true; if (!this.pop2AndPushIntResult(this.quickDivide(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide / case 0xBA: this.success = true; if (!this.pop2AndPushIntResult(this.mod(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // MOD \ case 0xBB: this.success = true; if (!this.primHandler.primitiveMakePoint(1, true)) this.sendSpecial(b&0xF); return; // MakePt int@int case 0xBC: this.success = true; if (!this.pop2AndPushIntResult(this.safeShift(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // bitShift: case 0xBD: this.success = true; if (!this.pop2AndPushIntResult(this.div(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide // case 0xBE: this.success = true; if (!this.pop2AndPushIntResult(this.stackInteger(1) & this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitAnd: case 0xBF: this.success = true; if (!this.pop2AndPushIntResult(this.stackInteger(1) | this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitOr: // at:, at:put:, size, next, nextPut:, ... case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: if (!this.primHandler.quickSendOther(this.receiver, b&0xF)) this.sendSpecial((b&0xF)+16); return; // Send Literal Selector with 0, 1, and 2 args case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: this.send(this.method.methodGetSelector(b&0xF), 0, false); return; case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: this.send(this.method.methodGetSelector(b&0xF), 1, false); return; case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: this.send(this.method.methodGetSelector(b&0xF), 2, false); return; } throw Error("not a bytecode: " + b); }, interpretOneSistaWithExtensions: function(singleStep, extA, extB) { var Squeak = this.Squeak; // avoid dynamic lookup of "Squeak" in Lively var b, b2; this.byteCodeCount++; b = this.nextByte(); switch (b) { /* The Main Sista Bytecode Dispatch Loop */ // 1 Byte Bytecodes // load receiver variable case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: this.push(this.receiver.pointers[b&0xF]); return; // load literal variable case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: this.push((this.method.methodGetLiteral(b&0xF)).pointers[Squeak.Assn_value]); return; // load literal constant case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: this.push(this.method.methodGetLiteral(b&0x1F)); return; // load temporary variable case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&0x7)]); return; case 0x48: case 0x49: case 0x4A: case 0x4B: this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&0x3)+8]); return; case 0x4C: this.push(this.receiver); return; case 0x4D: this.push(this.trueObj); return; case 0x4E: this.push(this.falseObj); return; case 0x4F: this.push(this.nilObj); return; case 0x50: this.push(0); return; case 0x51: this.push(1); return; case 0x52: if (extB == 0) { this.push(this.exportThisContext()); return; } else { this.nono(); return; } case 0x53: this.push(this.top()); return; case 0x54: case 0x55: case 0x56: case 0x57: this.nono(); return; // unused case 0x58: this.doReturn(this.receiver); return; case 0x59: this.doReturn(this.trueObj); return; case 0x5A: this.doReturn(this.falseObj); return; case 0x5B: this.doReturn(this.nilObj); return; case 0x5C: this.doReturn(this.pop()); return; case 0x5D: this.doReturn(this.nilObj, this.activeContext.pointers[Squeak.BlockContext_caller]); return; // blockReturn nil case 0x5E: if (extA == 0) { this.doReturn(this.pop(), this.activeContext.pointers[Squeak.BlockContext_caller]); return; // blockReturn } else { this.nono(); return; } case 0x5F: return; // nop // Arithmetic Ops... + - < > <= >= = ~= * / @ lshift: lxor: land: lor: case 0x60: this.success = true; this.resultIsFloat = false; if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) + this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // PLUS + case 0x61: this.success = true; this.resultIsFloat = false; if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) - this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // MINUS - case 0x62: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) < this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LESS < case 0x63: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) > this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GRTR > case 0x64: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) <= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // LEQ <= case 0x65: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) >= this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // GEQ >= case 0x66: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) === this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // EQU = case 0x67: this.success = true; if (!this.pop2AndPushBoolResult(this.stackIntOrFloat(1) !== this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // NEQ ~= case 0x68: this.success = true; this.resultIsFloat = false; if (!this.pop2AndPushNumResult(this.stackIntOrFloat(1) * this.stackIntOrFloat(0))) this.sendSpecial(b&0xF); return; // TIMES * case 0x69: this.success = true; if (!this.pop2AndPushIntResult(this.quickDivide(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide / case 0x6A: this.success = true; if (!this.pop2AndPushIntResult(this.mod(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // MOD \ case 0x6B: this.success = true; if (!this.primHandler.primitiveMakePoint(1, true)) this.sendSpecial(b&0xF); return; // MakePt int@int case 0x6C: this.success = true; if (!this.pop2AndPushIntResult(this.safeShift(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // bitShift: case 0x6D: this.success = true; if (!this.pop2AndPushIntResult(this.div(this.stackInteger(1),this.stackInteger(0)))) this.sendSpecial(b&0xF); return; // Divide // case 0x6E: this.success = true; if (!this.pop2AndPushIntResult(this.stackInteger(1) & this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitAnd: case 0x6F: this.success = true; if (!this.pop2AndPushIntResult(this.stackInteger(1) | this.stackInteger(0))) this.sendSpecial(b&0xF); return; // bitOr: // at:, at:put:, size, next, nextPut:, ... case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: if (!this.primHandler.quickSendOther(this.receiver, b&0xF)) this.sendSpecial((b&0xF)+16); return; // Send Literal Selector with 0, 1, and 2 args case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: this.send(this.method.methodGetSelector(b&0xF), 0, false); return; case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: this.send(this.method.methodGetSelector(b&0xF), 1, false); return; case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: this.send(this.method.methodGetSelector(b&0xF), 2, false); return; // Short jmp case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: this.pc += (b&7)+1; return; // Short conditional jump on true case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: this.jumpIfTrue((b&7)+1); return; // Short conditional jump on false case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: this.jumpIfFalse((b&7)+1); return; // storeAndPop rcvr, temp case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: this.receiver.dirty = true; this.receiver.pointers[b&7] = this.pop(); return; case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: this.homeContext.pointers[Squeak.Context_tempFrameStart+(b&7)] = this.pop(); return; case 0xD8: this.pop(); return; // pop case 0xD9: this.nono(); return; // FIXME: Unconditional trap case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: this.nono(); return; // unused // 2 Byte Bytecodes case 0xE0: b2 = this.nextByte(); this.interpretOneSistaWithExtensions(singleStep, (extA << 8) + b2, extB); return; case 0xE1: b2 = this.nextByte(); this.interpretOneSistaWithExtensions(singleStep, extA, (extB << 8) + (b2 < 128 ? b2 : b2-256)); return; case 0xE2: b2 = this.nextByte(); this.push(this.receiver.pointers[b2 + (extA << 8)]); return; case 0xE3: b2 = this.nextByte(); this.push((this.method.methodGetLiteral(b2 + (extA << 8))).pointers[Squeak.Assn_value]); return; case 0xE4: b2 = this.nextByte(); this.push(this.method.methodGetLiteral(b2 + (extA << 8))); return; case 0xE5: b2 = this.nextByte(); this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+b2]); return; case 0xE6: this.nono(); return; // unused case 0xE7: this.pushNewArray(this.nextByte()); return; // create new temp vector case 0xE8: b2 = this.nextByte(); this.push(b2 + (extB << 8)); return; // push SmallInteger case 0xE9: b2 = this.nextByte(); this.push(this.image.getCharacter(b2 + (extB << 8))); return; // push Character case 0xEA: b2 = this.nextByte(); this.send(this.method.methodGetSelector((b2 >> 3) + (extA << 5)), (b2 & 7) + (extB << 3), false); return; case 0xEB: b2 = this.nextByte(); var literal = this.method.methodGetSelector((b2 >> 3) + (extA << 5)); if (extB >= 64) { this.sendSuperDirected(literal, (b2 & 7) + ((extB & 63) << 3)); return; } else { this.send(literal, (b2 & 7) + (extB << 3), true); return; } case 0xEC: this.nono(); return; // unused case 0xED: // long jump, forward and back var offset = this.nextByte() + (extB << 8); this.pc += offset; if (offset < 0) // check for process switch on backward jumps (loops) if (this.interruptCheckCounter-- <= 0) this.checkForInterrupts(); return; case 0xEE: // long conditional jump on true this.jumpIfTrue(this.nextByte() + (extB << 8)); return; case 0xEF: // long conditional jump on false this.jumpIfFalse(this.nextByte() + (extB << 8)); return; case 0xF0: // pop into receiver this.receiver.dirty = true; this.receiver.pointers[this.nextByte() + (extA << 8)] = this.pop(); return; case 0xF1: // pop into literal var assoc = this.method.methodGetLiteral(this.nextByte() + (extA << 8)); assoc.dirty = true; assoc.pointers[Squeak.Assn_value] = this.pop(); return; case 0xF2: // pop into temp this.homeContext.pointers[Squeak.Context_tempFrameStart + this.nextByte()] = this.pop(); return; case 0xF3: // store into receiver this.receiver.dirty = true; this.receiver.pointers[this.nextByte() + (extA << 8)] = this.top(); return; case 0xF4: // store into literal var assoc = this.method.methodGetLiteral(this.nextByte() + (extA << 8)); assoc.dirty = true; assoc.pointers[Squeak.Assn_value] = this.top(); return; case 0xF5: // store into temp this.homeContext.pointers[Squeak.Context_tempFrameStart + this.nextByte()] = this.top(); return; case 0xF6: case 0xF7: this.nono(); return; // unused // 3 Byte Bytecodes case 0xF8: this.callPrimBytecode(0xF5); return; case 0xF9: this.pushFullClosure(extA); return; case 0xFA: this.pushClosureCopyExtended(extA, extB); return; case 0xFB: b2 = this.nextByte(); // remote push from temp vector this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()].pointers[b2]); return; case 0xFC: b2 = this.nextByte(); // remote store into temp vector var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; vec.pointers[b2] = this.top(); vec.dirty = true; return; case 0xFD: b2 = this.nextByte(); // remote store and pop into temp vector var vec = this.homeContext.pointers[Squeak.Context_tempFrameStart+this.nextByte()]; vec.pointers[b2] = this.pop(); vec.dirty = true; return; case 0xFE: case 0xFF: this.nono(); return; // unused } throw Error("not a bytecode: " + b); }, interpret: function(forMilliseconds, thenDo) { // run for a couple milliseconds (but only until idle or break) // answer milliseconds to sleep (until next timer wakeup) // or 'break' if reached breakpoint // call thenDo with that result when done if (this.frozen) return 'frozen'; this.isIdle = false; this.breakOutOfInterpreter = false; this.breakAfter(forMilliseconds || 500); while (this.breakOutOfInterpreter === false) if (this.method.compiled) { this.method.compiled(this); } else { this.interpretOne(); } // this is to allow 'freezing' the interpreter and restarting it asynchronously. See freeze() if (typeof this.breakOutOfInterpreter == "function") return this.breakOutOfInterpreter(thenDo); // normally, we answer regularly var result = this.breakOutOfInterpreter == 'break' ? 'break' : !this.isIdle ? 0 : !this.nextWakeupTick ? 'sleep' // all processes waiting : Math.max(1, this.nextWakeupTick - this.primHandler.millisecondClockValue()); if (thenDo) thenDo(result); return result; }, goIdle: function() { // make sure we tend to pending delays var hadTimer = this.nextWakeupTick !== 0; this.forceInterruptCheck(); this.checkForInterrupts(); var hasTimer = this.nextWakeupTick !== 0; // go idle unless a timer just expired this.isIdle = hasTimer || !hadTimer; this.breakOut(); }, freeze: function(frozenDo) { // Stop the interpreter. Answer a function that can be // called to continue interpreting. // Optionally, frozenDo is called asynchronously when frozen var continueFunc; this.frozen = true; this.breakOutOfInterpreter = function(thenDo) { if (!thenDo) throw Error("need function to restart interpreter"); continueFunc = thenDo; return "frozen"; }.bind(this); var unfreeze = function() { this.frozen = false; if (!continueFunc) throw Error("no continue function"); continueFunc(0); //continue without timeout }.bind(this); if (frozenDo) self.setTimeout(function(){frozenDo(unfreeze);}, 0); return unfreeze; }, breakOut: function() { this.breakOutOfInterpreter = this.breakOutOfInterpreter || true; // do not overwrite break string }, nextByte: function() { return this.method.bytes[this.pc++]; }, nono: function() { throw Error("Oh No!"); }, forceInterruptCheck: function() { this.interruptCheckCounter = -1e3; }, checkForInterrupts: function() { //Check for interrupts at sends and backward jumps var now = this.primHandler.millisecondClockValue(); if (now < this.lastTick) { // millisecond clock wrapped this.nextPollTick = now + (this.nextPollTick - this.lastTick); this.breakOutTick = now + (this.breakOutTick - this.lastTick); if (this.nextWakeupTick !== 0) this.nextWakeupTick = now + (this.nextWakeupTick - this.lastTick); } //Feedback logic attempts to keep interrupt response around 3ms... if (this.interruptCheckCounter > -100) { // only if not a forced check if ((now - this.lastTick) < this.interruptChecksEveryNms) { //wrapping is not a concern this.interruptCheckCounterFeedBackReset += 10; } else { // do a thousand sends even if we are too slow for 3ms if (this.interruptCheckCounterFeedBackReset <= 1000) this.interruptCheckCounterFeedBackReset = 1000; else this.interruptCheckCounterFeedBackReset -= 12; } } this.interruptCheckCounter = this.interruptCheckCounterFeedBackReset; //reset the interrupt check counter this.lastTick = now; //used to detect wraparound of millisecond clock if (this.signalLowSpace) { this.signalLowSpace = false; // reset flag var sema = this.specialObjects[Squeak.splOb_TheLowSpaceSemaphore]; if (!sema.isNil) this.primHandler.synchronousSignal(sema); } // if (now >= nextPollTick) { // ioProcessEvents(); //sets interruptPending if interrupt key pressed // nextPollTick= now + 500; } //msecs to wait before next call to ioProcessEvents" if (this.interruptPending) { this.interruptPending = false; //reset interrupt flag var sema = this.specialObjects[Squeak.splOb_TheInterruptSemaphore]; if (!sema.isNil) this.primHandler.synchronousSignal(sema); } if ((this.nextWakeupTick !== 0) && (now >= this.nextWakeupTick)) { this.nextWakeupTick = 0; //reset timer interrupt var sema = this.specialObjects[Squeak.splOb_TheTimerSemaphore]; if (!sema.isNil) this.primHandler.synchronousSignal(sema); } if (this.pendingFinalizationSignals > 0) { //signal any pending finalizations var sema = this.specialObjects[Squeak.splOb_TheFinalizationSemaphore]; this.pendingFinalizationSignals = 0; if (!sema.isNil) this.primHandler.synchronousSignal(sema); } if (this.primHandler.semaphoresToSignal.length > 0) this.primHandler.signalExternalSemaphores(); // signal pending semaphores, if any // if this is a long-running do-it, compile it if (!this.method.compiled) this.compileIfPossible(this.method); // have to return to web browser once in a while if (now >= this.breakOutTick) this.breakOut(); }, extendedPush: function(nextByte) { var lobits = nextByte & 63; switch (nextByte>>6) { case 0: this.push(this.receiver.pointers[lobits]);break; case 1: this.push(this.homeContext.pointers[Squeak.Context_tempFrameStart+lobits]); break; case 2: this.push(this.method.methodGetLiteral(lobits)); break; case 3: this.push(this.method.methodGetLiteral(lobits).pointers[Squeak.Assn_value]); break; } }, extendedStore: function( nextByte) { var lobits = nextByte & 63; switch (nextByte>>6) { case 0: this.receiver.dirty = true; this.receiver.pointers[lobits] = this.top(); break; case 1: this.homeContext.pointers[Squeak.Context_tempFrameStart+lobits] = this.top(); break; case 2: this.nono(); break; case 3: var assoc = this.method.methodGetLiteral(lobits); assoc.dirty = true; assoc.pointers[Squeak.Assn_value] = this.top(); break; } }, extendedStorePop: function(nextByte) { var lobits = nextByte & 63; switch (nextByte>>6) { case 0: this.receiver.dirty = true; this.receiver.pointers[lobits] = this.pop(); break; case 1: this.homeContext.pointers[Squeak.Context_tempFrameStart+lobits] = this.pop(); break; case 2: this.nono(); break; case 3: var assoc = this.method.methodGetLiteral(lobits); assoc.dirty = true; assoc.pointers[Squeak.Assn_value] = this.pop(); break; } }, doubleExtendedDoAnything: function(byte2) { var byte3 = this.nextByte(); switch (byte2>>5) { case 0: this.send(this.method.methodGetSelector(byte3), byte2&31, false); break; case 1: this.send(this.method.methodGetSelector(byte3), byte2&31, true); break; case 2: this.push(this.receiver.pointers[byte3]); break; case 3: this.push(this.method.methodGetLiteral(byte3)); break; case 4: this.push(this.method.methodGetLiteral(byte3).pointers[Squeak.Assn_value]); break; case 5: this.receiver.dirty = true; this.receiver.pointers[byte3] = this.top(); break; case 6: this.receiver.dirty = true; this.receiver.pointers[byte3] = this.pop(); break; case 7: var assoc = this.method.methodGetLiteral(byte3); assoc.dirty = true; assoc.pointers[Squeak.Assn_value] = this.top(); break; } }, jumpIfTrue: function(delta) { var top = this.pop(); if (top.isTrue) {this.pc += delta; return;} if (top.isFalse) return; this.push(top); //Uh-oh it's not even a boolean (that we know of ;-). Restore stack... this.send(this.specialObjects[Squeak.splOb_SelectorMustBeBoolean], 0, false); }, jumpIfFalse: function(delta) { var top = this.pop(); if (top.isFalse) {this.pc += delta; return;} if (top.isTrue) return; this.push(top); //Uh-oh it's not even a boolean (that we know of ;-). Restore stack... this.send(this.specialObjects[Squeak.splOb_SelectorMustBeBoolean], 0, false); }, sendSpecial: function(lobits) { this.send(this.specialSelectors[lobits*2], this.specialSelectors[(lobits*2)+1], false); //specialSelectors is {...sel,nArgs,sel,nArgs,...) }, callPrimBytecode: function(extendedStoreBytecode) { this.pc += 2; // skip over primitive number if (this.primFailCode) { if (this.method.bytes[this.pc] === extendedStoreBytecode) this.stackTopPut(this.getErrorObjectFromPrimFailCode()); this.primFailCode = 0; } }, getErrorObjectFromPrimFailCode: function() { var primErrTable = this.specialObjects[Squeak.splOb_PrimErrTableIndex]; if (primErrTable && primErrTable.pointers) { var errorObject = primErrTable.pointers[this.primFailCode - 1]; if (errorObject) return errorObject; } return this.primFailCode; }, }, 'closures', { pushNewArray: function(nextByte) { var popValues = nextByte > 127, count = nextByte & 127, array = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassArray], count); if (popValues) { for (var i = 0; i < count; i++) array.pointers[i] = this.stackValue(count - i - 1); this.popN(count); } this.push(array); }, pushClosureCopy: function() { // The compiler has pushed the values to be copied, if any. Find numArgs and numCopied in the byte following. // Create a Closure with space for the copiedValues and pop numCopied values off the stack into the closure. // Set numArgs as specified, and set startpc to the pc following the block size and jump over that code. var numArgsNumCopied = this.nextByte(), numArgs = numArgsNumCopied & 0xF, numCopied = numArgsNumCopied >> 4, blockSizeHigh = this.nextByte(), blockSize = blockSizeHigh * 256 + this.nextByte(), initialPC = this.encodeSqueakPC(this.pc, this.method), closure = this.newClosure(numArgs, initialPC, numCopied); closure.pointers[Squeak.Closure_outerContext] = this.activeContext; this.reclaimableContextCount = 0; // The closure refers to thisContext so it can't be reclaimed if (numCopied > 0) { for (var i = 0; i < numCopied; i++) closure.pointers[Squeak.Closure_firstCopiedValue + i] = this.stackValue(numCopied - i - 1); this.popN(numCopied); } this.pc += blockSize; this.push(closure); }, pushClosureCopyExtended: function(extA, extB) { var byteA = this.nextByte(); var byteB = this.nextByte(); var numArgs = (byteA & 7) + this.mod(extA, 16) * 8, numCopied = (byteA >> 3 & 0x7) + this.div(extA, 16) * 8, blockSize = byteB + (extB << 8), initialPC = this.encodeSqueakPC(this.pc, this.method), closure = this.newClosure(numArgs, initialPC, numCopied); closure.pointers[Squeak.Closure_outerContext] = this.activeContext; this.reclaimableContextCount = 0; // The closure refers to thisContext so it can't be reclaimed if (numCopied > 0) { for (var i = 0; i < numCopied; i++) closure.pointers[Squeak.Closure_firstCopiedValue + i] = this.stackValue(numCopied - i - 1); this.popN(numCopied); } this.pc += blockSize; this.push(closure); }, pushFullClosure: function(extA) { var byteA = this.nextByte(); var byteB = this.nextByte(); var literalIndex = byteA + (extA << 8); var numCopied = byteB & 63; var context; if ((byteB >> 6 & 1) == 1) { context = this.vm.nilObj; } else { context = this.activeContext; } var compiledBlock = this.method.methodGetLiteral(literalIndex); var closure = this.newFullClosure(context, numCopied, compiledBlock); if ((byteB >> 7 & 1) == 1) { throw Error("on-stack receiver not yet supported"); } else { closure.pointers[Squeak.ClosureFull_receiver] = this.receiver; } this.reclaimableContextCount = 0; // The closure refers to thisContext so it can't be reclaimed if (numCopied > 0) { for (var i = 0; i < numCopied; i++) closure.pointers[Squeak.ClosureFull_firstCopiedValue + i] = this.stackValue(numCopied - i - 1); this.popN(numCopied); } this.push(closure); }, newClosure: function(numArgs, initialPC, numCopied) { var closure = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassBlockClosure], numCopied); closure.pointers[Squeak.Closure_startpc] = initialPC; closure.pointers[Squeak.Closure_numArgs] = numArgs; return closure; }, newFullClosure: function(context, numCopied, compiledBlock) { var closure = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassFullBlockClosure], numCopied); closure.pointers[Squeak.Closure_outerContext] = context; closure.pointers[Squeak.ClosureFull_method] = compiledBlock; closure.pointers[Squeak.Closure_numArgs] = compiledBlock.methodNumArgs(); return closure; }, }, 'sending', { send: function(selector, argCount, doSuper) { var newRcvr = this.stackValue(argCount); var lookupClass; if (doSuper) { lookupClass = this.method.methodClassForSuper(); lookupClass = lookupClass.superclass(); } else { lookupClass = this.getClass(newRcvr); } var entry = this.findSelectorInClass(selector, argCount, lookupClass); if (entry.primIndex) { //note details for verification of at/atput primitives this.verifyAtSelector = selector; this.verifyAtClass = lookupClass; } this.executeNewMethod(newRcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); }, sendSuperDirected: function(selector, argCount) { var lookupClass = this.pop().superclass(); var newRcvr = this.stackValue(argCount); var entry = this.findSelectorInClass(selector, argCount, lookupClass); if (entry.primIndex) { //note details for verification of at/atput primitives this.verifyAtSelector = selector; this.verifyAtClass = lookupClass; } this.executeNewMethod(newRcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); }, sendAsPrimitiveFailure: function(rcvr, method, argCount) { this.executeNewMethod(rcvr, method, argCount, 0); }, /** * @param {*} trueArgCount The number of arguments for the method to be found * @param {*} argCount The number of arguments currently on the stack (may be different from trueArgCount in the context of primitive 84 etc.) */ findSelectorInClass: function(selector, trueArgCount, startingClass, argCount = trueArgCount) { this.currentSelector = selector; // for primitiveInvokeObjectAsMethod var cacheEntry = this.findMethodCacheEntry(selector, startingClass); if (cacheEntry.method) return cacheEntry; // Found it in the method cache var currentClass = startingClass; var mDict; while (!currentClass.isNil) { mDict = currentClass.pointers[Squeak.Class_mdict]; if (mDict.isNil) { // MethodDict pointer is nil (hopefully due a swapped out stub) // -- send #cannotInterpret: var cantInterpSel = this.specialObjects[Squeak.splOb_SelectorCannotInterpret], cantInterpMsg = this.createActualMessage(selector, trueArgCount, startingClass); this.popNandPush(argCount + 1, cantInterpMsg); return this.findSelectorInClass(cantInterpSel, 1, currentClass.superclass()); } var newMethod = this.lookupSelectorInDict(mDict, selector); if (!newMethod.isNil) { cacheEntry.method = newMethod; if (newMethod.isMethod()) { cacheEntry.primIndex = newMethod.methodPrimitiveIndex(); cacheEntry.argCount = newMethod.methodNumArgs(); } else { // if method is not actually a CompiledMethod, let primitiveInvokeObjectAsMethod (576) handle it cacheEntry.primIndex = 576; cacheEntry.argCount = trueArgCount; } cacheEntry.mClass = currentClass; return cacheEntry; } currentClass = currentClass.superclass(); } //Cound not find a normal message -- send #doesNotUnderstand: var dnuSel = this.specialObjects[Squeak.splOb_SelectorDoesNotUnderstand]; if (selector === dnuSel) // Cannot find #doesNotUnderstand: -- unrecoverable error. throw Error("Recursive not understood error encountered"); var dnuMsg = this.createActualMessage(selector, trueArgCount, startingClass); // The argument to doesNotUnderstand: if (this.breakOnMessageNotUnderstood) { var receiver = this.stackValue(argCount); this.breakNow("Message not understood: " + receiver + " " + startingClass.className() + ">>" + selector.bytesAsString()); } this.popNandPush(argCount, dnuMsg); return this.findSelectorInClass(dnuSel, 1, startingClass); }, lookupSelectorInDict: function(mDict, messageSelector) { //Returns a method or nilObject var dictSize = mDict.pointersSize(); var mask = (dictSize - Squeak.MethodDict_selectorStart) - 1; var index = (mask & messageSelector.hash) + Squeak.MethodDict_selectorStart; // If there are no nils (should always be), then stop looping on second wrap. var hasWrapped = false; while (true) { var nextSelector = mDict.pointers[index]; if (nextSelector === messageSelector) { var methArray = mDict.pointers[Squeak.MethodDict_array]; return methArray.pointers[index - Squeak.MethodDict_selectorStart]; } if (nextSelector.isNil) return this.nilObj; if (++index === dictSize) { if (hasWrapped) return this.nilObj; index = Squeak.MethodDict_selectorStart; hasWrapped = true; } } }, executeNewMethod: function(newRcvr, newMethod, argumentCount, primitiveIndex, optClass, optSel) { this.sendCount++; if (newMethod === this.breakOnMethod) this.breakNow("executing method " + this.printMethod(newMethod, optClass, optSel)); if (this.logSends) { var indent = ' '; var ctx = this.activeContext; while (!ctx.isNil) { indent += '| '; ctx = ctx.pointers[Squeak.Context_sender]; } var args = this.activeContext.pointers.slice(this.sp + 1 - argumentCount, this.sp + 1); console.log(this.sendCount + indent + this.printMethod(newMethod, optClass, optSel, args)); } if (this.breakOnContextChanged) { this.breakOnContextChanged = false; this.breakNow(); } if (primitiveIndex > 0) if (this.tryPrimitive(primitiveIndex, argumentCount, newMethod)) return; //Primitive succeeded -- end of story var newContext = this.allocateOrRecycleContext(newMethod.methodNeedsLargeFrame()); var tempCount = newMethod.methodTempCount(); var newPC = 0; // direct zero-based index into byte codes var newSP = Squeak.Context_tempFrameStart + tempCount - 1; // direct zero-based index into context pointers newContext.pointers[Squeak.Context_method] = newMethod; //Following store is in case we alloc without init; all other fields get stored newContext.pointers[Squeak.BlockContext_initialIP] = this.nilObj; newContext.pointers[Squeak.Context_sender] = this.activeContext; //Copy receiver and args to new context //Note this statement relies on the receiver slot being contiguous with args... this.arrayCopy(this.activeContext.pointers, this.sp-argumentCount, newContext.pointers, Squeak.Context_tempFrameStart-1, argumentCount+1); //...and fill the remaining temps with nil this.arrayFill(newContext.pointers, Squeak.Context_tempFrameStart+argumentCount, Squeak.Context_tempFrameStart+tempCount, this.nilObj); this.popN(argumentCount+1); this.reclaimableContextCount++; this.storeContextRegisters(); /////// Woosh ////// this.activeContext = newContext; //We're off and running... //Following are more efficient than fetchContextRegisters() in newActiveContext() this.activeContext.dirty = true; this.homeContext = newContext; this.method = newMethod; this.pc = newPC; this.sp = newSP; this.receiver = newContext.pointers[Squeak.Context_receiver]; if (this.receiver !== newRcvr) throw Error("receivers don't match"); if (!newMethod.compiled) this.compileIfPossible(newMethod, optClass, optSel); // check for process switch on full method activation if (this.interruptCheckCounter-- <= 0) this.checkForInterrupts(); }, compileIfPossible(newMethod, optClass, optSel) { if (!newMethod.compiled && this.compiler) { this.compiler.compile(newMethod, optClass, optSel); } }, doReturn: function(returnValue, targetContext) { // get sender from block home or closure's outerContext if (!targetContext) { var ctx = this.homeContext; if (this.hasClosures) { var closure; while (!(closure = ctx.pointers[Squeak.Context_closure]).isNil) ctx = closure.pointers[Squeak.Closure_outerContext]; } targetContext = ctx.pointers[Squeak.Context_sender]; } if (targetContext.isNil || targetContext.pointers[Squeak.Context_instructionPointer].isNil) return this.cannotReturn(returnValue); // search up stack for unwind var thisContext = this.activeContext.pointers[Squeak.Context_sender]; while (thisContext !== targetContext) { if (thisContext.isNil) return this.cannotReturn(returnValue); if (this.isUnwindMarked(thisContext)) return this.aboutToReturnThrough(returnValue, thisContext); thisContext = thisContext.pointers[Squeak.Context_sender]; } // no unwind to worry about, just peel back the stack (usually just to sender) var nextContext; thisContext = this.activeContext; while (thisContext !== targetContext) { if (this.breakOnContextReturned === thisContext) { this.breakOnContextReturned = null; this.breakNow(); } nextContext = thisContext.pointers[Squeak.Context_sender]; thisContext.pointers[Squeak.Context_sender] = this.nilObj; thisContext.pointers[Squeak.Context_instructionPointer] = this.nilObj; if (this.reclaimableContextCount > 0) { this.reclaimableContextCount--; this.recycleIfPossible(thisContext); } thisContext = nextContext; } this.activeContext = thisContext; this.activeContext.dirty = true; this.fetchContextRegisters(this.activeContext); this.push(returnValue); if (this.breakOnContextChanged) { this.breakOnContextChanged = false; this.breakNow(); } }, aboutToReturnThrough: function(resultObj, aContext) { this.push(this.exportThisContext()); this.push(resultObj); this.push(aContext); var aboutToReturnSel = this.specialObjects[Squeak.splOb_SelectorAboutToReturn]; this.send(aboutToReturnSel, 2); }, cannotReturn: function(resultObj) { this.push(this.exportThisContext()); this.push(resultObj); var cannotReturnSel = this.specialObjects[Squeak.splOb_SelectorCannotReturn]; this.send(cannotReturnSel, 1); }, tryPrimitive: function(primIndex, argCount, newMethod) { if ((primIndex > 255) && (primIndex < 520)) { if (primIndex >= 264) {//return instvars this.popNandPush(1, this.top().pointers[primIndex - 264]); return true; } switch (primIndex) { case 256: //return self return true; case 257: this.popNandPush(1, this.trueObj); //return true return true; case 258: this.popNandPush(1, this.falseObj); //return false return true; case 259: this.popNandPush(1, this.nilObj); //return nil return true; } this.popNandPush(1, primIndex - 261); //return -1...2 return true; } var sp = this.sp; var context = this.activeContext; var success = this.primHandler.doPrimitive(primIndex, argCount, newMethod); if (success && this.sp !== sp - argCount && context === this.activeContext && primIndex !== 117 // named prims are checked separately (see namedPrimitive) && primIndex !== 118 // primitiveDoPrimitiveWithArgs (will call tryPrimitive again) && primIndex !== 218 // primitiveDoNamedPrimitive (will call namedPrimitive) && !this.frozen) { this.warnOnce("stack unbalanced after primitive " + primIndex, "error"); } return success; }, createActualMessage: function(selector, argCount, cls) { //Bundle up receiver, args and selector as a messageObject var message = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassMessage], 0); var argArray = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassArray], argCount); this.arrayCopy(this.activeContext.pointers, this.sp-argCount+1, argArray.pointers, 0, argCount); //copy args from stack message.pointers[Squeak.Message_selector] = selector; message.pointers[Squeak.Message_arguments] = argArray; if (message.pointers.length > Squeak.Message_lookupClass) //Early versions don't have lookupClass message.pointers[Squeak.Message_lookupClass] = cls; return message; }, primitivePerform: function(argCount) { var selector = this.stackValue(argCount-1); var rcvr = this.stackValue(argCount); var trueArgCount = argCount - 1; var entry = this.findSelectorInClass(selector, trueArgCount, this.getClass(rcvr), argCount); if (entry.selector === selector) { // selector has been found, rearrange stack if (entry.argCount !== trueArgCount) return false; var stack = this.activeContext.pointers; // slide eveything down... var selectorIndex = this.sp - trueArgCount; this.arrayCopy(stack, selectorIndex+1, stack, selectorIndex, trueArgCount); this.sp--; // adjust sp accordingly } else { // stack has already been arranged for #doesNotUnderstand:/#cannotInterpret: rcvr = this.stackValue(entry.argCount); } this.executeNewMethod(rcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); return true; }, primitivePerformWithArgs: function(argCount, supered) { var rcvrPos = supered ? 3 : 2; var rcvr = this.stackValue(rcvrPos); var selector = this.stackValue(rcvrPos - 1); var args = this.stackValue(rcvrPos - 2); if (args.sqClass !== this.specialObjects[Squeak.splOb_ClassArray]) return false; var lookupClass = supered ? this.top() : this.getClass(rcvr); if (supered) { // verify that lookupClass is in fact in superclass chain of receiver; var cls = this.getClass(rcvr); while (cls !== lookupClass) { cls = cls.superclass(); if (cls.isNil) return false; } } var trueArgCount = args.pointersSize(); var entry = this.findSelectorInClass(selector, trueArgCount, lookupClass, argCount); if (entry.selector === selector) { // selector has been found, rearrange stack if (entry.argCount !== trueArgCount) return false; var stack = this.activeContext.pointers; var selectorIndex = this.sp - (argCount - 1); stack[selectorIndex - 1] = rcvr; this.arrayCopy(args.pointers, 0, stack, selectorIndex, trueArgCount); this.sp += trueArgCount - argCount; // pop old args then push new args } else { // stack has already been arranged for #doesNotUnderstand: or #cannotInterpret: rcvr = this.stackValue(entry.argCount); } this.executeNewMethod(rcvr, entry.method, entry.argCount, entry.primIndex, entry.mClass, selector); return true; }, primitiveInvokeObjectAsMethod: function(argCount, method) { // invoked from VM if non-method found in lookup var orgArgs = this.instantiateClass(this.specialObjects[Squeak.splOb_ClassArray], argCount); for (var i = 0; i < argCount; i++) orgArgs.pointers[argCount - i - 1] = this.pop(); var orgReceiver = this.pop(), orgSelector = this.currentSelector; // send run:with:in: to non-method object var runWithIn = this.specialObjects[Squeak.splOb_SelectorRunWithIn]; this.push(method); // not actually a method this.push(orgSelector); this.push(orgArgs); this.push(orgReceiver); this.send(runWithIn, 3, false); return true; }, findMethodCacheEntry: function(selector, lkupClass) { //Probe the cache, and return the matching entry if found //Otherwise return one that can be used (selector and class set) with method == null. //Initial probe is class xor selector, reprobe delta is selector //We do not try to optimize probe time -- all are equally 'fast' compared to lookup //Instead we randomize the reprobe so two or three very active conflicting entries //will not keep dislodging each other var entry; this.methodCacheRandomish = (this.methodCacheRandomish + 1) & 3; var firstProbe = (selector.hash ^ lkupClass.hash) & this.methodCacheMask; var probe = firstProbe; for (var i = 0; i < 4; i++) { // 4 reprobes for now entry = this.methodCache[probe]; if (entry.selector === selector && entry.lkupClass === lkupClass) return entry; if (i === this.methodCacheRandomish) firstProbe = probe; probe = (probe + selector.hash) & this.methodCacheMask; } entry = this.methodCache[firstProbe]; entry.lkupClass = lkupClass; entry.selector = selector; entry.method = null; return entry; }, flushMethodCache: function() { //clear all cache entries (prim 89) for (var i = 0; i < this.methodCacheSize; i++) { this.methodCache[i].selector = null; // mark it free this.methodCache[i].method = null; // release the method } return true; }, flushMethodCacheForSelector: function(selector) { //clear cache entries for selector (prim 119) for (var i = 0; i < this.methodCacheSize; i++) if (this.methodCache[i].selector === selector) { this.methodCache[i].selector = null; // mark it free this.methodCache[i].method = null; // release the method } return true; }, flushMethodCacheForMethod: function(method) { //clear cache entries for method (prim 116) for (var i = 0; i < this.methodCacheSize; i++) if (this.methodCache[i].method === method) { this.methodCache[i].selector = null; // mark it free this.methodCache[i].method = null; // release the method } return true; }, flushMethodCacheAfterBecome: function(mutations) { // could be selective by checking lkupClass, selector, // and method against mutations dict this.flushMethodCache(); }, }, 'contexts', { isUnwindMarked: function(ctx) { if (!this.isMethodContext(ctx)) return false; var method = ctx.pointers[Squeak.Context_method]; return method.methodPrimitiveIndex() == 198; }, newActiveContext: function(newContext) { // Note: this is inlined in executeNewMethod() and doReturn() this.storeContextRegisters(); this.activeContext = newContext; //We're off and running... this.activeContext.dirty = true; this.fetchContextRegisters(newContext); if (this.breakOnContextChanged) { this.breakOnContextChanged = false; this.breakNow(); } }, exportThisContext: function() { this.storeContextRegisters(); this.reclaimableContextCount = 0; return this.activeContext; }, fetchContextRegisters: function(ctxt) { var meth = ctxt.pointers[Squeak.Context_method]; if (this.isSmallInt(meth)) { //if the Method field is an integer, activeCntx is a block context this.homeContext = ctxt.pointers[Squeak.BlockContext_home]; meth = this.homeContext.pointers[Squeak.Context_method]; } else { //otherwise home==ctxt this.homeContext = ctxt; } this.receiver = this.homeContext.pointers[Squeak.Context_receiver]; this.method = meth; this.pc = this.decodeSqueakPC(ctxt.pointers[Squeak.Context_instructionPointer], meth); this.sp = this.decodeSqueakSP(ctxt.pointers[Squeak.Context_stackPointer]); }, storeContextRegisters: function() { //Save pc, sp into activeContext object, prior to change of context // see fetchContextRegisters for symmetry // expects activeContext, pc, sp, and method state vars to still be valid this.activeContext.pointers[Squeak.Context_instructionPointer] = this.encodeSqueakPC(this.pc, this.method); this.activeContext.pointers[Squeak.Context_stackPointer] = this.encodeSqueakSP(this.sp); }, encodeSqueakPC: function(intPC, method) { // Squeak pc is offset by header and literals // and 1 for z-rel addressing return intPC + method.pointers.length * 4 + 1; }, decodeSqueakPC: function(squeakPC, method) { return squeakPC - method.pointers.length * 4 - 1; }, encodeSqueakSP: function(intSP) { // sp is offset by tempFrameStart, -1 for z-rel addressing return intSP - (Squeak.Context_tempFrameStart - 1); }, decodeSqueakSP: function(squeakSP) { return squeakSP + (Squeak.Context_tempFrameStart - 1); }, recycleIfPossible: function(ctxt) { if (!this.isMethodContext(ctxt)) return; if (ctxt.pointersSize() === (Squeak.Context_tempFrameStart+Squeak.Context_smallFrameSize)) { // Recycle small contexts ctxt.pointers[0] = this.freeContexts; this.freeContexts = ctxt; } else { // Recycle large contexts if (ctxt.pointersSize() !== (Squeak.Context_tempFrameStart+Squeak.Context_largeFrameSize)) return; ctxt.pointers[0] = this.freeLargeContexts; this.freeLargeContexts = ctxt; } }, allocateOrRecycleContext: function(needsLarge) { //Return a recycled context or a newly allocated one if none is available for recycling." var freebie; if (needsLarge) { if (!this.freeLargeContexts.isNil) { freebie = this.freeLargeContexts; this.freeLargeContexts = freebie.pointers[0]; this.nRecycledContexts++; return freebie; } this.nAllocatedContexts++; return this.instantiateClass(this.specialObjects[Squeak.splOb_ClassMethodContext], Squeak.Context_largeFrameSize); } else { if (!this.freeContexts.isNil) { freebie = this.freeContexts; this.freeContexts = freebie.pointers[0]; this.nRecycledContexts++; return freebie; } this.nAllocatedContexts++; return this.instantiateClass(this.specialObjects[Squeak.splOb_ClassMethodContext], Squeak.Context_smallFrameSize); } }, }, 'stack access', { pop: function() { //Note leaves garbage above SP. Cleaned out by fullGC. return this.activeContext.pointers[this.sp--]; }, popN: function(nToPop) { this.sp -= nToPop; }, push: function(object) { this.activeContext.pointers[++this.sp] = object; }, popNandPush: function(nToPop, object) { this.activeContext.pointers[this.sp -= nToPop - 1] = object; }, top: function() { return this.activeContext.pointers[this.sp]; }, stackTopPut: function(object) { this.activeContext.pointers[this.sp] = object; }, stackValue: function(depthIntoStack) { return this.activeContext.pointers[this.sp - depthIntoStack]; }, stackInteger: function(depthIntoStack) { return this.checkSmallInt(this.stackValue(depthIntoStack)); }, stackIntOrFloat: function(depthIntoStack) { var num = this.stackValue(depthIntoStack); // is it a SmallInt? if (typeof num === "number") return num; if (num === undefined) {this.success = false; return 0;} // is it a Float? if (num.isFloat) { this.resultIsFloat = true; // need to return result as Float return num.float; } // maybe a 32-bit LargeInt? var bytes = num.bytes; if (bytes && bytes.length == 4) { var value = 0; for (var i = 3; i >= 0; i--) value = value * 256 + bytes[i]; if (num.sqClass === this.specialObjects[Squeak.splOb_ClassLargePositiveInteger]) return value; if (num.sqClass === this.specialObjects[Squeak.splOb_ClassLargeNegativeInteger]) return -value; } // none of the above this.success = false; return 0; }, pop2AndPushIntResult: function(intResult) {// returns success boolean if (this.success && this.canBeSmallInt(intResult)) { this.popNandPush(2, intResult); return true; } return false; }, pop2AndPushNumResult: function(numResult) {// returns success boolean if (this.success) { if (this.resultIsFloat) { this.popNandPush(2, this.primHandler.makeFloat(numResult)); return true; } if (numResult >= Squeak.MinSmallInt && numResult <= Squeak.MaxSmallInt) { this.popNandPush(2, numResult); return true; } if (numResult >= -4294967295 && numResult <= 0xFFFFFFFF) { var negative = numResult < 0, unsigned = negative ? -numResult : numResult, lgIntClass = negative ? Squeak.splOb_ClassLargeNegativeInteger : Squeak.splOb_ClassLargePositiveInteger, lgIntObj = this.instantiateClass(this.specialObjects[lgIntClass], 4), bytes = lgIntObj.bytes; bytes[0] = unsigned & 255; bytes[1] = unsigned>>8 & 255; bytes[2] = unsigned>>16 & 255; bytes[3] = unsigned>>24 & 255; this.popNandPush(2, lgIntObj); return true; } } return false; }, pop2AndPushBoolResult: function(boolResult) { if (!this.success) return false; this.popNandPush(2, boolResult ? this.trueObj : this.falseObj); return true; }, }, 'numbers', { getClass: function(obj) { if (this.isSmallInt(obj)) return this.specialObjects[Squeak.splOb_ClassInteger]; return obj.sqClass; }, canBeSmallInt: function(anInt) { return (anInt >= Squeak.MinSmallInt) && (anInt <= Squeak.MaxSmallInt); }, isSmallInt: function(object) { return typeof object === "number"; }, checkSmallInt: function(maybeSmallInt) { // returns an int and sets success if (typeof maybeSmallInt === "number") return maybeSmallInt; this.success = false; return 1; }, quickDivide: function(rcvr, arg) { // must only handle exact case if (arg === 0) return Squeak.NonSmallInt; // fail if divide by zero var result = rcvr / arg | 0; if (result * arg === rcvr) return result; return Squeak.NonSmallInt; // fail if result is not exact }, div: function(rcvr, arg) { if (arg === 0) return Squeak.NonSmallInt; // fail if divide by zero return Math.floor(rcvr/arg); }, mod: function(rcvr, arg) { if (arg === 0) return Squeak.NonSmallInt; // fail if divide by zero return rcvr - Math.floor(rcvr/arg) * arg; }, safeShift: function(smallInt, shiftCount) { // must only be used if smallInt is actually a SmallInt! // the logic is complex because JS shifts only up to 31 bits // and treats e.g. 1<<32 as 1<<0, so we have to do our own checks if (shiftCount < 0) { if (shiftCount < -31) return smallInt < 0 ? -1 : 0; // this would wrongly return a negative result if // smallInt >= 0x80000000, but the largest smallInt // is 0x3FFFFFFF so we're ok return smallInt >> -shiftCount; // OK to lose bits shifting right } if (shiftCount > 31) return smallInt === 0 ? 0 : Squeak.NonSmallInt; var shifted = smallInt << shiftCount; // check for lost bits by seeing if computation is reversible if ((shifted>>shiftCount) !== smallInt) return Squeak.NonSmallInt; // fail return shifted; // caller will check if still within SmallInt range }, }, 'utils', { isContext: function(obj) {//either block or methodContext if (obj.sqClass === this.specialObjects[Squeak.splOb_ClassMethodContext]) return true; if (obj.sqClass === this.specialObjects[Squeak.splOb_ClassBlockContext]) return true; return false; }, isMethodContext: function(obj) { return obj.sqClass === this.specialObjects[Squeak.splOb_ClassMethodContext]; }, instantiateClass: function(aClass, indexableSize) { return this.image.instantiateClass(aClass, indexableSize, this.nilObj); }, arrayFill: function(array, fromIndex, toIndex, value) { // assign value to range from fromIndex (inclusive) to toIndex (exclusive) for (var i = fromIndex; i < toIndex; i++) array[i] = value; }, arrayCopy: function(src, srcPos, dest, destPos, length) { // copy length elements from src at srcPos to dest at destPos if (src === dest && srcPos < destPos) for (var i = length - 1; i >= 0; i--) dest[destPos + i] = src[srcPos + i]; else for (var i = 0; i < length; i++) dest[destPos + i] = src[srcPos + i]; }, signalLowSpaceIfNecessary: function(bytesLeft) { if (bytesLeft < this.lowSpaceThreshold && this.lowSpaceThreshold > 0) { var increase = prompt && prompt("Out of memory, " + Math.ceil(this.image.totalMemory/1000000) + " MB used.\nEnter additional MB, or 0 to signal low space in image", "0"); if (increase) { var bytes = parseInt(increase, 10) * 1000000; this.image.totalMemory += bytes; this.signalLowSpaceIfNecessary(this.image.bytesLeft()); } else { console.warn("squeak: low memory (" + bytesLeft + "/" + this.image.totalMemory + " bytes left), signaling low space"); this.signalLowSpace = true; this.lowSpaceThreshold = 0; var lastSavedProcess = this.specialObjects[Squeak.splOb_ProcessSignalingLowSpace]; if (lastSavedProcess.isNil) { this.specialObjects[Squeak.splOb_ProcessSignalingLowSpace] = this.primHandler.activeProcess(); } this.forceInterruptCheck(); } } }, }, 'debugging', { addMessage: function(message) { return this.messages[message] ? ++this.messages[message] : this.messages[message] = 1; }, warnOnce: function(message, what) { if (this.addMessage(message) == 1) { console[what || "warn"](message); return true; } }, printMethod: function(aMethod, optContext, optSel, optArgs) { // return a 'class>>selector' description for the method if (aMethod.sqClass != this.specialObjects[Squeak.splOb_ClassCompiledMethod]) { return this.printMethod(aMethod.blockOuterCode(), optContext, optSel, optArgs) } var found; if (optSel) { var printed = optContext.className() + '>>'; var selector = optSel.bytesAsString(); if (!optArgs || !optArgs.length) printed += selector; else { var parts = selector.split(/(?<=:)/); // keywords for (var i = 0; i < optArgs.length; i++) { if (i > 0) printed += ' '; printed += parts[i] + ' ' + optArgs[i]; } } return printed; } // this is expensive, we have to search all classes if (!aMethod) aMethod = this.activeContext.contextMethod(); this.allMethodsDo(function(classObj, methodObj, selectorObj) { if (methodObj === aMethod) return found = classObj.className() + '>>' + selectorObj.bytesAsString(); }); if (found) return found; if (optContext) { var rcvr = optContext.pointers[Squeak.Context_receiver]; return "(" + rcvr + ")>>?"; } return "?>>?"; }, allInstancesOf: function(classObj, callback) { if (typeof classObj === "string") classObj = this.globalNamed(classObj); var instances = [], inst = this.image.someInstanceOf(classObj); while (inst) { if (callback) callback(inst); else instances.push(inst); inst = this.image.nextInstanceAfter(inst); } return instances; }, globalNamed: function(name) { return this.allGlobalsDo(function(nameObj, globalObj){ if (nameObj.bytesAsString() === name) return globalObj; }); }, allGlobalsDo: function(callback) { // callback(globalNameObj, globalObj), truish result breaks out of iteration var globals = this.getGlobals(); for (var i = 0; i < globals.length; i++) { var assn = globals[i]; if (!assn.isNil) { var result = callback(assn.pointers[0], assn.pointers[1]); if (result) return result; } } }, allMethodsDo: function(callback) { // callback(classObj, methodObj, selectorObj), truish result breaks out of iteration this.allGlobalsDo(function(globalNameObj, globalObj) { if (globalObj.pointers && globalObj.pointers.length >= 9) { var clsAndMeta = [globalObj, globalObj.sqClass]; for (var c = 0; c < clsAndMeta.length; c++) { var cls = clsAndMeta[c]; var mdict = cls.pointers[1]; if (!mdict.pointers || !mdict.pointers[1]) continue; var methods = mdict.pointers[1].pointers; if (!methods) continue; var selectors = mdict.pointers; if (methods.length + 2 !== selectors.length) continue; for (var j = 0; j < methods.length; j++) { var method = methods[j]; var selector = selectors[2+j]; if (!method.isMethod || !method.isMethod()) continue; if (!selector.bytesSize || !selector.bytesSize()) continue; var result = callback.call(null, cls, method, selector); if (result) return true; } } } }); }, printStack: function(ctx, limit, indent) { // both args are optional if (typeof ctx == "number") {limit = ctx; ctx = null;} if (!ctx) ctx = this.activeContext; if (!limit) limit = 100; var contexts = [], hardLimit = Math.max(limit, 1000000); while (!ctx.isNil && hardLimit-- > 0) { contexts.push(ctx); ctx = ctx.pointers[Squeak.Context_sender]; } var extra = 200; if (contexts.length > limit + extra) { if (!ctx.isNil) contexts.push('...'); // over hard limit contexts = contexts.slice(0, limit).concat(['...']).concat(contexts.slice(-200)); } var stack = [], i = contexts.length, indents = ''; if (indent && this.logSends) indents = Array((""+this.sendCount).length + 2).join(' '); while (i-- > 0) { var ctx = contexts[i]; if (!ctx.pointers) { stack.push('...\n'); } else { var block = '', method = ctx.pointers[Squeak.Context_method]; if (typeof method === 'number') { // it's a block context, fetch home method = ctx.pointers[Squeak.BlockContext_home].pointers[Squeak.Context_method]; block = '[] in '; } else if (!ctx.pointers[Squeak.Context_closure].isNil) { block = '[] in '; // it's a closure activation } var line = block + this.printMethod(method, ctx); if (indent) line = indents + line; stack.push(line + '\n'); if (indent) indents += indent; } } return stack.join(''); }, findMethod: function(classAndMethodString) { // classAndMethodString is 'Class>>method' var found, className = classAndMethodString.split('>>')[0], methodName = classAndMethodString.split('>>')[1]; this.allMethodsDo(function(classObj, methodObj, selectorObj) { if (methodName.length == selectorObj.bytesSize() && methodName == selectorObj.bytesAsString() && className == classObj.className()) return found = methodObj; }); return found; }, breakAfter: function(ms) { this.breakOutTick = this.primHandler.millisecondClockValue() + ms; }, breakNow: function(msg) { if (msg) console.log("Break: " + msg); this.breakOutOfInterpreter = 'break'; }, breakOn: function(classAndMethodString) { // classAndMethodString is 'Class>>method' return this.breakOnMethod = classAndMethodString && this.findMethod(classAndMethodString); }, breakOnReturnFromThisContext: function() { this.breakOnContextChanged = false; this.breakOnContextReturned = this.activeContext; }, breakOnSendOrReturn: function() { this.breakOnContextChanged = true; this.breakOnContextReturned = null; }, printContext: function(ctx, maxWidth) { if (!this.isContext(ctx)) return "NOT A CONTEXT: " + printObj(ctx); if (!maxWidth) maxWidth = 72; function printObj(obj) { var value = typeof obj === 'number' || typeof obj === 'object' ? obj.sqInstName() : "<" + obj + ">"; value = JSON.stringify(value).slice(1, -1); if (value.length > maxWidth - 3) value = value.slice(0, maxWidth - 3) + '...'; return value; } // temps and stack in current context var isBlock = typeof ctx.pointers[Squeak.BlockContext_argumentCount] === 'number'; var closure = ctx.pointers[Squeak.Context_closure]; var isClosure = !isBlock && !closure.isNil; var homeCtx = isBlock ? ctx.pointers[Squeak.BlockContext_home] : ctx; var tempCount = isClosure ? closure.pointers[Squeak.Closure_numArgs] : homeCtx.pointers[Squeak.Context_method].methodTempCount(); var stackBottom = this.decodeSqueakSP(0); var stackTop = homeCtx.contextSizeWithStack(this) - 1; var firstTemp = stackBottom + 1; var lastTemp = firstTemp + tempCount - 1; var lastArg = firstTemp + homeCtx.pointers[Squeak.Context_method].methodNumArgs() - 1; var stack = ''; for (var i = stackBottom; i <= stackTop; i++) { var value = printObj(homeCtx.pointers[i]); var label = ''; if (i === stackBottom) { label = '=rcvr'; } else { if (i <= lastTemp) label = '=tmp' + (i - firstTemp); if (i <= lastArg) label += '/arg' + (i - firstTemp); } stack += '\nctx[' + i + ']' + label +': ' + value; } if (isBlock) { stack += '\n'; var nArgs = ctx.pointers[Squeak.BlockContext_argumentCount]; var firstArg = this.decodeSqueakSP(1); var lastArg = firstArg + nArgs; var sp = ctx === this.activeContext ? this.sp : ctx.pointers[Squeak.Context_stackPointer]; if (sp < firstArg) stack += '\nblk '; for (var i = firstArg; i <= sp; i++) { var value = printObj(ctx.pointers[i]); var label = ''; if (i < lastArg) label = '=arg' + (i - firstArg); stack += '\nblk[' + i + ']' + label +': ' + value; } } return stack; }, printActiveContext: function(maxWidth) { return this.printContext(this.activeContext, maxWidth); }, printAllProcesses: function() { var schedAssn = this.specialObjects[Squeak.splOb_SchedulerAssociation], sched = schedAssn.pointers[Squeak.Assn_value]; // print active process var activeProc = sched.pointers[Squeak.ProcSched_activeProcess], result = "Active: " + this.printProcess(activeProc, true); // print other runnable processes in order of priority var lists = sched.pointers[Squeak.ProcSched_processLists].pointers; for (var priority = lists.length - 1; priority >= 0; priority--) { var process = lists[priority].pointers[Squeak.LinkedList_firstLink]; while (!process.isNil) { result += "\n------------------------------------------"; result += "\nRunnable: " + this.printProcess(process); process = process.pointers[Squeak.Link_nextLink]; } } // print all processes waiting on a semaphore in order of priority var semaClass = this.specialObjects[Squeak.splOb_ClassSemaphore], sema = this.image.someInstanceOf(semaClass), waiting = []; while (sema) { var process = sema.pointers[Squeak.LinkedList_firstLink]; while (!process.isNil) { waiting.push(process); process = process.pointers[Squeak.Link_nextLink]; } sema = this.image.nextInstanceAfter(sema); } waiting.sort(function(a, b){ return b.pointers[Squeak.Proc_priority] - a.pointers[Squeak.Proc_priority]; }); for (var i = 0; i < waiting.length; i++) { result += "\n------------------------------------------"; result += "\nWaiting: " + this.printProcess(waiting[i]); } return result; }, printProcess: function(process, active, indent) { if (!process) { var schedAssn = this.specialObjects[Squeak.splOb_SchedulerAssociation], sched = schedAssn.pointers[Squeak.Assn_value]; process = sched.pointers[Squeak.ProcSched_activeProcess], active = true; } var context = active ? this.activeContext : process.pointers[Squeak.Proc_suspendedContext], priority = process.pointers[Squeak.Proc_priority], stack = this.printStack(context, 20, indent), values = indent && this.logSends ? "" : this.printContext(context) + "\n"; return process.toString() +" at priority " + priority + "\n" + stack + values; }, printByteCodes: function(aMethod, optionalIndent, optionalHighlight, optionalPC) { if (!aMethod) aMethod = this.method; var printer = new Squeak.InstructionPrinter(aMethod, this); return printer.printInstructions(optionalIndent, optionalHighlight, optionalPC); }, logStack: function() { // useful for debugging interactively: // SqueakJS.vm.logStack() console.log(this.printStack() + this.printActiveContext() + '\n\n' + this.printByteCodes(this.method, '   ', '=> ', this.pc), this.activeContext.pointers.slice(0, this.sp + 1)); }, willSendOrReturn: function() { // Answer whether the next bytecode corresponds to a Smalltalk // message send or return var byte = this.method.bytes[this.pc]; if (this.method.methodSignFlag()) { if (0x60 <= byte && byte <= 0x7F) { selectorObj = this.specialSelectors[2 * (byte - 0x60)]; } else if (0x80 <= byte && byte <= 0xAF) { selectorObj = this.method.methodGetSelector(byte&0xF); } else if (byte == 0xEA || byte == 0xEB) { this.method.methodGetSelector((this.method.bytes[this.pc+1] >> 3)); // (extA << 5) } else if (0x58 <= byte && byte <= 0x5E) { return true; // return } } else { if (byte >= 120 && byte <= 125) return true; // return if (byte < 131 || byte == 200) return false; if (byte >= 176) return true; // special send or short send if (byte <= 134) { // long sends // long form support demands we check the selector var litIndex; if (byte === 132) { if ((this.method.bytes[this.pc + 1] >> 5) > 1) return false; litIndex = this.method.bytes[this.pc + 2]; } else litIndex = this.method.bytes[this.pc + 1] & (byte === 134 ? 63 : 31); var selectorObj = this.method.methodGetLiteral(litIndex); if (selectorObj.bytesAsString() !== 'blockCopy:') return true; } } return false; }, nextSendSelector: function() { // if the next bytecode corresponds to a Smalltalk // message send, answer the selector var byte = this.method.bytes[this.pc]; var selectorObj; if (this.method.methodSignFlag()) { if (0x60 <= byte && byte <= 0x7F) { selectorObj = this.specialSelectors[2 * (byte - 0x60)]; } else if (0x80 <= byte && byte <= 0xAF) { selectorObj = this.method.methodGetSelector(byte&0xF); } else if (byte == 0xEA || byte == 0xEB) { this.method.methodGetSelector((this.method.bytes[this.pc+1] >> 3)); // (extA << 5) } else { return null; } } else { if (byte < 131 || byte == 200) return null; if (byte >= 0xD0 ) { selectorObj = this.method.methodGetLiteral(byte & 0x0F); } else if (byte >= 0xB0 ) { selectorObj = this.specialSelectors[2 * (byte - 0xB0)]; } else if (byte <= 134) { // long form support demands we check the selector var litIndex; if (byte === 132) { if ((this.method.bytes[this.pc + 1] >> 5) > 1) return null; litIndex = this.method.bytes[this.pc + 2]; } else litIndex = this.method.bytes[this.pc + 1] & (byte === 134 ? 63 : 31); selectorObj = this.method.methodGetLiteral(litIndex); } } if (selectorObj) { var selector = selectorObj.bytesAsString(); if (selector !== 'blockCopy:') return selector; } }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.InterpreterProxy', // provides function names exactly like the C interpreter, for ease of porting // but maybe less efficiently because of the indirection // mostly used for plugins translated from Slang (see plugins/*.js) // built-in primitives use the interpreter directly 'initialization', { VM_PROXY_MAJOR: 1, VM_PROXY_MINOR: 11, initialize: function(vm) { this.vm = vm; this.remappableOops = []; Object.defineProperty(this, 'successFlag', { get: function() { return vm.primHandler.success; }, set: function(success) { vm.primHandler.success = success; }, }); }, majorVersion: function() { return this.VM_PROXY_MAJOR; }, minorVersion: function() { return this.VM_PROXY_MINOR; }, }, 'success', { failed: function() { return !this.successFlag; }, primitiveFail: function() { this.successFlag = false; }, primitiveFailFor: function(reasonCode) { this.successFlag = false; }, success: function(boolean) { if (!boolean) this.successFlag = false; }, }, 'stack access', { pop: function(n) { this.vm.popN(n); }, popthenPush: function(n, obj) { this.vm.popNandPush(n, obj); }, push: function(obj) { this.vm.push(obj); }, pushBool: function(bool) { this.vm.push(bool ? this.vm.trueObj : this.vm.falseObj); }, pushInteger: function(int) { this.vm.push(int); }, pushFloat: function(num) { this.vm.push(this.floatObjectOf(num)); }, stackValue: function(n) { return this.vm.stackValue(n); }, stackIntegerValue: function(n) { var int = this.vm.stackValue(n); if (typeof int === "number") return int; this.successFlag = false; return 0; }, stackFloatValue: function(n) { this.vm.success = true; var float = this.vm.stackIntOrFloat(n); if (this.vm.success) return float; this.successFlag = false; return 0; }, stackObjectValue: function(n) { var obj = this.vm.stackValue(n); if (typeof obj !== "number") return obj; this.successFlag = false; return this.vm.nilObj; }, stackBytes: function(n) { var oop = this.vm.stackValue(n); if (oop.bytes) return oop.bytes; if (typeof oop === "number" || !oop.isBytes()) this.successFlag = false; return []; }, stackWords: function(n) { var oop = this.vm.stackValue(n); if (oop.words) return oop.words; if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, stackInt32Array: function(n) { var oop = this.vm.stackValue(n); if (oop.words) return oop.wordsAsInt32Array(); if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, stackInt16Array: function(n) { var oop = this.vm.stackValue(n); if (oop.words) return oop.wordsAsInt16Array(); if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, stackUint16Array: function(n) { var oop = this.vm.stackValue(n); if (oop.words) return oop.wordsAsUint16Array(); if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, }, 'object access', { isBytes: function(obj) { return typeof obj !== "number" && obj.isBytes(); }, isWords: function(obj) { return typeof obj !== "number" && obj.isWords(); }, isWordsOrBytes: function(obj) { return typeof obj !== "number" && obj.isWordsOrBytes(); }, isPointers: function(obj) { return typeof obj !== "number" && obj.isPointers(); }, isIntegerValue: function(obj) { return typeof obj === "number" && obj >= -1073741824 && obj <= 0x3FFFFFFF; }, isArray: function(obj) { return obj.sqClass === this.vm.specialObjects[Squeak.splOb_ClassArray]; }, isMemberOf: function(obj, className) { var nameBytes = obj.sqClass.pointers[Squeak.Class_name].bytes; if (className.length !== nameBytes.length) return false; for (var i = 0; i < className.length; i++) if (className.charCodeAt(i) !== nameBytes[i]) return false; return true; }, booleanValueOf: function(obj) { if (obj.isTrue) return true; if (obj.isFalse) return false; this.successFlag = false; return false; }, positive32BitValueOf: function(obj) { return this.vm.primHandler.positive32BitValueOf(obj); }, positive32BitIntegerFor: function(int) { return this.vm.primHandler.pos32BitIntFor(int); }, floatValueOf: function(obj) { if (obj.isFloat) return obj.float; this.successFlag = false; return 0; }, floatObjectOf: function(num) { return this.vm.primHandler.makeFloat(num); }, fetchPointerofObject: function(n, obj) { return obj.pointers[n]; }, fetchBytesofObject: function(n, obj) { var oop = obj.pointers[n]; if (oop.bytes) return oop.bytes; if (oop.words) return oop.wordsAsUint8Array(); if (typeof oop === "number" || !oop.isWordsOrBytes()) this.successFlag = false; return []; }, fetchWordsofObject: function(n, obj) { var oop = obj.pointers[n]; if (oop.words) return oop.words; if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, fetchInt32ArrayofObject: function(n, obj) { var oop = obj.pointers[n]; if (oop.words) return oop.wordsAsInt32Array(); if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, fetchInt16ArrayofObject: function(n, obj) { var oop = obj.pointers[n]; if (oop.words) return oop.wordsAsInt16Array(); if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, fetchUint16ArrayofObject: function(n, obj) { var oop = obj.pointers[n]; if (oop.words) return oop.wordsAsUint16Array(); if (typeof oop === "number" || !oop.isWords()) this.successFlag = false; return []; }, fetchIntegerofObject: function(n, obj) { var int = obj.pointers[n]; if (typeof int === "number") return int; this.successFlag = false; return 0; }, fetchLong32ofObject: function(n, obj) { return obj.words[n]; }, fetchFloatofObject: function(n, obj) { return this.floatValueOf(obj.pointers[n]); }, storeIntegerofObjectwithValue: function(n, obj, value) { if (typeof value === "number") obj.pointers[n] = value; else this.successFlag = false; }, storePointerofObjectwithValue: function(n, obj, value) { obj.pointers[n] = value; }, stObjectatput: function(array, index, obj) { if (array.sqClass !== this.classArray()) throw Error("Array expected"); if (index < 1 || index > array.pointers.length) return this.successFlag = false; array.pointers[index-1] = obj; }, }, 'constant access', { isKindOfInteger: function(obj) { return typeof obj === "number" || obj.sqClass == this.classLargeNegativeInteger() || obj.sqClass == this.classLargePositiveInteger(); }, classArray: function() { return this.vm.specialObjects[Squeak.splOb_ClassArray]; }, classBitmap: function() { return this.vm.specialObjects[Squeak.splOb_ClassBitmap]; }, classSmallInteger: function() { return this.vm.specialObjects[Squeak.splOb_ClassInteger]; }, classLargePositiveInteger: function() { return this.vm.specialObjects[Squeak.splOb_ClassLargePositiveInteger]; }, classLargeNegativeInteger: function() { return this.vm.specialObjects[Squeak.splOb_ClassLargeNegativeInteger]; }, classPoint: function() { return this.vm.specialObjects[Squeak.splOb_ClassPoint]; }, classString: function() { return this.vm.specialObjects[Squeak.splOb_ClassString]; }, classByteArray: function() { return this.vm.specialObjects[Squeak.splOb_ClassByteArray]; }, nilObject: function() { return this.vm.nilObj; }, falseObject: function() { return this.vm.falseObj; }, trueObject: function() { return this.vm.trueObj; }, }, 'vm functions', { clone: function(object) { return this.vm.image.clone(object); }, instantiateClassindexableSize: function(aClass, indexableSize) { return this.vm.instantiateClass(aClass, indexableSize); }, methodArgumentCount: function() { return this.argCount; }, makePointwithxValueyValue: function(x, y) { return this.vm.primHandler.makePointWithXandY(x, y); }, pushRemappableOop: function(obj) { this.remappableOops.push(obj); }, popRemappableOop: function() { return this.remappableOops.pop(); }, showDisplayBitsLeftTopRightBottom: function(form, left, top, right, bottom) { if (left < right && top < bottom) { var rect = {left: left, top: top, right: right, bottom: bottom}; this.vm.primHandler.displayDirty(form, rect); } }, ioLoadFunctionFrom: function(funcName, pluginName) { return this.vm.primHandler.loadFunctionFrom(funcName, pluginName); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.InstructionStream', 'initialization', { initialize: function(method, vm) { this.vm = vm; this.method = method; this.pc = 0; this.specialConstants = [vm.trueObj, vm.falseObj, vm.nilObj, -1, 0, 1, 2]; }, }, 'decoding', { interpretNextInstructionFor: function(client) { // Send to the argument, client, a message that specifies the type of the next instruction. var method = this.method; var byte = method.bytes[this.pc++]; var type = (byte / 16) | 0; var offset = byte % 16; if (type === 0) return client.pushReceiverVariable(offset); if (type === 1) return client.pushTemporaryVariable(offset); if (type === 2) return client.pushConstant(method.methodGetLiteral(offset)); if (type === 3) return client.pushConstant(method.methodGetLiteral(offset + 16)); if (type === 4) return client.pushLiteralVariable(method.methodGetLiteral(offset)); if (type === 5) return client.pushLiteralVariable(method.methodGetLiteral(offset + 16)); if (type === 6) if (offset<8) return client.popIntoReceiverVariable(offset) else return client.popIntoTemporaryVariable(offset-8); if (type === 7) { if (offset===0) return client.pushReceiver() if (offset < 8) return client.pushConstant(this.specialConstants[offset - 1]) if (offset===8) return client.methodReturnReceiver(); if (offset < 12) return client.methodReturnConstant(this.specialConstants[offset - 9]); if (offset===12) return client.methodReturnTop(); if (offset===13) return client.blockReturnTop(); if (offset > 13) throw Error("unusedBytecode"); } if (type === 8) return this.interpretExtension(offset, method, client); if (type === 9) // short jumps if (offset<8) return client.jump(offset+1); else return client.jumpIf(false, offset-8+1); if (type === 10) {// long jumps byte = this.method.bytes[this.pc++]; if (offset<8) return client.jump((offset-4)*256 + byte); else return client.jumpIf(offset<12, (offset & 3)*256 + byte); } if (type === 11) return client.send(this.vm.specialSelectors[2 * offset], this.vm.specialSelectors[2 * offset + 1], false); if (type === 12) return client.send(this.vm.specialSelectors[2 * (offset + 16)], this.vm.specialSelectors[2 * (offset + 16) + 1], false); if (type > 12) return client.send(method.methodGetLiteral(offset), type-13, false); }, interpretExtension: function(offset, method, client) { if (offset <= 6) { // Extended op codes 128-134 var byte2 = this.method.bytes[this.pc++]; if (offset <= 2) { // 128-130: extended pushes and pops var type = byte2 / 64 | 0; var offset2 = byte2 % 64; if (offset === 0) { if (type === 0) return client.pushReceiverVariable(offset2); if (type === 1) return client.pushTemporaryVariable(offset2); if (type === 2) return client.pushConstant(this.method.methodGetLiteral(offset2)); if (type === 3) return client.pushLiteralVariable(this.method.methodGetLiteral(offset2)); } if (offset === 1) { if (type === 0) return client.storeIntoReceiverVariable(offset2); if (type === 1) return client.storeIntoTemporaryVariable(offset2); if (type === 2) throw Error("illegalStore"); if (type === 3) return client.storeIntoLiteralVariable(this.method.methodGetLiteral(offset2)); } if (offset === 2) { if (type === 0) return client.popIntoReceiverVariable(offset2); if (type === 1) return client.popIntoTemporaryVariable(offset2); if (type === 2) throw Error("illegalStore"); if (type === 3) return client.popIntoLiteralVariable(this.method.methodGetLiteral(offset2)); } } // 131-134 (extended sends) if (offset === 3) // Single extended send return client.send(this.method.methodGetLiteral(byte2 % 32), byte2 / 32 | 0, false); if (offset === 4) { // Double extended do-anything var byte3 = this.method.bytes[this.pc++]; var type = byte2 / 32 | 0; if (type === 0) return client.send(this.method.methodGetLiteral(byte3), byte2 % 32, false); if (type === 1) return client.send(this.method.methodGetLiteral(byte3), byte2 % 32, true); if (type === 2) return client.pushReceiverVariable(byte3); if (type === 3) return client.pushConstant(this.method.methodGetLiteral(byte3)); if (type === 4) return client.pushLiteralVariable(this.method.methodGetLiteral(byte3)); if (type === 5) return client.storeIntoReceiverVariable(byte3); if (type === 6) return client.popIntoReceiverVariable(byte3); if (type === 7) return client.storeIntoLiteralVariable(this.method.methodGetLiteral(byte3)); } if (offset === 5) // Single extended send to super return client.send(this.method.methodGetLiteral(byte2 & 31), byte2 >> 5, true); if (offset === 6) // Second extended send return client.send(this.method.methodGetLiteral(byte2 & 63), byte2 >> 6, false); } if (offset === 7) return client.doPop(); if (offset === 8) return client.doDup(); if (offset === 9) return client.pushActiveContext(); // closures var byte2 = this.method.bytes[this.pc++]; if (offset === 10) return byte2 < 128 ? client.pushNewArray(byte2) : client.popIntoNewArray(byte2 - 128); var byte3 = this.method.bytes[this.pc++]; if (offset === 11) return client.callPrimitive(byte2 + 256 * byte3); if (offset === 12) return client.pushRemoteTemp(byte2, byte3); if (offset === 13) return client.storeIntoRemoteTemp(byte2, byte3); if (offset === 14) return client.popIntoRemoteTemp(byte2, byte3); // offset === 15 var byte4 = this.method.bytes[this.pc++]; return client.pushClosureCopy(byte2 >> 4, byte2 & 0xF, (byte3 * 256) + byte4); } }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Squeak.InstructionStream.subclass('Squeak.InstructionStreamSista', 'decoding', { interpretNextInstructionFor: function(client) { return this.interpretNextInstructionExtFor(client, 0, 0); }, interpretNextInstructionExtFor: function(client, extA, extB) { this.Squeak; // avoid dynamic lookup of "Squeak" in Lively // Send to the argument, client, a message that specifies the type of the next instruction. var b = this.method.bytes[this.pc++]; switch (b) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: return client.pushReceiverVariable(b&0xF); case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: return client.pushLiteralVariable(this.method.methodGetLiteral(b&0xF)); case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: return client.pushConstant(this.method.methodGetLiteral(b&0x1F)); case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: return client.pushTemporaryVariable(b&0xF); case 0x48: case 0x49: case 0x4A: case 0x4B: return client.pushTemporaryVariable((b&0x3)+8); case 0x4C: return client.pushReceiver(); case 0x4D: return client.pushConstant(this.vm.trueObj); case 0x4E: return client.pushConstant(this.vm.falseObj); case 0x4F: return client.pushConstant(this.vm.nilObj); case 0x50: return client.pushConstant(0); case 0x51: return client.pushConstant(1); case 0x52: return client.pushActiveContext(); case 0x53: return client.doDup(); case 0x58: return client.methodReturnReceiver(); case 0x59: return client.methodReturnConstant(this.vm.trueObj); case 0x5A: return client.methodReturnConstant(this.vm.falseObj); case 0x5B: return client.methodReturnConstant(this.vm.nilObj); case 0x5C: return client.methodReturnTop(); case 0x5D: return client.blockReturnConstant(this.vm.nilObj); case 0x5E: if (extA===0) return client.blockReturnTop(); else break; case 0x5F: return client.nop(); case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: return client.send(this.vm.specialSelectors[2 * (b - 0x60)], this.vm.specialSelectors[2 * (b - 0x60) + 1], false); case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: return client.send(this.method.methodGetLiteral(b&0xF), 0, false); case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: return client.send(this.method.methodGetLiteral(b&0xF), 1, false); case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: return client.send(this.method.methodGetLiteral(b&0xF), 2, false); case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: return client.jump((b&7) + 1); case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: return client.jumpIf(true, (b&7) + 1); case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: return client.jumpIf(false, (b&7) + 1); case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: return client.popIntoReceiverVariable(b&7) case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: return client.popIntoTemporaryVariable(b - 0xD0); case 0xD8: return client.doPop(); } var b2 = this.method.bytes[this.pc++]; switch (b) { case 0xE0: return this.interpretNextInstructionExtFor(client, (extA << 8) + b2, extB); case 0xE1: return this.interpretNextInstructionExtFor(client, extA, (extB << 8) + (b2 < 128 ? b2 : b2-256)); case 0xE2: return client.pushReceiverVariable(b2 + (extA << 8)); case 0xE3: return client.pushLiteralVariable(this.method.methodGetLiteral(b2 + (extA << 8))); case 0xE4: return client.pushConstant(this.method.methodGetLiteral(b2 + (extA << 8))); case 0xE5: return client.pushTemporaryVariable(b2); case 0xE7: { return b2 < 128 ? client.pushNewArray(b2) : client.popIntoNewArray(b2 - 128); } case 0xE8: return client.pushConstant(b2 + (extB << 8)); case 0xE9: var unicode = b2 + (extB << 8); return client.pushConstant("$" + String.fromCodePoint(unicode) + " (" + unicode + ")"); case 0xEA: return client.send(this.method.methodGetSelector((b2 >> 3) + (extA << 5)), (b2 & 7) + (extB << 3), false); case 0xEB: var literal = this.method.methodGetSelector((b2 >> 3) + (extA << 5)); if (extB >= 64) { return client.sendSuperDirected(literal); } else { return client.send(literal, (b2 & 7) + (extB << 3), true); } case 0xED: return client.jump(b2 + (extB << 8)); case 0xEE: return client.jumpIf(true, b2 + (extB << 8)); case 0xEF: return client.jumpIf(false, b2 + (extB << 8)); case 0xF0: return client.popIntoReceiverVariable(b2 + (extA << 8)); case 0xF1: return client.popIntoLiteralVariable(this.method.methodGetLiteral(b2 + (extA << 8))); case 0xF2: return client.popIntoTemporaryVariable(b2); case 0xF3: return client.storeIntoReceiverVariable(b2 + (extA << 8)); case 0xF4: return client.storeIntoLiteralVariable(this.method.methodGetLiteral(b2 + (extA << 8))); case 0xF5: return client.storeIntoTemporaryVariable(b2); } var b3 = this.method.bytes[this.pc++]; switch (b) { case 0xF8: return client.callPrimitive(b2 + (b3 << 8)); case 0xF9: { var literalIndex = b2 + (extA << 8), numCopied = b3 & 63, compiledBlock = this.method.methodGetLiteral(literalIndex); return client.pushFullClosure(literalIndex, numCopied, compiledBlock.methodNumArgs()); } case 0xFA: { var numArgs = (b2 & 7) + this.mod(extA, 16) * 8, numCopied = (b2 >> 3 & 0x7) + this.div(extA, 16) * 8, blockSize = b3 + (extB << 8); return client.pushClosureCopy(numCopied, numArgs, blockSize); } case 0xFB: return client.pushRemoteTemp(b2, b3); case 0xFC: return client.storeIntoRemoteTemp(b2, b3); case 0xFD: return client.popIntoRemoteTemp(b2, b3); } throw Error("Unknown bytecode: " + b); } }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.InstructionPrinter', 'initialization', { initialize: function(method, vm) { this.method = method; this.vm = vm; }, }, 'printing', { printInstructions: function(indent, highlight, highlightPC) { // all args are optional this.indent = indent; // prepend to every line except if highlighted this.highlight = highlight; // prepend to highlighted line this.highlightPC = highlightPC; // PC of highlighted line this.innerIndents = {}; this.result = ''; this.scanner = this.method.methodSignFlag() ? new Squeak.InstructionStreamSista(this.method, this.vm) : new Squeak.InstructionStream(this.method, this.vm); this.oldPC = this.scanner.pc; this.endPC = 0; // adjusted while scanning this.done = false; try { while (!this.done) this.scanner.interpretNextInstructionFor(this); } catch(ex) { this.print("!!! " + ex.message); } return this.result; }, print: function(instruction) { if (this.oldPC === this.highlightPC) { if (this.highlight) this.result += this.highlight; } else { if (this.indent) this.result += this.indent; } this.result += this.oldPC; for (var i = 0; i < this.innerIndents[this.oldPC] || 0; i++) this.result += " "; this.result += " <"; for (var i = this.oldPC; i < this.scanner.pc; i++) { if (i > this.oldPC) this.result += " "; this.result += (this.method.bytes[i]+0x100).toString(16).substr(-2).toUpperCase(); // padded hex } this.result += "> " + instruction + "\n"; this.oldPC = this.scanner.pc; } }, 'decoding', { blockReturnConstant: function(obj) { this.print('blockReturn: ' + obj.toString()); this.done = this.scanner.pc > this.endPC; // full block }, blockReturnTop: function() { this.print('blockReturn'); this.done = this.scanner.pc > this.endPC; // full block }, doDup: function() { this.print('dup'); }, doPop: function() { this.print('pop'); }, jump: function(offset) { this.print('jumpTo: ' + (this.scanner.pc + offset)); if (this.scanner.pc + offset > this.endPC) this.endPC = this.scanner.pc + offset; }, jumpIf: function(condition, offset) { this.print((condition ? 'jumpIfTrue: ' : 'jumpIfFalse: ') + (this.scanner.pc + offset)); if (this.scanner.pc + offset > this.endPC) this.endPC = this.scanner.pc + offset; }, methodReturnReceiver: function() { this.print('return: receiver'); this.done = this.scanner.pc > this.endPC; }, methodReturnTop: function() { this.print('return: topOfStack'); this.done = this.scanner.pc > this.endPC; }, methodReturnConstant: function(obj) { this.print('returnConst: ' + obj.toString()); this.done = this.scanner.pc > this.endPC; }, nop: function() { this.print('nop'); }, popIntoLiteralVariable: function(anAssociation) { this.print('popIntoBinding: ' + anAssociation.assnKeyAsString()); }, popIntoReceiverVariable: function(offset) { this.print('popIntoInstVar: ' + offset); }, popIntoTemporaryVariable: function(offset) { this.print('popIntoTemp: ' + offset); }, pushActiveContext: function() { this.print('push: thisContext'); }, pushConstant: function(obj) { var value = obj.sqInstName ? obj.sqInstName() : obj.toString(); this.print('pushConst: ' + value); }, pushLiteralVariable: function(anAssociation) { this.print('pushBinding: ' + anAssociation.assnKeyAsString()); }, pushReceiver: function() { this.print('push: self'); }, pushReceiverVariable: function(offset) { this.print('pushInstVar: ' + offset); }, pushTemporaryVariable: function(offset) { this.print('pushTemp: ' + offset); }, send: function(selector, numberArguments, supered) { this.print( (supered ? 'superSend: #' : 'send: #') + (selector.bytesAsString ? selector.bytesAsString() : selector)); }, sendSuperDirected: function(selector) { this.print('directedSuperSend: #' + (selector.bytesAsString ? selector.bytesAsString() : selector)); }, storeIntoLiteralVariable: function(anAssociation) { this.print('storeIntoBinding: ' + anAssociation.assnKeyAsString()); }, storeIntoReceiverVariable: function(offset) { this.print('storeIntoInstVar: ' + offset); }, storeIntoTemporaryVariable: function(offset) { this.print('storeIntoTemp: ' + offset); }, pushNewArray: function(size) { this.print('push: (Array new: ' + size + ')'); }, popIntoNewArray: function(numElements) { this.print('pop: ' + numElements + ' into: (Array new: ' + numElements + ')'); }, pushRemoteTemp: function(offset , arrayOffset) { this.print('push: ' + offset + ' ofTemp: ' + arrayOffset); }, storeIntoRemoteTemp: function(offset , arrayOffset) { this.print('storeInto: ' + offset + ' ofTemp: ' + arrayOffset); }, popIntoRemoteTemp: function(offset , arrayOffset) { this.print('popInto: ' + offset + ' ofTemp: ' + arrayOffset); }, pushClosureCopy: function(numCopied, numArgs, blockSize) { var from = this.scanner.pc, to = from + blockSize; this.print('closure(' + from + '-' + (to-1) + '): ' + numCopied + ' copied, ' + numArgs + ' args'); for (var i = from; i < to; i++) this.innerIndents[i] = (this.innerIndents[i] || 0) + 1; if (to > this.endPC) this.endPC = to; }, pushFullClosure: function(literalIndex, numCopied, numArgs) { this.print('pushFullClosure: (self literalAt: ' + (literalIndex + 1) + ') numCopied: ' + numCopied + ' numArgs: ' + numArgs); }, callPrimitive: function(primitiveIndex) { this.print('primitive: ' + primitiveIndex); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.Primitives', 'initialization', { initialize: function(vm, display) { this.vm = vm; this.oldPrims = !this.vm.image.hasClosures; this.allowAccessBeyondSP = this.oldPrims; this.deferDisplayUpdates = false; this.semaphoresToSignal = []; this.initDisplay(display); this.initAtCache(); this.initModules(); this.initPlugins(); if (vm.image.isSpur) { this.charFromInt = this.charFromIntSpur; this.charToInt = this.charToIntSpur; this.identityHash = this.identityHashSpur; } }, initDisplay: function(display) { // Placeholder (can be replaced by a display module at runtime, before starting the Squeak interpreter) this.display = display; }, initModules: function() { this.loadedModules = {}; this.builtinModules = {}; this.patchModules = {}; this.interpreterProxy = new Squeak.InterpreterProxy(this.vm); }, initPlugins: function() { // Empty placeholder (can be replaced by a plugins module at runtime, before starting the Squeak interpreter) } }, 'dispatch', { quickSendOther: function(rcvr, lobits) { // returns true if it succeeds this.success = true; switch (lobits) { case 0x0: return this.popNandPushIfOK(2, this.objectAt(true,true,false)); // at: case 0x1: return this.popNandPushIfOK(3, this.objectAtPut(true,true,false)); // at:put: case 0x2: return this.popNandPushIfOK(1, this.objectSize(true)); // size //case 0x3: return false; // next //case 0x4: return false; // nextPut: //case 0x5: return false; // atEnd case 0x6: return this.popNandPushBoolIfOK(2, this.vm.stackValue(1) === this.vm.stackValue(0)); // == case 0x7: return this.popNandPushIfOK(1,this.vm.getClass(this.vm.top())); // class case 0x8: return this.popNandPushIfOK(2,this.doBlockCopy()); // blockCopy: case 0x9: return this.primitiveBlockValue(0); // value case 0xA: return this.primitiveBlockValue(1); // value: //case 0xB: return false; // do: //case 0xC: return false; // new //case 0xD: return false; // new: //case 0xE: return false; // x //case 0xF: return false; // y } return false; }, doPrimitive: function(index, argCount, primMethod) { this.success = true; switch (index) { // Integer Primitives (0-19) case 1: return this.popNandPushIntIfOK(argCount+1,this.stackInteger(1) + this.stackInteger(0)); // Integer.add case 2: return this.popNandPushIntIfOK(argCount+1,this.stackInteger(1) - this.stackInteger(0)); // Integer.subtract case 3: return this.popNandPushBoolIfOK(argCount+1, this.stackInteger(1) < this.stackInteger(0)); // Integer.less case 4: return this.popNandPushBoolIfOK(argCount+1, this.stackInteger(1) > this.stackInteger(0)); // Integer.greater case 5: return this.popNandPushBoolIfOK(argCount+1, this.stackInteger(1) <= this.stackInteger(0)); // Integer.leq case 6: return this.popNandPushBoolIfOK(argCount+1, this.stackInteger(1) >= this.stackInteger(0)); // Integer.geq case 7: return this.popNandPushBoolIfOK(argCount+1, this.stackInteger(1) === this.stackInteger(0)); // Integer.equal case 8: return this.popNandPushBoolIfOK(argCount+1, this.stackInteger(1) !== this.stackInteger(0)); // Integer.notequal case 9: return this.popNandPushIntIfOK(argCount+1,this.stackInteger(1) * this.stackInteger(0)); // Integer.multiply * case 10: return this.popNandPushIntIfOK(argCount+1,this.vm.quickDivide(this.stackInteger(1),this.stackInteger(0))); // Integer.divide / (fails unless exact) case 11: return this.popNandPushIntIfOK(argCount+1,this.vm.mod(this.stackInteger(1),this.stackInteger(0))); // Integer.mod \\ case 12: return this.popNandPushIntIfOK(argCount+1,this.vm.div(this.stackInteger(1),this.stackInteger(0))); // Integer.div // case 13: return this.popNandPushIntIfOK(argCount+1,this.stackInteger(1) / this.stackInteger(0) | 0); // Integer.quo case 14: return this.popNandPushIfOK(argCount+1,this.doBitAnd()); // SmallInt.bitAnd case 15: return this.popNandPushIfOK(argCount+1,this.doBitOr()); // SmallInt.bitOr case 16: return this.popNandPushIfOK(argCount+1,this.doBitXor()); // SmallInt.bitXor case 17: return this.popNandPushIfOK(argCount+1,this.doBitShift()); // SmallInt.bitShift case 18: return this.primitiveMakePoint(argCount, false); case 19: return false; // Guard primitive for simulation -- *must* fail // LargeInteger Primitives (20-39) // 32-bit logic is aliased to Integer prims above case 20: this.vm.warnOnce("missing primitive: 20 (primitiveRemLargeIntegers)"); return false; case 21: this.vm.warnOnce("missing primitive: 21 (primitiveAddLargeIntegers)"); return false; case 22: this.vm.warnOnce("missing primitive: 22 (primitiveSubtractLargeIntegers)"); return false; case 23: return this.primitiveLessThanLargeIntegers(argCount); case 24: return this.primitiveGreaterThanLargeIntegers(argCount); case 25: return this.primitiveLessOrEqualLargeIntegers(argCount); case 26: return this.primitiveGreaterOrEqualLargeIntegers(argCount); case 27: return this.primitiveEqualLargeIntegers(argCount); case 28: return this.primitiveNotEqualLargeIntegers(argCount); case 29: this.vm.warnOnce("missing primitive: 29 (primitiveMultiplyLargeIntegers)"); return false; case 30: this.vm.warnOnce("missing primitive: 30 (primitiveDivideLargeIntegers)"); return false; case 31: this.vm.warnOnce("missing primitive: 31 (primitiveModLargeIntegers)"); return false; case 32: this.vm.warnOnce("missing primitive: 32 (primitiveDivLargeIntegers)"); return false; case 33: this.vm.warnOnce("missing primitive: 33 (primitiveQuoLargeIntegers)"); return false; case 34: this.vm.warnOnce("missing primitive: 34 (primitiveBitAndLargeIntegers)"); return false; case 35: this.vm.warnOnce("missing primitive: 35 (primitiveBitOrLargeIntegers)"); return false; case 36: this.vm.warnOnce("missing primitive: 36 (primitiveBitXorLargeIntegers)"); return false; case 37: this.vm.warnOnce("missing primitive: 37 (primitiveBitShiftLargeIntegers)"); return false; case 38: return this.popNandPushIfOK(argCount+1, this.objectAt(false,false,false)); // Float basicAt case 39: return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,false,false)); // Float basicAtPut // Float Primitives (40-59) case 40: return this.popNandPushFloatIfOK(argCount+1,this.stackInteger(0)); // primitiveAsFloat case 41: return this.popNandPushFloatIfOK(argCount+1,this.stackFloat(1)+this.stackFloat(0)); // Float + case 42: return this.popNandPushFloatIfOK(argCount+1,this.stackFloat(1)-this.stackFloat(0)); // Float - case 43: return this.popNandPushBoolIfOK(argCount+1, this.stackFloat(1)this.stackFloat(0)); // Float > case 45: return this.popNandPushBoolIfOK(argCount+1, this.stackFloat(1)<=this.stackFloat(0)); // Float <= case 46: return this.popNandPushBoolIfOK(argCount+1, this.stackFloat(1)>=this.stackFloat(0)); // Float >= case 47: return this.popNandPushBoolIfOK(argCount+1, this.stackFloat(1)===this.stackFloat(0)); // Float = case 48: return this.popNandPushBoolIfOK(argCount+1, this.stackFloat(1)!==this.stackFloat(0)); // Float != case 49: return this.popNandPushFloatIfOK(argCount+1,this.stackFloat(1)*this.stackFloat(0)); // Float.mul case 50: return this.popNandPushFloatIfOK(argCount+1,this.safeFDiv(this.stackFloat(1),this.stackFloat(0))); // Float.div case 51: return this.popNandPushIfOK(argCount+1,this.floatAsSmallInt(this.stackFloat(0))); // Float.asInteger case 52: return this.popNandPushFloatIfOK(argCount+1,this.floatFractionPart(this.stackFloat(0))); case 53: return this.popNandPushIntIfOK(argCount+1, this.frexp_exponent(this.stackFloat(0)) - 1); // Float.exponent case 54: return this.popNandPushFloatIfOK(argCount+1, this.ldexp(this.stackFloat(1), this.stackFloat(0))); // Float.timesTwoPower case 55: return this.popNandPushFloatIfOK(argCount+1, Math.sqrt(this.stackFloat(0))); // SquareRoot case 56: return this.popNandPushFloatIfOK(argCount+1, Math.sin(this.stackFloat(0))); // Sine case 57: return this.popNandPushFloatIfOK(argCount+1, Math.atan(this.stackFloat(0))); // Arctan case 58: return this.popNandPushFloatIfOK(argCount+1, Math.log(this.stackFloat(0))); // LogN case 59: return this.popNandPushFloatIfOK(argCount+1, Math.exp(this.stackFloat(0))); // Exp // Subscript and Stream Primitives (60-67) case 60: return this.popNandPushIfOK(argCount+1, this.objectAt(false,false,false)); // basicAt: case 61: return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,false,false)); // basicAt:put: case 62: return this.popNandPushIfOK(argCount+1, this.objectSize(false)); // size case 63: return this.popNandPushIfOK(argCount+1, this.objectAt(false,true,false)); // String.basicAt: case 64: return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,true,false)); // String.basicAt:put: case 65: this.vm.warnOnce("missing primitive: 65 (primitiveNext)"); return false; case 66: this.vm.warnOnce("missing primitive: 66 (primitiveNextPut)"); return false; case 67: this.vm.warnOnce("missing primitive: 67 (primitiveAtEnd)"); return false; // StorageManagement Primitives (68-79) case 68: return this.popNandPushIfOK(argCount+1, this.objectAt(false,false,true)); // Method.objectAt: case 69: return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,false,true)); // Method.objectAt:put: case 70: return this.popNandPushIfOK(argCount+1, this.instantiateClass(this.stackNonInteger(0), 0)); // Class.new case 71: return this.popNandPushIfOK(argCount+1, this.instantiateClass(this.stackNonInteger(1), this.stackPos32BitInt(0))); // Class.new: case 72: return this.primitiveArrayBecome(argCount, false, true); // one way, do copy hash case 73: return this.popNandPushIfOK(argCount+1, this.objectAt(false,false,true)); // instVarAt: case 74: return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,false,true)); // instVarAt:put: case 75: return this.popNandPushIfOK(argCount+1, this.identityHash(this.stackNonInteger(0))); // Object.identityHash case 76: return this.primitiveStoreStackp(argCount); // (Blue Book: primitiveAsObject) case 77: return this.popNandPushIfOK(argCount+1, this.someInstanceOf(this.stackNonInteger(0))); // Class.someInstance case 78: return this.popNandPushIfOK(argCount+1, this.nextInstanceAfter(this.stackNonInteger(0))); // Object.nextInstance case 79: return this.primitiveNewMethod(argCount); // Compiledmethod.new // Control Primitives (80-89) case 80: return this.popNandPushIfOK(argCount+1,this.doBlockCopy()); // blockCopy: case 81: return this.primitiveBlockValue(argCount); // BlockContext.value case 82: return this.primitiveBlockValueWithArgs(argCount); // BlockContext.valueWithArguments: case 83: return this.vm.primitivePerform(argCount); // Object.perform:(with:)* case 84: return this.vm.primitivePerformWithArgs(argCount, false); // Object.perform:withArguments: case 85: return this.primitiveSignal(); // Semaphore.wait case 86: return this.primitiveWait(); // Semaphore.wait case 87: return this.primitiveResume(); // Process.resume case 88: return this.primitiveSuspend(); // Process.suspend case 89: return this.vm.flushMethodCache(); //primitiveFlushCache // Input/Output Primitives (90-109) case 90: return this.primitiveMousePoint(argCount); // mousePoint case 91: return this.primitiveTestDisplayDepth(argCount); // cursorLocPut in old images case 92: this.vm.warnOnce("missing primitive: 92 (primitiveSetDisplayMode)"); return false; case 93: return this.primitiveInputSemaphore(argCount); case 94: return this.primitiveGetNextEvent(argCount); case 95: return this.primitiveInputWord(argCount); case 96: return this.namedPrimitive('BitBltPlugin', 'primitiveCopyBits', argCount); case 97: return this.primitiveSnapshot(argCount); case 98: return this.primitiveStoreImageSegment(argCount); case 99: return this.primitiveLoadImageSegment(argCount); case 100: return this.vm.primitivePerformWithArgs(argCount, true); // Object.perform:withArguments:inSuperclass: (Blue Book: primitiveSignalAtTick) case 101: return this.primitiveBeCursor(argCount); // Cursor.beCursor case 102: return this.primitiveBeDisplay(argCount); // DisplayScreen.beDisplay case 103: return this.primitiveScanCharacters(argCount); case 104: this.vm.warnOnce("missing primitive: 104 (primitiveDrawLoop)"); return false; case 105: return this.popNandPushIfOK(argCount+1, this.doStringReplace()); // string and array replace case 106: return this.primitiveScreenSize(argCount); // actualScreenSize case 107: return this.primitiveMouseButtons(argCount); // Sensor mouseButtons case 108: return this.primitiveKeyboardNext(argCount); // Sensor kbdNext case 109: return this.primitiveKeyboardPeek(argCount); // Sensor kbdPeek // System Primitives (110-119) case 110: return this.popNandPushBoolIfOK(argCount+1, this.vm.stackValue(1) === this.vm.stackValue(0)); // == case 111: return this.popNandPushIfOK(argCount+1, this.vm.getClass(this.vm.top())); // Object.class case 112: return this.popNandPushIfOK(argCount+1, this.vm.image.bytesLeft()); //primitiveBytesLeft case 113: return this.primitiveQuit(argCount); case 114: return this.primitiveExitToDebugger(argCount); case 115: return this.primitiveChangeClass(argCount); case 116: return this.vm.flushMethodCacheForMethod(this.vm.top()); // after Squeak 2.2 uses 119 case 117: return this.doNamedPrimitive(argCount, primMethod); // named prims case 118: return this.primitiveDoPrimitiveWithArgs(argCount); case 119: return this.vm.flushMethodCacheForSelector(this.vm.top()); // before Squeak 2.3 uses 116 // Miscellaneous Primitives (120-149) case 120: return this.primitiveCalloutToFFI(argCount, primMethod); case 121: return this.primitiveImageName(argCount); //get+set imageName case 122: return this.primitiveReverseDisplay(argCount); // Blue Book: primitiveImageVolume case 123: this.vm.warnOnce("missing primitive: 123 (primitiveValueUninterruptably)"); return false; case 124: return this.popNandPushIfOK(argCount+1, this.registerSemaphore(Squeak.splOb_TheLowSpaceSemaphore)); case 125: return this.popNandPushIfOK(argCount+1, this.setLowSpaceThreshold()); case 126: return this.primitiveDeferDisplayUpdates(argCount); case 127: return this.primitiveShowDisplayRect(argCount); case 128: return this.primitiveArrayBecome(argCount, true, true); // both ways, do copy hash case 129: return this.popNandPushIfOK(argCount+1, this.vm.image.specialObjectsArray); //specialObjectsOop case 130: return this.primitiveFullGC(argCount); case 131: return this.primitivePartialGC(argCount); case 132: return this.popNandPushBoolIfOK(argCount+1, this.pointsTo(this.stackNonInteger(1), this.vm.top())); //Object.pointsTo case 133: return this.popNIfOK(argCount); //TODO primitiveSetInterruptKey case 134: return this.popNandPushIfOK(argCount+1, this.registerSemaphore(Squeak.splOb_TheInterruptSemaphore)); case 135: return this.popNandPushIfOK(argCount+1, this.millisecondClockValue()); case 136: return this.primitiveSignalAtMilliseconds(argCount); //Delay signal:atMs:(); case 137: return this.popNandPushIfOK(argCount+1, this.secondClock()); // seconds since Jan 1, 1901 case 138: return this.popNandPushIfOK(argCount+1, this.someObject()); // Object.someObject case 139: return this.popNandPushIfOK(argCount+1, this.nextObject(this.vm.top())); // Object.nextObject case 140: return this.primitiveBeep(argCount); case 141: return this.primitiveClipboardText(argCount); case 142: return this.popNandPushIfOK(argCount+1, this.makeStString(this.filenameToSqueak(Squeak.vmPath))); case 143: // short at and shortAtPut case 144: return this.primitiveShortAtAndPut(argCount); case 145: return this.primitiveConstantFill(argCount); case 146: return this.namedPrimitive('JoystickTabletPlugin', 'primitiveReadJoystick', argCount); case 147: return this.namedPrimitive('BitBltPlugin', 'primitiveWarpBits', argCount); case 148: return this.popNandPushIfOK(argCount+1, this.vm.image.clone(this.vm.top())); //shallowCopy case 149: return this.primitiveGetAttribute(argCount); // File Primitives (150-169) case 150: if (this.oldPrims) return this.primitiveFileAtEnd(argCount); case 151: if (this.oldPrims) return this.primitiveFileClose(argCount); case 152: if (this.oldPrims) return this.primitiveFileGetPosition(argCount); case 153: if (this.oldPrims) return this.primitiveFileOpen(argCount); case 154: if (this.oldPrims) return this.primitiveFileRead(argCount); case 155: if (this.oldPrims) return this.primitiveFileSetPosition(argCount); case 156: if (this.oldPrims) return this.primitiveFileDelete(argCount); case 157: if (this.oldPrims) return this.primitiveFileSize(argCount); break; // fail 150-157 if fell through case 158: if (this.oldPrims) return this.primitiveFileWrite(argCount); else this.vm.warnOnce("missing primitive: 158 (primitiveCompareWith)"); return false; case 159: if (this.oldPrims) return this.primitiveFileRename(argCount); return this.popNandPushIntIfOK(argCount+1, this.stackSigned53BitInt(0) * 1664525 & 0xFFFFFFF); // primitiveHashMultiply case 160: if (this.oldPrims) return this.primitiveDirectoryCreate(argCount); else return this.primitiveAdoptInstance(argCount); case 161: if (this.oldPrims) return this.primitiveDirectoryDelimitor(argCount); this.vm.warnOnce("missing primitive: 161 (primitiveSetIdentityHash)"); return false; case 162: if (this.oldPrims) return this.primitiveDirectoryLookup(argCount); break; // fail case 163: if (this.oldPrims) return this.primitiveDirectoryDelete(argCount); else this.vm.warnOnce("missing primitive: 163 (primitiveGetImmutability)"); return false; case 164: return this.popNandPushIfOK(argCount+1, this.vm.trueObj); // Fake primitiveSetImmutability case 165: case 166: return this.primitiveIntegerAtAndPut(argCount); case 167: return false; // Processor.yield case 168: return this.primitiveCopyObject(argCount); case 169: if (this.oldPrims) return this.primitiveDirectorySetMacTypeAndCreator(argCount); else return this.popNandPushBoolIfOK(argCount+1, this.vm.stackValue(1) !== this.vm.stackValue(0)); //new: primitiveNotIdentical // Sound Primitives (170-199) case 170: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundStart', argCount); else return this.primitiveAsCharacter(argCount); case 171: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundStartWithSemaphore', argCount); else return this.popNandPushIfOK(argCount+1, this.stackNonInteger(0).hash); //primitiveImmediateAsInteger case 172: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundStop', argCount); this.vm.warnOnce("missing primitive: 172 (primitiveFetchMourner)"); return this.popNandPushIfOK(argCount+1, this.vm.nilObj); // do not fail case 173: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundAvailableSpace', argCount); else return this.popNandPushIfOK(argCount+1, this.objectAt(false,false,true)); // slotAt: case 174: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundPlaySamples', argCount); else return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,false,true)); // slotAt:put: case 175: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundPlaySilence', argCount); else if (!this.vm.image.isSpur) { this.vm.warnOnce("primitive 175 called in non-spur image"); // workaround for Cuis return this.popNandPushIfOK(argCount+1, this.identityHash(this.stackNonInteger(0))); } else return this.popNandPushIfOK(argCount+1, this.behaviorHash(this.stackNonInteger(0))); case 176: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primWaveTableSoundmixSampleCountintostartingAtpan', argCount); else return this.popNandPushIfOK(argCount+1, this.vm.image.isSpur ? 0x3FFFFF : 0xFFF); // primitiveMaxIdentityHash case 177: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primFMSoundmixSampleCountintostartingAtpan', argCount); return this.popNandPushIfOK(argCount+1, this.allInstancesOf(this.stackNonInteger(0))); case 178: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primPluckedSoundmixSampleCountintostartingAtpan', argCount); return false; // allObjectsDo fallback code is just as fast and uses less memory case 179: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primSampledSoundmixSampleCountintostartingAtpan', argCount); break; // fail case 180: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primitiveMixFMSound', argCount); return false; // growMemoryByAtLeast case 181: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primitiveMixPluckedSound', argCount); return this.primitiveSizeInBytesOfInstance(argCount); case 182: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'oldprimSampledSoundmixSampleCountintostartingAtleftVolrightVol', argCount); return this.primitiveSizeInBytes(argCount); case 183: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primitiveApplyReverb', argCount); else return this.primitiveIsPinned(argCount); case 184: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primitiveMixLoopedSampledSound', argCount); else return this.primitivePin(argCount); case 185: if (this.oldPrims) return this.namedPrimitive('SoundGenerationPlugin', 'primitiveMixSampledSound', argCount); else return this.primitiveExitCriticalSection(argCount); case 186: if (this.oldPrims) break; // unused else return this.primitiveEnterCriticalSection(argCount); case 187: if (this.oldPrims) break; // unused else return this.primitiveTestAndSetOwnershipOfCriticalSection(argCount); case 188: if (this.oldPrims) break; // unused else return this.primitiveExecuteMethodArgsArray(argCount); case 189: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundInsertSamples', argCount); return false; // fail to fall back to primitiveExecuteMethodArgsArray (188) case 190: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundStartRecording', argCount); case 191: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundStopRecording', argCount); case 192: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundGetRecordingSampleRate', argCount); case 193: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundRecordSamples', argCount); case 194: if (this.oldPrims) return this.namedPrimitive('SoundPlugin', 'primitiveSoundSetRecordLevel', argCount); break; // fail 190-194 if fell through case 195: return false; // Context.findNextUnwindContextUpTo: case 196: return false; // Context.terminateTo: case 197: return false; // Context.findNextHandlerContextStarting case 198: return false; // MarkUnwindMethod (must fail) case 199: return false; // MarkHandlerMethod (must fail) // Networking Primitives (200-229) case 200: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveInitializeNetwork', argCount); else return this.primitiveClosureCopyWithCopiedValues(argCount); case 201: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverStartNameLookup', argCount); else return this.primitiveClosureValue(argCount); case 202: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverNameLookupResult', argCount); else return this.primitiveClosureValue(argCount); case 203: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverStartAddressLookup', argCount); else return this.primitiveClosureValue(argCount); case 204: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverAddressLookupResult', argCount); else return this.primitiveClosureValue(argCount); case 205: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverAbortLookup', argCount); else return this.primitiveClosureValue(argCount); case 206: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverLocalAddress', argCount); else return this.primitiveClosureValueWithArgs(argCount); case 207: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverStatus', argCount); else return this.primitiveFullClosureValue(argCount); case 208: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveResolverError', argCount); else return this.primitiveFullClosureValueWithArgs(argCount); case 209: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketCreate', argCount); else return this.primitiveFullClosureValueNoContextSwitch(argCount); case 210: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketDestroy', argCount); else return this.popNandPushIfOK(argCount+1, this.objectAt(false,false,false)); // contextAt: case 211: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketConnectionStatus', argCount); else return this.popNandPushIfOK(argCount+1, this.objectAtPut(false,false,false)); // contextAt:put: case 212: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketError', argCount); else return this.popNandPushIfOK(argCount+1, this.objectSize(false)); // contextSize case 213: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketLocalAddress', argCount); case 214: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketLocalPort', argCount); case 215: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketRemoteAddress', argCount); case 216: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketRemotePort', argCount); case 217: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketConnectToPort', argCount); case 218: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketListenWithOrWithoutBacklog', argCount); else return this.primitiveDoNamedPrimitive(argCount); case 219: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketCloseConnection', argCount); case 220: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketAbortConnection', argCount); break; // fail 212-220 if fell through case 221: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketReceiveDataBufCount', argCount); else return this.primitiveClosureValueNoContextSwitch(argCount); case 222: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketReceiveDataAvailable', argCount); else return this.primitiveClosureValueNoContextSwitch(argCount); case 223: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketSendDataBufCount', argCount); case 224: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketSendDone', argCount); case 225: if (this.oldPrims) return this.namedPrimitive('SocketPlugin', 'primitiveSocketAccept', argCount); break; // fail 223-229 if fell through // 225-229: unused // Other Primitives (230-249) case 230: return this.primitiveRelinquishProcessorForMicroseconds(argCount); case 231: return this.primitiveForceDisplayUpdate(argCount); case 232: this.vm.warnOnce("missing primitive: 232 (primitiveFormPrint)"); return false; case 233: return this.primitiveSetFullScreen(argCount); case 234: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveDecompressFromByteArray', argCount); case 235: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveCompareString', argCount); case 236: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveConvert8BitSigned', argCount); case 237: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveCompressToByteArray', argCount); break; // fail 234-237 if fell through case 238: if (this.oldPrims) return this.namedPrimitive('SerialPlugin', 'primitiveSerialPortOpen', argCount); else return this.namedPrimitive('FloatArrayPlugin', 'primitiveAt', argCount); case 239: if (this.oldPrims) return this.namedPrimitive('SerialPlugin', 'primitiveSerialPortClose', argCount); else return this.namedPrimitive('FloatArrayPlugin', 'primitiveAtPut', argCount); case 240: if (this.oldPrims) return this.namedPrimitive('SerialPlugin', 'primitiveSerialPortWrite', argCount); else return this.popNandPushIfOK(argCount+1, this.microsecondClockUTC()); case 241: if (this.oldPrims) return this.namedPrimitive('SerialPlugin', 'primitiveSerialPortRead', argCount); else return this.popNandPushIfOK(argCount+1, this.microsecondClockLocal()); case 242: if (this.oldPrims) break; // unused else return this.primitiveSignalAtUTCMicroseconds(argCount); case 243: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveTranslateStringWithTable', argCount); else this.vm.warnOnce("missing primitive: 243 (primitiveUpdateTimeZone)"); return false; case 244: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveFindFirstInString' , argCount); case 245: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveIndexOfAsciiInString', argCount); case 246: if (this.oldPrims) return this.namedPrimitive('MiscPrimitivePlugin', 'primitiveFindSubstring', argCount); break; // fail 243-246 if fell through // 247: unused case 248: return this.primitiveArrayBecome(argCount, false, false); // one way, do not copy hash case 249: return this.primitiveArrayBecome(argCount, false, true); // one way, opt. copy hash case 254: return this.primitiveVMParameter(argCount); //MIDI Primitives (520-539) case 521: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIClosePort', argCount); case 522: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIGetClock', argCount); case 523: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIGetPortCount', argCount); case 524: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIGetPortDirectionality', argCount); case 525: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIGetPortName', argCount); case 526: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIOpenPort', argCount); case 527: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIParameterGetOrSet', argCount); case 528: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIRead', argCount); case 529: return this.namedPrimitive('MIDIPlugin', 'primitiveMIDIWrite', argCount); // 530-539: reserved for extended MIDI primitives // Sound Codec Primitives case 550: return this.namedPrimitive('ADPCMCodecPlugin', 'primitiveDecodeMono', argCount); case 551: return this.namedPrimitive('ADPCMCodecPlugin', 'primitiveDecodeStereo', argCount); case 552: return this.namedPrimitive('ADPCMCodecPlugin', 'primitiveEncodeMono', argCount); case 553: return this.namedPrimitive('ADPCMCodecPlugin', 'primitiveEncodeStereo', argCount); // External primitive support primitives (570-574) // case 570: return this.primitiveFlushExternalPrimitives(argCount); case 571: return this.primitiveUnloadModule(argCount); case 572: return this.primitiveListBuiltinModule(argCount); case 573: return this.primitiveListLoadedModule(argCount); case 575: this.vm.warnOnce("missing primitive: 575 (primitiveHighBit)"); return false; // this is not really a primitive, see findSelectorInClass() case 576: return this.vm.primitiveInvokeObjectAsMethod(argCount, primMethod); case 578: this.vm.warnOnce("missing primitive: 578 (primitiveSuspendAndBackupPC)"); return false; // see bit 5 of vmParameterAt: 65 } console.error("primitive " + index + " not implemented yet"); return false; }, namedPrimitive: function(modName, functionName, argCount) { // duplicated in loadFunctionFrom() var mod = modName === "" ? this : this.loadedModules[modName]; var justLoaded = false; if (mod === undefined) { // null if earlier load failed mod = this.loadModule(modName); this.loadedModules[modName] = mod; justLoaded = true; } var result = false; var sp = this.vm.sp; if (mod) { this.interpreterProxy.argCount = argCount; this.interpreterProxy.primitiveName = functionName; var primitive = mod[functionName]; if (typeof primitive === "function") { result = mod[functionName](argCount); } else if (typeof primitive === "string") { // allow late binding for built-ins result = this[primitive](argCount); } else { this.vm.warnOnce("missing primitive: " + modName + "." + functionName); } } else if (justLoaded) { if (this.success) this.vm.warnOnce("missing module: " + modName + " (" + functionName + ")"); else this.vm.warnOnce("failed to load module: " + modName + " (" + functionName + ")"); } if ((result === true || (result !== false && this.success)) && this.vm.sp !== sp - argCount && !this.vm.frozen) { this.vm.warnOnce("stack unbalanced after primitive " + modName + "." + functionName, "error"); } if (result === true || result === false) return result; return this.success; }, doNamedPrimitive: function(argCount, primMethod) { if (primMethod.pointersSize() < 2) return false; var firstLiteral = primMethod.pointers[1]; // skip method header if (firstLiteral.pointersSize() !== 4) return false; this.primMethod = primMethod; var moduleName = firstLiteral.pointers[0].bytesAsString(); var functionName = firstLiteral.pointers[1].bytesAsString(); return this.namedPrimitive(moduleName, functionName, argCount); }, fakePrimitive: function(prim, retVal, argCount) { // fake a named primitive // prim and retVal need to be curried when used: // this.fakePrimitive.bind(this, "Module.primitive", 42) this.vm.warnOnce("faking primitive: " + prim); if (retVal === undefined) this.vm.popN(argCount); else this.vm.popNandPush(argCount+1, this.makeStObject(retVal)); return true; }, }, 'modules', { loadModule: function(modName) { var mod = Squeak.externalModules[modName] || this.builtinModules[modName] || this.loadModuleDynamically(modName); if (!mod) return null; if (this.patchModules[modName]) this.patchModule(mod, modName); if (mod.setInterpreter) { if (!mod.setInterpreter(this.interpreterProxy)) { console.log("Wrong interpreter proxy version: " + modName); return null; } } var initFunc = mod.initialiseModule; if (typeof initFunc === 'function') { mod.initialiseModule(); } else if (typeof initFunc === 'string') { // allow late binding for built-ins this[initFunc](); } if (this.interpreterProxy.failed()) { console.log("Module initialization failed: " + modName); return null; } if (mod.getModuleName) modName = mod.getModuleName(); console.log("Loaded module: " + modName); return mod; }, loadModuleDynamically: function(modName) { // Placeholder (can be replaced by a module loader at runtime, before starting the Squeak interpreter) return undefined; }, patchModule: function(mod, modName) { var patch = this.patchModules[modName]; for (var key in patch) mod[key] = patch[key]; }, unloadModule: function(modName) { var mod = this.loadedModules[modName]; if (!modName || !mod|| mod === this) return null; delete this.loadedModules[modName]; var unloadFunc = mod.unloadModule; if (typeof unloadFunc === 'function') { mod.unloadModule(this); } else if (typeof unloadFunc === 'string') { // allow late binding for built-ins this[unloadFunc](this); } console.log("Unloaded module: " + modName); return mod; }, loadFunctionFrom: function(functionName, modName) { // copy of namedPrimitive() returning the bound function instead of calling it var mod = modName === "" ? this : this.loadedModules[modName]; if (mod === undefined) { // null if earlier load failed mod = this.loadModule(modName); this.loadedModules[modName] = mod; } if (!mod) return null; var func = mod[functionName]; if (typeof func === "function") { return func.bind(mod); } else if (typeof func === "string") { return (this[func]).bind(this); } this.vm.warnOnce("missing primitive: " + modName + "." + functionName); return null; }, primitiveUnloadModule: function(argCount) { var moduleName = this.stackNonInteger(0).bytesAsString(); if (!moduleName) return false; this.unloadModule(moduleName); return this.popNIfOK(argCount); }, primitiveListBuiltinModule: function(argCount) { var index = this.stackInteger(0) - 1; if (!this.success) return false; var moduleNames = Object.keys(this.builtinModules); return this.popNandPushIfOK(argCount + 1, this.makeStObject(moduleNames[index])); }, primitiveListLoadedModule: function(argCount) { var index = this.stackInteger(0) - 1; if (!this.success) return false; var moduleNames = []; for (var key in this.loadedModules) { var module = this.loadedModules[key]; if (module) { var moduleName = module.getModuleName ? module.getModuleName() : key; moduleNames.push(moduleName); } } return this.popNandPushIfOK(argCount + 1, this.makeStObject(moduleNames[index])); }, }, 'stack access', { popNIfOK: function(nToPop) { if (!this.success) return false; this.vm.popN(nToPop); return true; }, pop2andPushBoolIfOK: function(bool) { this.vm.success = this.success; return this.vm.pop2AndPushBoolResult(bool); }, popNandPushBoolIfOK: function(nToPop, bool) { if (!this.success) return false; this.vm.popNandPush(nToPop, bool ? this.vm.trueObj : this.vm.falseObj); return true; }, popNandPushIfOK: function(nToPop, returnValue) { if (!this.success || returnValue == null) return false; this.vm.popNandPush(nToPop, returnValue); return true; }, popNandPushIntIfOK: function(nToPop, returnValue) { if (!this.success || !this.vm.canBeSmallInt(returnValue)) return false; this.vm.popNandPush(nToPop, returnValue); return true; }, popNandPushFloatIfOK: function(nToPop, returnValue) { if (!this.success) return false; this.vm.popNandPush(nToPop, this.makeFloat(returnValue)); return true; }, stackNonInteger: function(nDeep) { return this.checkNonInteger(this.vm.stackValue(nDeep)); }, stackInteger: function(nDeep) { return this.checkSmallInt(this.vm.stackValue(nDeep)); }, stackPos32BitInt: function(nDeep) { return this.positive32BitValueOf(this.vm.stackValue(nDeep)); }, pos32BitIntFor: function(signed32) { // Return the 32-bit quantity as an unsigned 32-bit integer if (signed32 >= 0 && signed32 <= Squeak.MaxSmallInt) return signed32; var lgIntClass = this.vm.specialObjects[Squeak.splOb_ClassLargePositiveInteger], lgIntObj = this.vm.instantiateClass(lgIntClass, 4), bytes = lgIntObj.bytes; for (var i=0; i<4; i++) bytes[i] = (signed32>>>(8*i)) & 255; return lgIntObj; }, pos53BitIntFor: function(longlong) { // Return the quantity as an unsigned 64-bit integer if (longlong <= 0xFFFFFFFF) return this.pos32BitIntFor(longlong); if (longlong > 0x1FFFFFFFFFFFFF) { console.warn("Out of range: pos53BitIntFor(" + longlong + ")"); this.success = false; return 0; } var sz = longlong <= 0xFFFFFFFFFF ? 5 : longlong <= 0xFFFFFFFFFFFF ? 6 : 7; var lgIntClass = this.vm.specialObjects[Squeak.splOb_ClassLargePositiveInteger], lgIntObj = this.vm.instantiateClass(lgIntClass, sz), bytes = lgIntObj.bytes; for (var i = 0; i < sz; i++) { bytes[i] = longlong & 255; longlong /= 256; } return lgIntObj; }, stackSigned32BitInt: function(nDeep) { var stackVal = this.vm.stackValue(nDeep); if (typeof stackVal === "number") { // SmallInteger return stackVal; } if (stackVal.bytesSize() !== 4) { this.success = false; return 0; } var bytes = stackVal.bytes, value = 0; for (var i = 0, f = 1; i < 4; i++, f *= 256) value += bytes[i] * f; if (this.isA(stackVal, Squeak.splOb_ClassLargePositiveInteger) && value <= 0x7FFFFFFF) return value; if (this.isA(stackVal, Squeak.splOb_ClassLargeNegativeInteger) && -value >= -2147483648) return -value; this.success = false; return 0; }, signed32BitIntegerFor: function(signed32) { // Return the 32-bit quantity as a signed 32-bit integer if (signed32 >= Squeak.MinSmallInt && signed32 <= Squeak.MaxSmallInt) return signed32; var negative = signed32 < 0, unsigned = negative ? -signed32 : signed32, lgIntClass = negative ? Squeak.splOb_ClassLargeNegativeInteger : Squeak.splOb_ClassLargePositiveInteger, lgIntObj = this.vm.instantiateClass(this.vm.specialObjects[lgIntClass], 4), bytes = lgIntObj.bytes; for (var i=0; i<4; i++) bytes[i] = (unsigned>>>(8*i)) & 255; return lgIntObj; }, stackFloat: function(nDeep) { return this.checkFloat(this.vm.stackValue(nDeep)); }, stackBoolean: function(nDeep) { return this.checkBoolean(this.vm.stackValue(nDeep)); }, stackSigned53BitInt:function(nDeep) { var stackVal = this.vm.stackValue(nDeep); if (typeof stackVal === "number") { // SmallInteger return stackVal; } var n = stackVal.bytesSize(); if (n <= 7) { var bytes = stackVal.bytes, value = 0; for (var i = 0, f = 1; i < n; i++, f *= 256) value += bytes[i] * f; if (value <= 0x1FFFFFFFFFFFFF) { if (this.isA(stackVal, Squeak.splOb_ClassLargePositiveInteger)) return value; if (this.isA(stackVal, Squeak.splOb_ClassLargeNegativeInteger)) return -value; } } this.success = false; return 0; }, }, 'numbers', { doBitAnd: function() { var rcvr = this.stackPos32BitInt(1); var arg = this.stackPos32BitInt(0); if (!this.success) return 0; return this.pos32BitIntFor(rcvr & arg); }, doBitOr: function() { var rcvr = this.stackPos32BitInt(1); var arg = this.stackPos32BitInt(0); if (!this.success) return 0; return this.pos32BitIntFor(rcvr | arg); }, doBitXor: function() { var rcvr = this.stackPos32BitInt(1); var arg = this.stackPos32BitInt(0); if (!this.success) return 0; return this.pos32BitIntFor(rcvr ^ arg); }, doBitShift: function() { // SmallInts are handled by the bytecode, // so rcvr is a LargeInteger var rcvr = this.stackPos32BitInt(1); var arg = this.stackInteger(0); if (!this.success) return 0; // we're not using safeShift() here because we want the full 32 bits // and we know the receiver is unsigned var result; if (arg < 0) { if (arg < -31) return 0; // JS would treat arg=32 as arg=0 result = rcvr >>> -arg; } else { if (arg > 31) { this.success = false; // rcvr is never 0 return 0; } result = rcvr << arg; // check for lost bits by seeing if computation is reversible if ((result >>> arg) !== rcvr) { this.success = false; return 0; } } return this.pos32BitIntFor(result); }, safeFDiv: function(dividend, divisor) { if (divisor === 0.0) { this.success = false; return 1.0; } return dividend / divisor; }, floatAsSmallInt: function(float) { var truncated = float >= 0 ? Math.floor(float) : Math.ceil(float); return this.ensureSmallInt(truncated); }, floatFractionPart: function(float) { if (-9007199254740991 /* -((1 << 53) - 1) */ <= float && float <= 9007199254740991 /* (1 << 53) - 1 */) { return float - Math.floor(float); } else { this.success = false; return 0; } }, frexp_exponent: function(value) { // frexp separates a float into its mantissa and exponent if (value == 0.0) return 0; // zero is special var data = new DataView(new ArrayBuffer(8)); data.setFloat64(0, value); // for accessing IEEE-754 exponent bits var bits = (data.getUint32(0) >>> 20) & 0x7FF; if (bits === 0) { // we have a subnormal float (actual zero was handled above) // make it normal by multiplying a large number data.setFloat64(0, value * Math.pow(2, 64)); // access its exponent bits, and subtract the large number's exponent bits = ((data.getUint32(0) >>> 20) & 0x7FF) - 64; } var exponent = bits - 1022; // apply bias // mantissa = this.ldexp(value, -exponent) // not needed for Squeak return exponent; }, ldexp: function(mantissa, exponent) { // construct a float as mantissa * 2 ^ exponent // avoid multiplying by Infinity and Zero and rounding errors // by splitting the exponent (thanks to Nicolas Cellier) // 3 multiplies needed for e.g. ldexp(5e-324, 1023+1074) var steps = Math.min(3, Math.ceil(Math.abs(exponent) / 1023)); var result = mantissa; for (var i = 0; i < steps; i++) result *= Math.pow(2, Math.floor((exponent + i) / steps)); return result; }, primitiveLessThanLargeIntegers: function(argCount) { return this.popNandPushBoolIfOK(argCount+1, this.stackSigned53BitInt(1) < this.stackSigned53BitInt(0)); }, primitiveGreaterThanLargeIntegers: function(argCount) { return this.popNandPushBoolIfOK(argCount+1, this.stackSigned53BitInt(1) > this.stackSigned53BitInt(0)); }, primitiveLessOrEqualLargeIntegers: function(argCount) { return this.popNandPushBoolIfOK(argCount+1, this.stackSigned53BitInt(1) <= this.stackSigned53BitInt(0)); }, primitiveGreaterOrEqualLargeIntegers: function(argCount) { return this.popNandPushBoolIfOK(argCount+1, this.stackSigned53BitInt(1) >= this.stackSigned53BitInt(0)); }, primitiveEqualLargeIntegers: function(argCount) { return this.popNandPushBoolIfOK(argCount+1, this.stackSigned53BitInt(1) === this.stackSigned53BitInt(0)); }, primitiveNotEqualLargeIntegers: function(argCount) { return this.popNandPushBoolIfOK(argCount+1, this.stackSigned53BitInt(1) !== this.stackSigned53BitInt(0)); }, }, 'utils', { floatOrInt: function(obj) { if (obj.isFloat) return obj.float; if (typeof obj === "number") return obj; // SmallInteger return 0; }, positive32BitValueOf: function(obj) { if (typeof obj === "number") { // SmallInteger if (obj >= 0) return obj; this.success = false; return 0; } if (!this.isA(obj, Squeak.splOb_ClassLargePositiveInteger) || obj.bytesSize() !== 4) { this.success = false; return 0; } var bytes = obj.bytes, value = 0; for (var i = 0, f = 1; i < 4; i++, f *= 256) value += bytes[i] * f; return value; }, checkFloat: function(maybeFloat) { // returns a number and sets success if (maybeFloat.isFloat) return maybeFloat.float; if (typeof maybeFloat === "number") // SmallInteger return maybeFloat; this.success = false; return 0.0; }, checkSmallInt: function(maybeSmall) { // returns an int and sets success if (typeof maybeSmall === "number") return maybeSmall; this.success = false; return 0; }, checkNonInteger: function(obj) { // returns a SqObj and sets success if (typeof obj !== "number") return obj; this.success = false; return this.vm.nilObj; }, checkBoolean: function(obj) { // returns true/false and sets success if (obj.isTrue) return true; if (obj.isFalse) return false; return this.success = false; }, indexableSize: function(obj) { if (typeof obj === "number") return -1; // -1 means not indexable return obj.indexableSize(this); }, isA: function(obj, knownClass) { return obj.sqClass === this.vm.specialObjects[knownClass]; }, isKindOf: function(obj, knownClass) { var classOrSuper = obj.sqClass; var theClass = typeof knownClass === "number" ? this.vm.specialObjects[knownClass] : knownClass; while (!classOrSuper.isNil) { if (classOrSuper === theClass) return true; classOrSuper = classOrSuper.superclass(); } return false; }, isAssociation: function(obj) { if (this.associationClass && obj.sqClass === this.associationClass) return true; if (!obj.pointers || obj.pointers.length !== 2) return false; // we know the Processor binding is "like" an association, but in newer images it's // actually a Binding object, which only shares the superclass LookupKey with Association var lookupKeyClass = this.vm.specialObjects[Squeak.splOb_SchedulerAssociation].sqClass; while (lookupKeyClass.superclass().classInstSize() > 0) lookupKeyClass = lookupKeyClass.superclass(); var isAssociation = this.isKindOf(obj, lookupKeyClass); if (isAssociation) this.associationClass = obj.sqClass; // cache for next time return isAssociation; }, ensureSmallInt: function(number) { if (number === (number|0) && this.vm.canBeSmallInt(number)) return number; this.success = false; return 0; }, charFromInt: function(ascii) { var charTable = this.vm.specialObjects[Squeak.splOb_CharacterTable]; var char = charTable.pointers[ascii]; if (char) return char; var charClass = this.vm.specialObjects[Squeak.splOb_ClassCharacter]; char = this.vm.instantiateClass(charClass, 0); char.pointers[0] = ascii; return char; }, charFromIntSpur: function(unicode) { return this.vm.image.getCharacter(unicode); }, charToInt: function(obj) { return obj.pointers[0]; }, charToIntSpur: function(obj) { return obj.hash; }, makeFloat: function(value) { var floatClass = this.vm.specialObjects[Squeak.splOb_ClassFloat]; var newFloat = this.vm.instantiateClass(floatClass, 2); newFloat.float = value; return newFloat; }, makeLargeIfNeeded: function(integer) { return this.vm.canBeSmallInt(integer) ? integer : this.makeLargeInt(integer); }, makeLargeInt: function(integer) { if (integer < 0) throw Error("negative large ints not implemented yet"); if (integer > 0xFFFFFFFF) throw Error("large large ints not implemented yet"); return this.pos32BitIntFor(integer); }, makePointWithXandY: function(x, y) { var pointClass = this.vm.specialObjects[Squeak.splOb_ClassPoint]; var newPoint = this.vm.instantiateClass(pointClass, 0); newPoint.pointers[Squeak.Point_x] = x; newPoint.pointers[Squeak.Point_y] = y; return newPoint; }, makeStArray: function(jsArray, proxyClass) { var array = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassArray], jsArray.length); for (var i = 0; i < jsArray.length; i++) array.pointers[i] = this.makeStObject(jsArray[i], proxyClass); return array; }, makeStByteArray: function(jsArray) { var array = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassByteArray], jsArray.length); for (var i = 0; i < jsArray.length; i++) array.bytes[i] = jsArray[i] & 0xff; return array; }, makeStString: function(jsString) { var stString = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassString], jsString.length); for (var i = 0; i < jsString.length; ++i) stString.bytes[i] = jsString.charCodeAt(i) & 0xFF; return stString; }, makeStStringFromBytes: function(bytes, zeroTerminated) { var length = bytes.length; if (zeroTerminated) { length = bytes.indexOf(0); if (length < 0) length = bytes.length; } var stString = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassString], length); for (var i = 0; i < length; ++i) stString.bytes[i] = bytes[i]; return stString; }, makeStObject: function(obj, proxyClass) { if (obj === undefined || obj === null) return this.vm.nilObj; if (obj === true) return this.vm.trueObj; if (obj === false) return this.vm.falseObj; if (obj.sqClass) return obj; if (typeof obj === "number") if (obj === (obj|0)) return this.makeLargeIfNeeded(obj); else return this.makeFloat(obj); if (proxyClass) { // wrap in JS proxy instance var stObj = this.vm.instantiateClass(proxyClass, 0); stObj.jsObject = obj; return stObj; } // A direct test of the buffer's constructor doesn't work on Safari 10.0. if (typeof obj === "string" || obj.constructor.name === "Uint8Array") return this.makeStString(obj); if (obj.constructor.name === "Array") return this.makeStArray(obj); throw Error("cannot make smalltalk object"); }, pointsTo: function(rcvr, arg) { if (!rcvr.pointers) return false; return rcvr.pointers.indexOf(arg) >= 0; }, asUint8Array: function(buffer) { // A direct test of the buffer's constructor doesn't work on Safari 10.0. if (buffer.constructor.name === "Uint8Array") return buffer; if (buffer.constructor.name === "ArrayBuffer") return new Uint8Array(buffer); if (typeof buffer === "string") { var array = new Uint8Array(buffer.length); for (var i = 0; i < buffer.length; i++) array[i] = buffer.charCodeAt(i); return array; } throw Error("unknown buffer type"); }, filenameToSqueak: function(unixpath) { var slash = unixpath[0] !== "/" ? "/" : "", filepath = "/SqueakJS" + slash + unixpath; // add SqueakJS if (this.emulateMac) filepath = ("Macintosh HD" + filepath) // add Mac volume .replace(/\//g, "€").replace(/:/g, "/").replace(/€/g, ":"); // substitute : for / return filepath; }, filenameFromSqueak: function(filepath) { var unixpath = !this.emulateMac ? filepath : filepath.replace(/^[^:]*:/, ":") // remove volume .replace(/\//g, "€").replace(/:/g, "/").replace(/€/g, ":"); // substitute : for / unixpath = unixpath.replace(/^\/*SqueakJS\/?/, "/"); // strip SqueakJS /**/ return unixpath; }, }, 'indexing', { objectAt: function(cameFromBytecode, convertChars, includeInstVars) { //Returns result of at: or sets success false var array = this.stackNonInteger(1); var index = this.stackPos32BitInt(0); //note non-int returns zero if (!this.success) return array; var info; if (cameFromBytecode) {// fast entry checks cache info = this.atCache[array.hash & this.atCacheMask]; if (info.array !== array) {this.success = false; return array;} } else {// slow entry installs in cache if appropriate if (array.isFloat) { // present float as word array var floatData = array.floatData(); if (index==1) return this.pos32BitIntFor(floatData.getUint32(0, false)); if (index==2) return this.pos32BitIntFor(floatData.getUint32(4, false)); this.success = false; return array; } info = this.makeAtCacheInfo(this.atCache, this.vm.specialSelectors[32], array, convertChars, includeInstVars); } if (index < 1 || index > info.size) {this.success = false; return array;} if (includeInstVars) //pointers... instVarAt and objectAt return array.pointers[index-1]; if (array.isPointers()) //pointers... normal at: return array.pointers[index-1+info.ivarOffset]; if (array.isWords()) // words... if (info.convertChars) return this.charFromInt(array.words[index-1] & 0x3FFFFFFF); else return this.pos32BitIntFor(array.words[index-1]); if (array.isBytes()) // bytes... if (info.convertChars) return this.charFromInt(array.bytes[index-1] & 0xFF); else return array.bytes[index-1] & 0xFF; // methods must simulate Squeak's method indexing var offset = array.pointersSize() * 4; if (index-1-offset < 0) {this.success = false; return array;} //reading lits as bytes return array.bytes[index-1-offset] & 0xFF; }, objectAtPut: function(cameFromBytecode, convertChars, includeInstVars) { //Returns result of at:put: or sets success false var array = this.stackNonInteger(2); var index = this.stackPos32BitInt(1); //note non-int returns zero if (!this.success) return array; var info; if (cameFromBytecode) {// fast entry checks cache info = this.atPutCache[array.hash & this.atCacheMask]; if (info.array !== array) {this.success = false; return array;} } else {// slow entry installs in cache if appropriate if (array.isFloat) { // present float as word array var wordToPut = this.stackPos32BitInt(0); if (this.success && (index == 1 || index == 2)) { var floatData = array.floatData(); floatData.setUint32(index == 1 ? 0 : 4, wordToPut, false); array.float = floatData.getFloat64(0); } else this.success = false; return this.vm.stackValue(0); } info = this.makeAtCacheInfo(this.atPutCache, this.vm.specialSelectors[34], array, convertChars, includeInstVars); } if (index<1 || index>info.size) {this.success = false; return array;} var objToPut = this.vm.stackValue(0); if (includeInstVars) {// pointers... instVarAtPut and objectAtPut array.dirty = true; return array.pointers[index-1] = objToPut; //eg, objectAt: } if (array.isPointers()) {// pointers... normal atPut array.dirty = true; return array.pointers[index-1+info.ivarOffset] = objToPut; } var intToPut; if (array.isWords()) { // words... if (convertChars) { // put a character... if (objToPut.sqClass !== this.vm.specialObjects[Squeak.splOb_ClassCharacter]) {this.success = false; return objToPut;} intToPut = this.charToInt(objToPut); if (typeof intToPut !== "number") {this.success = false; return objToPut;} } else { intToPut = this.stackPos32BitInt(0); } if (this.success) array.words[index-1] = intToPut; return objToPut; } // bytes... if (convertChars) { // put a character... if (objToPut.sqClass !== this.vm.specialObjects[Squeak.splOb_ClassCharacter]) {this.success = false; return objToPut;} intToPut = this.charToInt(objToPut); if (typeof intToPut !== "number") {this.success = false; return objToPut;} } else { // put a byte... if (typeof objToPut !== "number") {this.success = false; return objToPut;} intToPut = objToPut; } if (intToPut<0 || intToPut>255) {this.success = false; return objToPut;} if (array.isBytes()) // bytes... {array.bytes[index-1] = intToPut; return objToPut;} // methods must simulate Squeak's method indexing var offset = array.pointersSize() * 4; if (index-1-offset < 0) {this.success = false; return array;} //writing lits as bytes array.bytes[index-1-offset] = intToPut; return objToPut; }, objectSize: function(cameFromBytecode) { var rcvr = this.vm.stackValue(0), size = -1; if (cameFromBytecode) { // must only handle classes with size == basicSize, fail otherwise if (rcvr.sqClass === this.vm.specialObjects[Squeak.splOb_ClassArray]) { size = rcvr.pointersSize(); } else if (rcvr.sqClass === this.vm.specialObjects[Squeak.splOb_ClassString]) { size = rcvr.bytesSize(); } } else { // basicSize size = this.indexableSize(rcvr); } if (size === -1) {this.success = false; return -1} return this.pos32BitIntFor(size); }, initAtCache: function() { // The purpose of the at-cache is to allow fast (bytecode) access to at/atput code // without having to check whether this object has overridden at, etc. this.atCacheSize = 32; // must be power of 2 this.atCacheMask = this.atCacheSize - 1; //...so this is a mask this.atCache = []; this.atPutCache = []; this.nonCachedInfo = {}; for (var i= 0; i < this.atCacheSize; i++) { this.atCache.push({}); this.atPutCache.push({}); } }, makeAtCacheInfo: function(atOrPutCache, atOrPutSelector, array, convertChars, includeInstVars) { //Make up an info object and store it in the atCache or the atPutCache. //If it's not cacheable (not a non-super send of at: or at:put:) //then return the info in nonCachedInfo. //Note that info for objectAt (includeInstVars) will have //a zero ivarOffset, and a size that includes the extra instVars var info; var cacheable = (this.vm.verifyAtSelector === atOrPutSelector) //is at or atPut && (this.vm.verifyAtClass === array.sqClass) //not a super send && !this.vm.isContext(array); //not a context (size can change) info = cacheable ? atOrPutCache[array.hash & this.atCacheMask] : this.nonCachedInfo; info.array = array; info.convertChars = convertChars; if (includeInstVars) { info.size = array.instSize() + Math.max(0, array.indexableSize(this)); info.ivarOffset = 0; } else { info.size = array.indexableSize(this); info.ivarOffset = array.isPointers() ? array.instSize() : 0; } return info; }, }, 'basic',{ instantiateClass: function(clsObj, indexableSize) { if (indexableSize * 4 > this.vm.image.bytesLeft()) { // we're not really out of memory, we have no idea how much memory is available // but we need to stop runaway allocations console.warn("squeak: out of memory, failing allocation"); this.success = false; this.vm.primFailCode = Squeak.PrimErrNoMemory; return null; } else { return this.vm.instantiateClass(clsObj, indexableSize); } }, someObject: function() { return this.vm.image.firstOldObject; }, nextObject: function(obj) { return this.vm.image.objectAfter(obj) || 0; }, someInstanceOf: function(clsObj) { var someInstance = this.vm.image.someInstanceOf(clsObj); if (someInstance) return someInstance; this.success = false; return 0; }, nextInstanceAfter: function(obj) { var nextInstance = this.vm.image.nextInstanceAfter(obj); if (nextInstance) return nextInstance; this.success = false; return 0; }, allInstancesOf: function(clsObj) { var instances = this.vm.image.allInstancesOf(clsObj); var array = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassArray], instances.length); array.pointers = instances; return array; }, identityHash: function(obj) { return obj.hash; }, identityHashSpur: function(obj) { var hash = obj.hash; if (hash > 0) return hash; return obj.hash = this.newObjectHash(); }, behaviorHash: function(obj) { var hash = obj.hash; if (hash > 0) return hash; return this.vm.image.enterIntoClassTable(obj); }, newObjectHash: function(obj) { return Math.floor(Math.random() * 0x3FFFFE) + 1; }, primitivePin: function(argCount) { // For us, pinning is a no-op, so we just toggle the pinned flag var rcvr = this.stackNonInteger(1), pin = this.stackBoolean(0); if (!this.success) return false; var wasPinned = rcvr.pinned; rcvr.pinned = pin; return this.popNandPushIfOK(argCount + 1, this.makeStObject(!!wasPinned)); }, primitiveIsPinned: function(argCount) { var rcvr = this.stackNonInteger(0); if (!this.success) return false; return this.popNandPushIfOK(argCount + 1, this.makeStObject(!!rcvr.pinned)); }, primitiveSizeInBytesOfInstance: function(argCount) { if (argCount > 1) return false; var classObj = this.stackNonInteger(argCount), nElements = argCount ? this.stackInteger(0) : 0, bytes = classObj.classByteSizeOfInstance(nElements); return this.popNandPushIfOK(argCount + 1, this.makeLargeIfNeeded(bytes)); }, primitiveSizeInBytes: function(argCount) { var object = this.stackNonInteger(0), bytes = object.totalBytes(); return this.popNandPushIfOK(argCount + 1, this.makeLargeIfNeeded(bytes)); }, primitiveAsCharacter: function(argCount) { var unicode = this.stackInteger(0); if (unicode < 0 || unicode > 0x3FFFFFFF) return false; var char = this.charFromInt(unicode); if (!char) return false; return this.popNandPushIfOK(argCount + 1, char); }, primitiveFullGC: function(argCount) { this.vm.image.fullGC("primitive"); var bytes = this.vm.image.bytesLeft(); return this.popNandPushIfOK(argCount+1, this.makeLargeIfNeeded(bytes)); }, primitivePartialGC: function(argCount) { var young = this.vm.image.partialGC("primitive"); var youngSpaceBytes = 0; while (young) { youngSpaceBytes += young.totalBytes(); young = young.nextObject; } console.log(" old space: " + this.vm.image.oldSpaceBytes.toLocaleString() + " bytes, " + "young space: " + youngSpaceBytes.toLocaleString() + " bytes, " + "total: " + (this.vm.image.oldSpaceBytes + youngSpaceBytes).toLocaleString() + " bytes"); var bytes = this.vm.image.bytesLeft() - youngSpaceBytes; return this.popNandPushIfOK(argCount+1, this.makeLargeIfNeeded(bytes)); }, primitiveMakePoint: function(argCount, checkNumbers) { var x = this.vm.stackValue(1); var y = this.vm.stackValue(0); if (checkNumbers) { this.checkFloat(x); this.checkFloat(y); if (!this.success) return false; } this.vm.popNandPush(1+argCount, this.makePointWithXandY(x, y)); return true; }, primitiveStoreStackp: function(argCount) { var ctxt = this.stackNonInteger(1), newStackp = this.stackInteger(0); if (!this.success || newStackp < 0 || this.vm.decodeSqueakSP(newStackp) >= ctxt.pointers.length) return false; var stackp = ctxt.pointers[Squeak.Context_stackPointer]; while (stackp < newStackp) ctxt.pointers[this.vm.decodeSqueakSP(++stackp)] = this.vm.nilObj; ctxt.pointers[Squeak.Context_stackPointer] = newStackp; this.vm.popN(argCount); return true; }, primitiveChangeClass: function(argCount) { if (argCount > 2) return false; var rcvr = this.stackNonInteger(1), arg = this.stackNonInteger(0); if (!this.changeClassTo(rcvr, arg.sqClass)) { return false; } return this.popNIfOK(argCount); }, primitiveAdoptInstance: function(argCount) { if (argCount > 2) return false; var cls = this.stackNonInteger(1), obj = this.stackNonInteger(0); if (!this.changeClassTo(obj, cls)) { return false; } return this.popNIfOK(argCount); }, changeClassTo: function(rcvr, cls) { if (rcvr.sqClass.isCompact !== cls.isCompact) return false; var classInstIsPointers = cls.classInstIsPointers(); if (rcvr.isPointers()) { if (!classInstIsPointers) return false; if (rcvr.sqClass.classInstSize() !== cls.classInstSize()) return false; } else { if (classInstIsPointers) return false; var hasBytes = rcvr.isBytes(), needBytes = cls.classInstIsBytes(); if (hasBytes && !needBytes) { if (rcvr.bytes) { if (rcvr.bytes.length & 3) return false; rcvr.words = new Uint32Array(rcvr.bytes.buffer); delete rcvr.bytes; } } else if (!hasBytes && needBytes) { if (rcvr.words) { rcvr.bytes = new Uint8Array(rcvr.words.buffer); delete rcvr.words; } } } rcvr._format = cls.classInstFormat(); rcvr.sqClass = cls; return true; }, primitiveDoPrimitiveWithArgs: function(argCount) { var argumentArray = this.stackNonInteger(0), primIdx = this.stackInteger(1); if (!this.success) return false; var arraySize = argumentArray.pointersSize(), cntxSize = this.vm.activeContext.pointersSize(); if (this.vm.sp + arraySize >= cntxSize) return false; // Pop primIndex and argArray, then push args in place... this.vm.popN(2); for (var i = 0; i < arraySize; i++) this.vm.push(argumentArray.pointers[i]); // Run the primitive if (this.vm.tryPrimitive(primIdx, arraySize)) return true; // Primitive failed, restore state for failure code this.vm.popN(arraySize); this.vm.push(primIdx); this.vm.push(argumentArray); return false; }, primitiveDoNamedPrimitive: function(argCount) { var argumentArray = this.stackNonInteger(0), rcvr = this.stackNonInteger(1), primMethod = this.stackNonInteger(2); if (!this.success) return false; var arraySize = argumentArray.pointersSize(), cntxSize = this.vm.activeContext.pointersSize(); if (this.vm.sp + arraySize >= cntxSize) return false; // Pop primIndex, rcvr, and argArray, then push new receiver and args in place... this.vm.popN(3); this.vm.push(rcvr); for (var i = 0; i < arraySize; i++) this.vm.push(argumentArray.pointers[i]); // Run the primitive if (this.doNamedPrimitive(arraySize, primMethod)) return true; // Primitive failed, restore state for failure code this.vm.popN(arraySize + 1); this.vm.push(primMethod); this.vm.push(rcvr); this.vm.push(argumentArray); return false; }, primitiveShortAtAndPut: function(argCount) { var rcvr = this.stackNonInteger(argCount), index = this.stackInteger(argCount-1) - 1, // make zero-based array = rcvr.wordsAsInt16Array(); if (!this.success || !array || index < 0 || index >= array.length) return false; var value; if (argCount < 2) { // shortAt: value = array[index]; } else { // shortAt:put: value = this.stackInteger(0); if (value < -32768 || value > 32767) return false; array[index] = value; } this.popNandPushIfOK(argCount+1, value); return true; }, primitiveIntegerAtAndPut: function(argCount) { var rcvr = this.stackNonInteger(argCount), index = this.stackInteger(argCount-1) - 1, // make zero-based array = rcvr.wordsAsInt32Array(); if (!this.success || !array || index < 0 || index >= array.length) return false; var value; if (argCount < 2) { // integerAt: value = this.signed32BitIntegerFor(array[index]); } else { // integerAt:put: value = this.stackSigned32BitInt(0); if (!this.success) return false; array[index] = value; } this.popNandPushIfOK(argCount+1, value); return true; }, primitiveConstantFill: function(argCount) { var rcvr = this.stackNonInteger(1), value = this.stackPos32BitInt(0); if (!this.success || !rcvr.isWordsOrBytes()) return false; var array = rcvr.words || rcvr.bytes; if (array) { if (array === rcvr.bytes && value > 255) return false; for (var i = 0; i < array.length; i++) array[i] = value; } this.vm.popN(argCount); return true; }, primitiveNewMethod: function(argCount) { var header = this.stackInteger(0); var bytecodeCount = this.stackInteger(1); if (!this.success) return 0; var method = this.vm.instantiateClass(this.vm.stackValue(2), bytecodeCount); method.pointers = [header]; var litCount = method.methodNumLits(); for (var i = 0; i < litCount; i++) method.pointers.push(this.vm.nilObj); this.vm.popNandPush(1+argCount, method); if (this.vm.breakOnNewMethod) // break on doit this.vm.breakOnMethod = method; return true; }, primitiveExecuteMethodArgsArray: function(argCount) { // receiver, argsArray, then method are on top of stack. Execute method with // receiver and args. var methodObj = this.stackNonInteger(0), argsArray = this.stackNonInteger(1), receiver = this.vm.stackValue(2); // Allow for up to two extra arguments (e.g. for mirror primitives). if (!this.success || !methodObj.isMethod() || argCount > 4) return false; var numArgs = methodObj.methodNumArgs(); if (numArgs !== argsArray.pointersSize()) return false; // drop all args, push receiver, and new arguments this.vm.popNandPush(argCount+1, receiver); for (var i = 0; i < numArgs; i++) this.vm.push(argsArray.pointers[i]); this.vm.executeNewMethod(receiver, methodObj, numArgs, methodObj.methodPrimitiveIndex(), null, null); return true; }, primitiveArrayBecome: function(argCount, doBothWays, copyHash) { var rcvr = this.stackNonInteger(argCount), arg = this.stackNonInteger(argCount-1); if (argCount > 1) copyHash = this.stackBoolean(argCount-2); if (!this.success) return false; this.success = this.vm.image.bulkBecome(rcvr.pointers, arg.pointers, doBothWays, copyHash); return this.popNIfOK(argCount); }, doStringReplace: function() { var dst = this.stackNonInteger(4); var dstPos = this.stackInteger(3) - 1; var count = this.stackInteger(2) - dstPos; var src = this.stackNonInteger(1); var srcPos = this.stackInteger(0) - 1; if (!this.success) return dst; //some integer not right if (!src.sameFormatAs(dst)) {this.success = false; return dst;} //incompatible formats if (src.isPointers()) {//pointer type objects var totalLength = src.pointersSize(); var srcInstSize = src.instSize(); srcPos += srcInstSize; if ((srcPos < 0) || (srcPos + count) > totalLength) {this.success = false; return dst;} //would go out of bounds totalLength = dst.pointersSize(); var dstInstSize= dst.instSize(); dstPos += dstInstSize; if ((dstPos < 0) || (dstPos + count) > totalLength) {this.success= false; return dst;} //would go out of bounds for (var i = 0; i < count; i++) dst.pointers[dstPos + i] = src.pointers[srcPos + i]; return dst; } else if (src.isWords()) { //words type objects var totalLength = src.wordsSize(); if ((srcPos < 0) || (srcPos + count) > totalLength) {this.success = false; return dst;} //would go out of bounds totalLength = dst.wordsSize(); if ((dstPos < 0) || (dstPos + count) > totalLength) {this.success = false; return dst;} //would go out of bounds if (src.isFloat && dst.isFloat) dst.float = src.float; else if (src.isFloat) dst.wordsAsFloat64Array()[dstPos] = src.float; else if (dst.isFloat) dst.float = src.wordsAsFloat64Array()[srcPos]; else for (var i = 0; i < count; i++) dst.words[dstPos + i] = src.words[srcPos + i]; return dst; } else { //bytes type objects var totalLength = src.bytesSize(); if ((srcPos < 0) || (srcPos + count) > totalLength) {this.success = false; return dst;} //would go out of bounds totalLength = dst.bytesSize(); if ((dstPos < 0) || (dstPos + count) > totalLength) {this.success = false; return dst;} //would go out of bounds for (var i = 0; i < count; i++) dst.bytes[dstPos + i] = src.bytes[srcPos + i]; return dst; } }, primitiveCopyObject: function(argCount) { var rcvr = this.stackNonInteger(1), arg = this.stackNonInteger(0), length = rcvr.pointersSize(); if (!this.success || rcvr.isWordsOrBytes() || rcvr.sqClass !== arg.sqClass || length !== arg.pointersSize()) return false; for (var i = 0; i < length; i++) rcvr.pointers[i] = arg.pointers[i]; rcvr.dirty = arg.dirty; this.vm.popN(argCount); return true; }, primitiveStoreImageSegment: function(argCount) { var arrayOfRoots = this.stackNonInteger(2), segmentWordArray = this.stackNonInteger(1), outPointerArray = this.stackNonInteger(0); if (!arrayOfRoots.pointers || !segmentWordArray.words || !outPointerArray.pointers) return false; var success = this.vm.image.storeImageSegment(segmentWordArray, outPointerArray, arrayOfRoots); if (!success) return false; this.vm.popN(argCount); // return self return true; }, primitiveLoadImageSegment: function(argCount) { var segmentWordArray = this.stackNonInteger(1), outPointerArray = this.stackNonInteger(0); if (!segmentWordArray.words || !outPointerArray.pointers) return false; var roots = this.vm.image.loadImageSegment(segmentWordArray, outPointerArray); if (!roots) return false; return this.popNandPushIfOK(argCount + 1, roots); }, }, 'blocks/closures', { doBlockCopy: function() { var rcvr = this.vm.stackValue(1); var sqArgCount = this.stackInteger(0); var homeCtxt = rcvr; if (!this.vm.isContext(homeCtxt)) this.success = false; if (!this.success) return rcvr; if (typeof homeCtxt.pointers[Squeak.Context_method] === "number") // ctxt is itself a block; get the context for its enclosing method homeCtxt = homeCtxt.pointers[Squeak.BlockContext_home]; var blockSize = homeCtxt.pointersSize() - homeCtxt.instSize(); // could use a const for instSize var newBlock = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassBlockContext], blockSize); var initialPC = this.vm.encodeSqueakPC(this.vm.pc + 2, this.vm.method); //*** check this... newBlock.pointers[Squeak.BlockContext_initialIP] = initialPC; newBlock.pointers[Squeak.Context_instructionPointer] = initialPC; // claim not needed; value will set it newBlock.pointers[Squeak.Context_stackPointer] = 0; newBlock.pointers[Squeak.BlockContext_argumentCount] = sqArgCount; newBlock.pointers[Squeak.BlockContext_home] = homeCtxt; newBlock.pointers[Squeak.Context_sender] = this.vm.nilObj; // claim not needed; just initialized return newBlock; }, primitiveBlockValue: function(argCount) { var rcvr = this.vm.stackValue(argCount); if (!this.isA(rcvr, Squeak.splOb_ClassBlockContext)) return false; var block = rcvr; var blockArgCount = block.pointers[Squeak.BlockContext_argumentCount]; if (typeof blockArgCount !== "number") return false; if (blockArgCount != argCount) return false; if (!block.pointers[Squeak.BlockContext_caller].isNil) return false; this.vm.arrayCopy(this.vm.activeContext.pointers, this.vm.sp-argCount+1, block.pointers, Squeak.Context_tempFrameStart, argCount); var initialIP = block.pointers[Squeak.BlockContext_initialIP]; block.pointers[Squeak.Context_instructionPointer] = initialIP; block.pointers[Squeak.Context_stackPointer] = argCount; block.pointers[Squeak.BlockContext_caller] = this.vm.activeContext; this.vm.popN(argCount+1); this.vm.newActiveContext(block); if (this.vm.interruptCheckCounter-- <= 0) this.vm.checkForInterrupts(); return true; }, primitiveBlockValueWithArgs: function(argCount) { var block = this.vm.stackValue(1); var array = this.vm.stackValue(0); if (!this.isA(block, Squeak.splOb_ClassBlockContext)) return false; if (!this.isA(array, Squeak.splOb_ClassArray)) return false; var blockArgCount = block.pointers[Squeak.BlockContext_argumentCount]; if (typeof blockArgCount !== "number") return false; if (blockArgCount != array.pointersSize()) return false; if (!block.pointers[Squeak.BlockContext_caller].isNil) return false; this.vm.arrayCopy(array.pointers, 0, block.pointers, Squeak.Context_tempFrameStart, blockArgCount); var initialIP = block.pointers[Squeak.BlockContext_initialIP]; block.pointers[Squeak.Context_instructionPointer] = initialIP; block.pointers[Squeak.Context_stackPointer] = blockArgCount; block.pointers[Squeak.BlockContext_caller] = this.vm.activeContext; this.vm.popN(argCount+1); this.vm.newActiveContext(block); if (this.vm.interruptCheckCounter-- <= 0) this.vm.checkForInterrupts(); return true; }, primitiveClosureCopyWithCopiedValues: function(argCount) { this.vm.breakNow("primitiveClosureCopyWithCopiedValues"); debugger; return false; }, primitiveClosureValue: function(argCount) { var blockClosure = this.vm.stackValue(argCount), blockArgCount = blockClosure.pointers[Squeak.Closure_numArgs]; if (argCount !== blockArgCount) return false; this.activateNewClosureMethod(blockClosure, argCount); if (this.vm.interruptCheckCounter-- <= 0) this.vm.checkForInterrupts(); return true; }, primitiveClosureValueWithArgs: function(argCount) { var array = this.vm.top(), arraySize = array.pointersSize(), blockClosure = this.vm.stackValue(argCount), blockArgCount = blockClosure.pointers[Squeak.Closure_numArgs]; if (arraySize !== blockArgCount) return false; this.vm.pop(); for (var i = 0; i < arraySize; i++) this.vm.push(array.pointers[i]); this.activateNewClosureMethod(blockClosure, arraySize); if (this.vm.interruptCheckCounter-- <= 0) this.vm.checkForInterrupts(); return true; }, primitiveClosureValueNoContextSwitch: function(argCount) { // An exact clone of primitiveClosureValue except that this version will not check for interrupts var blockClosure = this.vm.stackValue(argCount), blockArgCount = blockClosure.pointers[Squeak.Closure_numArgs]; if (argCount !== blockArgCount) return false; this.activateNewClosureMethod(blockClosure, argCount); return true; }, primitiveFullClosureValue: function(argCount) { var blockClosure = this.vm.stackValue(argCount), blockArgCount = blockClosure.pointers[Squeak.Closure_numArgs]; if (argCount !== blockArgCount) return false; this.activateNewFullClosure(blockClosure, argCount); if (this.vm.interruptCheckCounter-- <= 0) this.vm.checkForInterrupts(); return true; }, primitiveFullClosureValueWithArgs: function(argCount) { var array = this.vm.top(), arraySize = array.pointersSize(), blockClosure = this.vm.stackValue(argCount), blockArgCount = blockClosure.pointers[Squeak.Closure_numArgs]; if (arraySize !== blockArgCount) return false; this.vm.pop(); for (var i = 0; i < arraySize; i++) this.vm.push(array.pointers[i]); this.activateNewFullClosure(blockClosure, arraySize); if (this.vm.interruptCheckCounter-- <= 0) this.vm.checkForInterrupts(); return true; }, primitiveFullClosureValueNoContextSwitch: function(argCount) { // An exact clone of primitiveFullClosureValue except that this version will not check for interrupts var blockClosure = this.vm.stackValue(argCount), blockArgCount = blockClosure.pointers[Squeak.Closure_numArgs]; if (argCount !== blockArgCount) return false; this.activateNewFullClosure(blockClosure, argCount); return true; }, activateNewClosureMethod: function(blockClosure, argCount) { var outerContext = blockClosure.pointers[Squeak.Closure_outerContext], method = outerContext.pointers[Squeak.Context_method], newContext = this.vm.allocateOrRecycleContext(method.methodNeedsLargeFrame()), numCopied = blockClosure.pointers.length - Squeak.Closure_firstCopiedValue; newContext.pointers[Squeak.Context_sender] = this.vm.activeContext; newContext.pointers[Squeak.Context_instructionPointer] = blockClosure.pointers[Squeak.Closure_startpc]; newContext.pointers[Squeak.Context_stackPointer] = argCount + numCopied; newContext.pointers[Squeak.Context_method] = outerContext.pointers[Squeak.Context_method]; newContext.pointers[Squeak.Context_closure] = blockClosure; newContext.pointers[Squeak.Context_receiver] = outerContext.pointers[Squeak.Context_receiver]; // Copy the arguments and copied values ... var where = Squeak.Context_tempFrameStart; for (var i = 0; i < argCount; i++) newContext.pointers[where++] = this.vm.stackValue(argCount - i - 1); for (var i = 0; i < numCopied; i++) newContext.pointers[where++] = blockClosure.pointers[Squeak.Closure_firstCopiedValue + i]; // The initial instructions in the block nil-out remaining temps. this.vm.popN(argCount + 1); this.vm.newActiveContext(newContext); }, activateNewFullClosure: function(blockClosure, argCount) { var closureMethod = blockClosure.pointers[Squeak.ClosureFull_method], newContext = this.vm.allocateOrRecycleContext(closureMethod.methodNeedsLargeFrame()), numCopied = blockClosure.pointers.length - Squeak.ClosureFull_firstCopiedValue; newContext.pointers[Squeak.Context_sender] = this.vm.activeContext; newContext.pointers[Squeak.Context_instructionPointer] = this.vm.encodeSqueakPC(0, closureMethod); newContext.pointers[Squeak.Context_stackPointer] = closureMethod.methodTempCount(); // argCount + numCopied + numActualTemps newContext.pointers[Squeak.Context_method] = closureMethod; newContext.pointers[Squeak.Context_closure] = blockClosure; newContext.pointers[Squeak.Context_receiver] = blockClosure.pointers[Squeak.ClosureFull_receiver]; // Copy the arguments and copied values ... var where = Squeak.Context_tempFrameStart; for (var i = 0; i < argCount; i++) newContext.pointers[where++] = this.vm.stackValue(argCount - i - 1); for (var i = 0; i < numCopied; i++) newContext.pointers[where++] = blockClosure.pointers[Squeak.ClosureFull_firstCopiedValue + i]; // No need to nil-out remaining temps as context pointers are nil-initialized. this.vm.popN(argCount + 1); this.vm.newActiveContext(newContext); if (!closureMethod.compiled) this.vm.compileIfPossible(closureMethod); }, }, 'scheduling', { primitiveResume: function() { this.resume(this.vm.top()); return true; }, primitiveSuspend: function() { var process = this.vm.top(); if (process === this.activeProcess()) { this.vm.popNandPush(1, this.vm.nilObj); this.transferTo(this.wakeHighestPriority()); } else { var oldList = process.pointers[Squeak.Proc_myList]; if (oldList.isNil) return false; this.removeProcessFromList(process, oldList); if (!this.success) return false; process.pointers[Squeak.Proc_myList] = this.vm.nilObj; this.vm.popNandPush(1, oldList); } return true; }, getScheduler: function() { var assn = this.vm.specialObjects[Squeak.splOb_SchedulerAssociation]; return assn.pointers[Squeak.Assn_value]; }, activeProcess: function() { return this.getScheduler().pointers[Squeak.ProcSched_activeProcess]; }, resume: function(newProc) { var activeProc = this.activeProcess(); var activePriority = activeProc.pointers[Squeak.Proc_priority]; var newPriority = newProc.pointers[Squeak.Proc_priority]; if (newPriority > activePriority) { this.putToSleep(activeProc); this.transferTo(newProc); } else { this.putToSleep(newProc); } }, putToSleep: function(aProcess) { //Save the given process on the scheduler process list for its priority. var priority = aProcess.pointers[Squeak.Proc_priority]; var processLists = this.getScheduler().pointers[Squeak.ProcSched_processLists]; var processList = processLists.pointers[priority - 1]; this.linkProcessToList(aProcess, processList); }, transferTo: function(newProc) { //Record a process to be awakened on the next interpreter cycle. var sched = this.getScheduler(); var oldProc = sched.pointers[Squeak.ProcSched_activeProcess]; sched.pointers[Squeak.ProcSched_activeProcess] = newProc; sched.dirty = true; oldProc.pointers[Squeak.Proc_suspendedContext] = this.vm.activeContext; oldProc.dirty = true; this.vm.newActiveContext(newProc.pointers[Squeak.Proc_suspendedContext]); newProc.pointers[Squeak.Proc_suspendedContext] = this.vm.nilObj; if (!this.oldPrims) newProc.pointers[Squeak.Proc_myList] = this.vm.nilObj; this.vm.reclaimableContextCount = 0; if (this.vm.breakOnContextChanged) { this.vm.breakOnContextChanged = false; this.vm.breakNow(); } if (this.vm.logProcess) console.log( "\n============= Process Switch ==================\n" + this.vm.printProcess(newProc, true, this.vm.logSends ? '| ' : '') + "==============================================="); }, wakeHighestPriority: function() { //Return the highest priority process that is ready to run. //Note: It is a fatal VM error if there is no runnable process. var schedLists = this.getScheduler().pointers[Squeak.ProcSched_processLists]; var p = schedLists.pointersSize() - 1; // index of last indexable field var processList; do { if (p < 0) throw Error("scheduler could not find a runnable process"); processList = schedLists.pointers[p--]; } while (this.isEmptyList(processList)); return this.removeFirstLinkOfList(processList); }, linkProcessToList: function(proc, aList) { // Add the given process to the given linked list and set the backpointer // of process to its new list. if (this.isEmptyList(aList)) { aList.pointers[Squeak.LinkedList_firstLink] = proc; } else { var lastLink = aList.pointers[Squeak.LinkedList_lastLink]; lastLink.pointers[Squeak.Link_nextLink] = proc; lastLink.dirty = true; } aList.pointers[Squeak.LinkedList_lastLink] = proc; aList.dirty = true; proc.pointers[Squeak.Proc_myList] = aList; proc.dirty = true; }, isEmptyList: function(aLinkedList) { return aLinkedList.pointers[Squeak.LinkedList_firstLink].isNil; }, removeFirstLinkOfList: function(aList) { //Remove the first process from the given linked list. var first = aList.pointers[Squeak.LinkedList_firstLink]; var last = aList.pointers[Squeak.LinkedList_lastLink]; if (first === last) { aList.pointers[Squeak.LinkedList_firstLink] = this.vm.nilObj; aList.pointers[Squeak.LinkedList_lastLink] = this.vm.nilObj; } else { var next = first.pointers[Squeak.Link_nextLink]; aList.pointers[Squeak.LinkedList_firstLink] = next; aList.dirty = true; } first.pointers[Squeak.Link_nextLink] = this.vm.nilObj; return first; }, removeProcessFromList: function(process, list) { var first = list.pointers[Squeak.LinkedList_firstLink]; var last = list.pointers[Squeak.LinkedList_lastLink]; if (process === first) { var next = process.pointers[Squeak.Link_nextLink]; list.pointers[Squeak.LinkedList_firstLink] = next; if (process === last) { list.pointers[Squeak.LinkedList_lastLink] = this.vm.nilObj; } } else { var temp = first; while (true) { if (temp.isNil) { if (this.oldPrims) this.success = false; return; } next = temp.pointers[Squeak.Link_nextLink]; if (next === process) break; temp = next; } next = process.pointers[Squeak.Link_nextLink]; temp.pointers[Squeak.Link_nextLink] = next; if (process === last) { list.pointers[Squeak.LinkedList_lastLink] = temp; } } process.pointers[Squeak.Link_nextLink] = this.vm.nilObj; }, registerSemaphore: function(specialObjIndex) { var sema = this.vm.top(); if (this.isA(sema, Squeak.splOb_ClassSemaphore)) this.vm.specialObjects[specialObjIndex] = sema; else this.vm.specialObjects[specialObjIndex] = this.vm.nilObj; return this.vm.stackValue(1); }, primitiveWait: function() { var sema = this.vm.top(); if (!this.isA(sema, Squeak.splOb_ClassSemaphore)) return false; var excessSignals = sema.pointers[Squeak.Semaphore_excessSignals]; if (excessSignals > 0) sema.pointers[Squeak.Semaphore_excessSignals] = excessSignals - 1; else { this.linkProcessToList(this.activeProcess(), sema); this.transferTo(this.wakeHighestPriority()); } return true; }, primitiveSignal: function() { var sema = this.vm.top(); if (!this.isA(sema, Squeak.splOb_ClassSemaphore)) return false; this.synchronousSignal(sema); return true; }, synchronousSignal: function(sema) { if (this.isEmptyList(sema)) { // no process is waiting on this semaphore sema.pointers[Squeak.Semaphore_excessSignals]++; } else this.resume(this.removeFirstLinkOfList(sema)); return; }, signalAtMilliseconds: function(sema, msTime) { if (this.isA(sema, Squeak.splOb_ClassSemaphore)) { this.vm.specialObjects[Squeak.splOb_TheTimerSemaphore] = sema; this.vm.nextWakeupTick = msTime; } else { this.vm.specialObjects[Squeak.splOb_TheTimerSemaphore] = this.vm.nilObj; this.vm.nextWakeupTick = 0; } }, primitiveSignalAtMilliseconds: function(argCount) { var msTime = this.stackInteger(0); var sema = this.stackNonInteger(1); if (!this.success) return false; this.signalAtMilliseconds(sema, msTime); this.vm.popN(argCount); // return self return true; }, primitiveSignalAtUTCMicroseconds: function(argCount) { var usecsUTC = this.stackSigned53BitInt(0); var sema = this.stackNonInteger(1); if (!this.success) return false; var msTime = (usecsUTC / 1000 + Squeak.EpochUTC - this.vm.startupTime) & Squeak.MillisecondClockMask; this.signalAtMilliseconds(sema, msTime); this.vm.popN(argCount); // return self return true; }, signalSemaphoreWithIndex: function(semaIndex) { // asynch signal: will actually be signaled in checkForInterrupts() this.semaphoresToSignal.push(semaIndex); }, signalExternalSemaphores: function() { var semaphores = this.vm.specialObjects[Squeak.splOb_ExternalObjectsArray].pointers, semaClass = this.vm.specialObjects[Squeak.splOb_ClassSemaphore]; while (this.semaphoresToSignal.length) { var semaIndex = this.semaphoresToSignal.shift(), sema = semaphores[semaIndex - 1]; if (sema.sqClass == semaClass) this.synchronousSignal(sema); } }, primitiveEnterCriticalSection: function(argCount) { if (argCount > 1) return false; var mutex = this.vm.stackValue(argCount); var activeProc = argCount ? this.vm.top() : this.activeProcess(); var owningProcess = mutex.pointers[Squeak.Mutex_owner]; if (owningProcess.isNil) { mutex.pointers[Squeak.Mutex_owner] = activeProc; mutex.dirty = true; this.popNandPushIfOK(argCount + 1, this.vm.falseObj); } else if (owningProcess === activeProc) { this.popNandPushIfOK(argCount + 1, this.vm.trueObj); } else { this.popNandPushIfOK(argCount + 1, this.vm.falseObj); this.linkProcessToList(activeProc, mutex); this.transferTo(this.wakeHighestPriority()); } return true; }, primitiveExitCriticalSection: function(argCount) { var criticalSection = this.vm.top(); if (this.isEmptyList(criticalSection)) { criticalSection.pointers[Squeak.Mutex_owner] = this.vm.nilObj; } else { var owningProcess = this.removeFirstLinkOfList(criticalSection); criticalSection.pointers[Squeak.Mutex_owner] = owningProcess; criticalSection.dirty = true; this.resume(owningProcess); } return true; }, primitiveTestAndSetOwnershipOfCriticalSection: function(argCount) { if (argCount > 1) return false; var mutex = this.vm.stackValue(argCount); var activeProc = argCount ? this.vm.top() : this.activeProcess(); var owningProcess = mutex.pointers[Squeak.Mutex_owner]; if (owningProcess.isNil) { mutex.pointers[Squeak.Mutex_owner] = activeProc; mutex.dirty = true; this.popNandPushIfOK(argCount + 1, this.vm.falseObj); } else if (owningProcess === activeProc) { this.popNandPushIfOK(argCount + 1, this.vm.trueObj); } else { this.popNandPushIfOK(argCount + 1, this.vm.nilObj); } return true; }, }, 'vm functions', { primitiveGetAttribute: function(argCount) { var attr = this.stackInteger(0); if (!this.success) return false; var argv = this.display.argv, vmOptions = this.display.vmOptions, value = null; switch (attr) { case 0: value = (argv && argv[0]) || this.filenameToSqueak(Squeak.vmPath + Squeak.vmFile); break; case 1: value = (argv && argv[1]) || this.display.documentName; break; // 1.x images want document here case 2: value = (argv && argv[2]) || this.display.documentName; break; // later images want document here case 1001: value = this.vm.options.unix ? "unix" : Squeak.platformName; break; case 1002: value = Squeak.osVersion; break; case 1003: value = Squeak.platformSubtype; break; case 1004: value = Squeak.vmVersion + ' ' + Squeak.vmMakerVersion; break; case 1005: value = Squeak.windowSystem; break; case 1006: value = Squeak.vmBuild; break; case 1007: value = Squeak.vmInterpreterVersion; break; // Interpreter class // case 1008: Cogit class case 1009: value = Squeak.vmVersion + ' Date: ' + Squeak.vmDate; break; // Platform source version default: if (attr >= 0 && argv && argv.length > attr) { value = argv[attr]; } else if (attr < 0 && vmOptions && vmOptions.length > -attr - 1) { value = vmOptions[-attr - 1]; } else { return false; } } this.vm.popNandPush(argCount+1, this.makeStObject(value)); return true; }, setLowSpaceThreshold: function() { var nBytes = this.stackInteger(0); if (this.success) this.vm.lowSpaceThreshold = nBytes; return this.vm.stackValue(1); }, primitiveVMParameter: function(argCount) { /* Behaviour depends on argument count: 0 args: return an Array of VM parameter values; 1 arg: return the indicated VM parameter; 2 args: set the VM indicated parameter. */ var paramsArraySize = this.vm.image.isSpur ? 71 : 44; switch (argCount) { case 0: var arrayObj = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassArray], paramsArraySize); for (var i = 0; i < paramsArraySize; i++) arrayObj.pointers[i] = this.makeStObject(this.vmParameterAt(i+1)); return this.popNandPushIfOK(1, arrayObj); case 1: var parm = this.stackInteger(0); if (parm < 1 || parm > paramsArraySize) return false; return this.popNandPushIfOK(2, this.makeStObject(this.vmParameterAt(parm))); case 2: // ignore writes return this.popNandPushIfOK(3, 0); } return false; }, vmParameterAt: function(index) { switch (index) { case 1: return this.vm.image.oldSpaceBytes; // end of old-space (0-based, read-only) case 2: return this.vm.image.oldSpaceBytes; // end of young-space (read-only) case 3: return this.vm.image.totalMemory; // end of memory (read-only) case 4: return this.vm.image.allocationCount + this.vm.image.newSpaceCount; // allocationCount (read-only; nil in Cog VMs) // 5 allocations between GCs (read-write; nil in Cog VMs) // 6 survivor count tenuring threshold (read-write) case 7: return this.vm.image.gcCount; // full GCs since startup (read-only) case 8: return this.vm.image.gcMilliseconds; // total milliseconds in full GCs since startup (read-only) case 9: return this.vm.image.pgcCount; // incremental GCs since startup (read-only) case 10: return this.vm.image.pgcMilliseconds; // total milliseconds in incremental GCs since startup (read-only) case 11: return this.vm.image.gcTenured; // tenures of surving objects since startup (read-only) // 12-20 specific to the translating VM case 15: case 16: // idle microseconds case 17: case 18: case 19: case 20: return 0; // utc microseconds at VM start-up // 21 root table size (read-only) case 22: return 0; // root table overflows since startup (read-only) case 23: return this.vm.image.extraVMMemory; // bytes of extra memory to reserve for VM buffers, plugins, etc. // 24 memory threshold above which to shrink object memory (read-write) // 25 memory headroom when growing object memory (read-write) // 26 interruptChecksEveryNms - force an ioProcessEvents every N milliseconds (read-write) // 27 number of times mark loop iterated for current IGC/FGC (read-only) includes ALL marking // 28 number of times sweep loop iterated for current IGC/FGC (read-only) // 29 number of times make forward loop iterated for current IGC/FGC (read-only) // 30 number of times compact move loop iterated for current IGC/FGC (read-only) // 31 number of grow memory requests (read-only) // 32 number of shrink memory requests (read-only) // 33 number of root table entries used for current IGC/FGC (read-only) // 34 number of allocations done before current IGC/FGC (read-only) // 35 number of survivor objects after current IGC/FGC (read-only) // 36 millisecond clock when current IGC/FGC completed (read-only) // 37 number of marked objects for Roots of the world, not including Root Table entries for current IGC/FGC (read-only) // 38 milliseconds taken by current IGC (read-only) // 39 Number of finalization signals for Weak Objects pending when current IGC/FGC completed (read-only) case 40: return 4; // BytesPerWord for this image case 41: return this.vm.image.formatVersion(); //42 number of stack pages in use (Cog Stack VM only, otherwise nil) //43 desired number of stack pages (stored in image file header, max 65535; Cog VMs only, otherwise nil) case 44: return 0; // size of eden, in bytes // 45 desired size of eden, in bytes (stored in image file header; Cog VMs only, otherwise nil) // 46 size of machine code zone, in bytes (stored in image file header; Cog JIT VM only, otherwise nil) case 46: return 0; // 47 desired size of machine code zone, in bytes (applies at startup only, stored in image file header; Cog JIT VM only) case 48: return 0; // not yet using/modifying this.vm.image.headerFlags // 48 various properties stored in the image header (that instruct the VM) as an integer encoding an array of bit flags. // Bit 0: in a threaded VM, if set, tells the VM that the image's Process class has threadAffinity as its 5th inst var // (after nextLink, suspendedContext, priority & myList) // Bit 1: in Cog JIT VMs, if set, asks the VM to set the flag bit in interpreted methods // Bit 2: if set, preempting a process puts it to the head of its run queue, not the back, // i.e. preempting a process by a higher priority one will not cause the preempted process to yield // to others at the same priority. // Bit 3: in a muilt-threaded VM, if set, the Window system will only be accessed from the first VM thread (now unassigned) // Bit 4: in a Spur VM, if set, causes weaklings and ephemerons to be queued individually for finalization // Bit 5: if set, implies wheel events will be delivered as such and not mapped to arrow key events // Bit 6: if set, implies arithmetic primitives will fail if given arguments of different types (float vs int) // Bit 7: if set, causes times delivered from file primitives to be in UTC rather than local time // Bit 8: if set, implies the VM will not upscale the display on high DPI monitors; older VMs did this by default. // 49 the size of the external semaphore table (read-write; Cog VMs only) // 50-51 reserved for VM parameters that persist in the image (such as eden above) // 52 root (remembered) table maximum size (read-only) // 53 the number of oldSpace segments (Spur only, otherwise nil) case 54: return this.vm.image.bytesLeft(); // total size of free old space (Spur only, otherwise nil) // 55 ratio of growth and image size at or above which a GC will be performed post scavenge (Spur only, otherwise nil) // 56 number of process switches since startup (read-only) // 57 number of ioProcessEvents calls since startup (read-only) // 58 number of forceInterruptCheck (Cog VMs) or quickCheckInterruptCalls (non-Cog VMs) calls since startup (read-only) // 59 number of check event calls since startup (read-only) // 60 number of stack page overflows since startup (read-only; Cog VMs only) // 61 number of stack page divorces since startup (read-only; Cog VMs only) // 62 number of machine code zone compactions since startup (read-only; Cog VMs only) // 63 milliseconds taken by machine code zone compactions since startup (read-only; Cog VMs only) // 64 current number of machine code methods (read-only; Cog VMs only) // 65 In newer Cog VMs a set of flags describing VM features, // if non-zero bit 0 implies multiple bytecode set support; // if non-zero bit 1 implies read-only object support; // if non-zero bit 2 implies the VM suffers from using an ITIMER heartbeat (if 0 it has a thread that provides the heartbeat) // if non-zero bit 3 implies the VM supports cross-platform BIT_IDENTICAL_FLOATING_POINT arithmetic // if non-zero bit 4 implies the VM can catch exceptions in FFI calls and answer them as primitive failures // if non-zero bit 5 implies the VM's suspend primitive backs up the process to before the wait if it was waiting on a condition variable // (read-only; Cog VMs only; nil in older Cog VMs, a boolean answering multiple bytecode support in not so old Cog VMs) case 65: return 0; // 66 the byte size of a stack page in the stack zone (read-only; Cog VMs only) // 67 the maximum allowed size of old space in bytes, 0 implies no internal limit (Spur VMs only). case 67: return this.vm.image.totalMemory; // 68 - 69 reserved for more Cog-related info // 70 the value of VM_PROXY_MAJOR (the interpreterProxy major version number) // 71 the value of VM_PROXY_MINOR (the interpreterProxy minor version number) // 72 total milliseconds in full GCs Mark phase since startup (read-only) // 73 total milliseconds in full GCs Sweep phase since startup (read-only, can be 0 depending on compactors) // 74 maximum pause time due to segment allocation // 75 whether arithmetic primitives will do mixed type arithmetic; if false they fail for different receiver and argument types // 76 the minimum unused headroom in all stack pages; Cog VMs only } return null; }, primitiveImageName: function(argCount) { if (argCount == 0) return this.popNandPushIfOK(1, this.makeStString(this.filenameToSqueak(this.vm.image.name))); this.vm.image.name = this.filenameFromSqueak(this.vm.top().bytesAsString()); Squeak.Settings['squeakImageName'] = this.vm.image.name; this.vm.popN(argCount); return true; }, primitiveSnapshot: function(argCount) { this.vm.popNandPush(1, this.vm.trueObj); // put true on stack for saved snapshot this.vm.storeContextRegisters(); // store current state for snapshot this.activeProcess().pointers[Squeak.Proc_suspendedContext] = this.vm.activeContext; // store initial context this.vm.image.fullGC("snapshot"); // before cleanup so traversal works var buffer = this.vm.image.writeToBuffer(); // Write snapshot if files are supported if (Squeak.flushAllFiles) { Squeak.flushAllFiles(); // so there are no more writes pending Squeak.filePut(this.vm.image.name, buffer); } this.vm.popNandPush(1, this.vm.falseObj); // put false on stack for continuing return true; }, primitiveQuit: function(argCount) { // Flush any files if files are supported if (Squeak.flushAllFiles) Squeak.flushAllFiles(); this.display.quitFlag = true; this.vm.breakNow("quit"); return true; }, primitiveExitToDebugger: function(argCount) { this.vm.breakNow("debugger primitive"); //console.error(this.vm.printStack(null)); debugger; return true; }, primitiveSetGCBiasToGrow: function(argCount) { return this.fakePrimitive(".primitiveSetGCBiasToGrow", 0, argCount); }, primitiveSetGCBiasToGrowGCLimit: function(argCount) { return this.fakePrimitive(".primitiveSetGCBiasToGrowGCLimit", 0, argCount); }, }, 'time', { primitiveRelinquishProcessorForMicroseconds: function(argCount) { // we ignore the optional arg this.vm.popN(argCount); this.vm.goIdle(); // might switch process, so must be after pop return true; }, millisecondClockValue: function() { //Return the value of the millisecond clock as an integer. //Note that the millisecond clock wraps around periodically. //The range is limited to SmallInteger maxVal / 2 to allow //delays of up to that length without overflowing a SmallInteger. return (Date.now() - this.vm.startupTime) & Squeak.MillisecondClockMask; }, millisecondClockValueSet: function(clock) { // set millisecondClock to the (previously saved) clock value // to allow "stopping" the VM clock while debugging this.vm.startupTime = Date.now() - clock; }, secondClock: function() { return this.pos32BitIntFor(Squeak.totalSeconds()); // will overflow 32 bits in 2037 }, microsecondClock: function(state) { var millis = Date.now() - state.epoch; if (typeof performance !== "object") return this.pos53BitIntFor(millis * 1000); // use high-res clock, adjust for roll-over var micros = performance.now() * 1000 % 1000 | 0, oldMillis = state.millis, oldMicros = state.micros; if (oldMillis > millis) millis = oldMillis; // rolled over previously if (millis === oldMillis && micros < oldMicros) millis++; // roll over now state.millis = millis; state.micros = micros; return this.pos53BitIntFor(millis * 1000 + micros); }, microsecondClockUTC: function() { if (!this.microsecondClockUTCState) this.microsecondClockUTCState = {epoch: Squeak.EpochUTC, millis: 0, micros: 0}; return this.microsecondClock(this.microsecondClockUTCState); }, microsecondClockLocal: function() { if (!this.microsecondClockLocalState) this.microsecondClockLocalState = {epoch: Squeak.Epoch, millis: 0, micros: 0}; return this.microsecondClock(this.microsecondClockLocalState); }, primitiveUtcWithOffset: function(argCount) { var d = new Date(); var posixMicroseconds = this.pos53BitIntFor(d.getTime() * 1000); var offset = -60 * d.getTimezoneOffset(); if (argCount > 0) { // either an Array or a DateAndTime in new UTC format with two ivars var stWordIndexableObject = this.vm.stackValue(0); stWordIndexableObject.pointers[0] = posixMicroseconds; stWordIndexableObject.pointers[1] = offset; this.popNandPushIfOK(argCount + 1, stWordIndexableObject); return true; } var timeAndOffset = [ posixMicroseconds, offset, ]; this.popNandPushIfOK(argCount + 1, this.makeStArray(timeAndOffset)); return true; }, }); /* * Copyright (c) 2014-2025 Vanessa Freudenberg * * 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. */ Object.subclass('Squeak.Compiler', /**************************************************************************** VM and Compiler =============== The VM has an interpreter, it will work fine (and much more memory-efficient) without loading a compiler. The compiler plugs into the VM by providing the Squeak.Compiler global. It can be easily replaced by just loading a different script providing Squeak.Compiler. The VM creates the compiler instance after an image has been loaded and the VM been initialized. Whenever a method is activated that was not compiled yet, the compiler gets a chance to compile it. The compiler may decide to wait for a couple of activations before actually compiling it. This might prevent do-its from ever getting compiled, because they are only activated once. Therefore, the compiler is also called when a long-running non-optimized loop calls checkForInterrupts. Finally, whenever the interpreter is about to execute a bytecode, it calls the compiled method instead (which typically will execute many bytecodes): initialize: compiler = new Squeak.Compiler(vm); executeNewMethod, checkForInterrupts: if (!method.compiled && compiler) compiler.compile(method); interpret: if (method.compiled) method.compiled(vm); Note that a compiler could hook itself into a compiled method by dispatching to vm.compiler in the generated code. This would allow gathering statistics, recompiling with optimized code etc. About This Compiler =================== The compiler in this file is meant to be simple, fast-compiling, and general. It transcribes bytecodes 1-to-1 into equivalent JavaScript code using templates (and thus can even support single-stepping). It uses the interpreter's stack pointer (SP) and program counter (PC), actual context objects just like the interpreter, no register mapping, it does not optimize sends, etc. Jumps are handled by wrapping the whole method in a loop and switch. This also enables continuing in the middle of a compiled method: whenever another context is activated, the method returns to the main loop, and is entered again later with a different PC. Here is an example method, its bytecodes, and a simplified version of the generated JavaScript code: method [value selector] whileFalse. ^ 42 0 <00> pushInstVar: 0 1 send: #selector 2 jumpIfTrue: 6 4 jumpTo: 0 6 <21> pushConst: 42 7 <7C> return: topOfStack context = vm.activeContext while (true) switch (vm.pc) { case 0: stack[++vm.sp] = inst[0]; vm.pc = 2; vm.send(#selector); // activate new method return; // return to main loop // Main loop will execute the activated method. When // that method returns, this method will be called // again with vm.pc == 2 and jump directly to case 2 case 2: if (stack[vm.sp--] === vm.trueObj) { vm.pc = 6; continue; // jump to case 6 } // otherwise fall through to next case case 4: vm.pc = 0; continue; // jump to case 0 case 6: stack[++vm.sp] = 42; vm.pc = 7; vm.doReturn(stack[vm.sp]); return; } Debugging support ================= This compiler supports generating single-stepping code and comments, which are rather helpful during debugging. Normally, only bytecodes that can be a jump target are given a label. Also, bytecodes following a send operation need a label, to enable returning to that spot after the context switch. All other bytecodes are executed continuously. When compiling for single-stepping, each bytecode gets a label, and after each bytecode a flag is checked and the method returns if needed. Because this is a performance penalty, methods are first compiled without single-step support, and recompiled for single-stepping on demand. This is optional, another compiler can answer false from enableSingleStepping(). In that case the VM will delete the compiled method and invoke the interpreter to single-step. *****************************************************************************/ 'initialization', { initialize: function(vm) { this.vm = vm; this.comments = !!Squeak.Compiler.comments, // generate comments // for debug-printing only this.specialSelectors = ['+', '-', '<', '>', '<=', '>=', '=', '~=', '*', '/', '\\\\', '@', 'bitShift:', '//', 'bitAnd:', 'bitOr:', 'at:', 'at:put:', 'size', 'next', 'nextPut:', 'atEnd', '==', 'class', 'blockCopy:', 'value', 'value:', 'do:', 'new', 'new:', 'x', 'y']; this.doitCounter = 0; this.blockCounter = 0; }, }, 'accessing', { compile: function(method, optClassObj, optSelObj) { if (method.compiled === undefined) { // 1st time method.compiled = false; } else { // 2nd time this.singleStep = false; this.debug = this.comments; var clsName, sel, instVars; if (this.debug && !optClassObj) { // this is expensive, so only do it when debugging var isMethod = method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod]; this.vm.allMethodsDo(function(classObj, methodObj, selectorObj) { if (isMethod ? methodObj === method : methodObj.pointers.includes(method)) { optClassObj = classObj; optSelObj = selectorObj; return true; } }); } if (optClassObj) { clsName = optClassObj.className(); sel = optSelObj.bytesAsString(); if (this.debug) { // only when debugging var isMethod = method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod]; if (!isMethod) { clsName = "[] in " + clsName; } instVars = optClassObj.allInstVarNames(); } } method.compiled = this.generate(method, clsName, sel, instVars); } }, enableSingleStepping: function(method, optClass, optSel) { // recompile method for single-stepping if (!method.compiled || !method.compiled.canSingleStep) { this.singleStep = true; // generate breakpoint support this.debug = true; if (!optClass) { this.vm.allMethodsDo(function(classObj, methodObj, selectorObj) { if (methodObj === method) { optClass = classObj; optSel = selectorObj; return true; } }); } var cls = optClass && optClass.className(); var sel = optSel && optSel.bytesAsString(); var instVars = optClass && optClass.allInstVarNames(); method.compiled = this.generate(method, cls, sel, instVars); method.compiled.canSingleStep = true; } // if a compiler does not support single-stepping, return false return true; }, functionNameFor: function(cls, sel) { if (cls === undefined || cls === '?') { var isMethod = this.method.sqClass === this.vm.specialObjects[Squeak.splOb_ClassCompiledMethod]; return isMethod ? "DOIT_" + ++this.doitCounter : "BLOCK_" + ++this.blockCounter; } cls = cls.replace(/ /g, "_").replace("[]", "Block"); if (!/[^a-zA-Z0-9:_]/.test(sel)) return cls + "_" + sel.replace(/:/g, "ː"); // unicode colon is valid in JS identifiers var op = sel.replace(/./g, function(char) { var repl = {'|': "OR", '~': "NOT", '<': "LT", '=': "EQ", '>': "GT", '&': "AND", '@': "AT", '*': "TIMES", '+': "PLUS", '\\': "MOD", '-': "MINUS", ',': "COMMA", '/': "DIV", '?': "IF"}[char]; return repl || 'OPERATOR'; }); return cls + "__" + op + "__"; }, }, 'generating', { generate: function(method, optClass, optSel, optInstVarNames) { this.method = method; this.sista = method.methodSignFlag(); this.pc = 0; // next bytecode this.endPC = 0; // pc of furthest jump target this.prevPC = 0; // pc at start of current instruction this.source = []; // snippets will be joined in the end this.sourceLabels = {}; // source pos of generated jump labels this.needsLabel = {}; // jump targets this.sourcePos = {}; // source pos of optional vars / statements this.needsVar = {}; // true if var was used this.needsBreak = false; // insert break check for previous bytecode if (optClass && optSel) this.source.push("// ", optClass, ">>", optSel, "\n"); this.instVarNames = optInstVarNames; this.allVars = ['context', 'stack', 'rcvr', 'inst[', 'temp[', 'lit[']; this.sourcePos['context'] = this.source.length; this.source.push("var context = vm.activeContext;\n"); this.sourcePos['stack'] = this.source.length; this.source.push("var stack = context.pointers;\n"); this.sourcePos['rcvr'] = this.source.length; this.source.push("var rcvr = vm.receiver;\n"); this.sourcePos['inst['] = this.source.length; this.source.push("var inst = rcvr.pointers;\n"); this.sourcePos['temp['] = this.source.length; this.source.push("var temp = vm.homeContext.pointers;\n"); this.sourcePos['lit['] = this.source.length; this.source.push("var lit = vm.method.pointers;\n"); this.sourcePos['loop-start'] = this.source.length; this.source.push("while (true) switch (vm.pc) {\ncase 0:\n"); if (this.sista) this.generateSista(method); else this.generateV3(method); var funcName = this.functionNameFor(optClass, optSel); if (this.singleStep) { if (this.debug) this.source.push("// all valid PCs have a label;\n"); this.source.push("default: throw Error('invalid PC');\n}"); // all PCs handled } else { this.sourcePos['loop-end'] = this.source.length; this.source.push("default: vm.interpretOne(true); return;\n}"); this.deleteUnneededLabels(); } this.deleteUnneededVariables(); var source = "'use strict';\nreturn function " + funcName + "(vm) {\n" + this.source.join("") + "}"; return new Function(source)(); }, generateV3: function(method) { this.done = false; while (!this.done) { var byte = method.bytes[this.pc++], byte2 = 0; switch (byte & 0xF8) { // load inst var case 0x00: case 0x08: this.generatePush("inst[", byte & 0x0F, "]"); break; // load temp var case 0x10: case 0x18: this.generatePush("temp[", 6 + (byte & 0xF), "]"); break; // loadLiteral case 0x20: case 0x28: case 0x30: case 0x38: this.generatePush("lit[", 1 + (byte & 0x1F), "]"); break; // loadLiteralIndirect case 0x40: case 0x48: case 0x50: case 0x58: this.generatePush("lit[", 1 + (byte & 0x1F), "].pointers[1]"); break; // storeAndPop inst var case 0x60: this.generatePopInto("inst[", byte & 0x07, "]"); break; // storeAndPop temp var case 0x68: this.generatePopInto("temp[", 6 + (byte & 0x07), "]"); break; // Quick push case 0x70: switch (byte) { case 0x70: this.generatePush("rcvr"); break; case 0x71: this.generatePush("vm.trueObj"); break; case 0x72: this.generatePush("vm.falseObj"); break; case 0x73: this.generatePush("vm.nilObj"); break; case 0x74: this.generatePush("-1"); break; case 0x75: this.generatePush("0"); break; case 0x76: this.generatePush("1"); break; case 0x77: this.generatePush("2"); break; } break; // Quick return case 0x78: switch (byte) { case 0x78: this.generateReturn("rcvr"); break; case 0x79: this.generateReturn("vm.trueObj"); break; case 0x7A: this.generateReturn("vm.falseObj"); break; case 0x7B: this.generateReturn("vm.nilObj"); break; case 0x7C: this.generateReturn("stack[vm.sp]"); break; case 0x7D: this.generateBlockReturn(); break; default: throw Error("unusedBytecode " + byte); } break; // Extended bytecodes case 0x80: case 0x88: this.generateV3Extended(byte); break; // short jump case 0x90: this.generateJump((byte & 0x07) + 1); break; // short conditional jump case 0x98: this.generateJumpIf(false, (byte & 0x07) + 1); break; // long jump, forward and back case 0xA0: byte2 = method.bytes[this.pc++]; this.generateJump(((byte&7)-4) * 256 + byte2); break; // long conditional jump case 0xA8: byte2 = method.bytes[this.pc++]; this.generateJumpIf(byte < 0xAC, (byte & 3) * 256 + byte2); break; // SmallInteger ops: + - < > <= >= = ~= * / @ lshift: lxor: land: lor: case 0xB0: case 0xB8: this.generateNumericOp(byte); break; // quick primitives: // at:, at:put:, size, next, nextPut:, ... case 0xC0: case 0xC8: this.generateQuickPrim(byte); break; // send literal selector case 0xD0: case 0xD8: this.generateSend("lit[", 1 + (byte & 0x0F), "]", 0, false); break; case 0xE0: case 0xE8: this.generateSend("lit[", 1 + (byte & 0x0F), "]", 1, false); break; case 0xF0: case 0xF8: this.generateSend("lit[", 1 + (byte & 0x0F), "]", 2, false); break; } } }, generateV3Extended: function(bytecode) { var byte2, byte3; switch (bytecode) { // extended push case 0x80: byte2 = this.method.bytes[this.pc++]; switch (byte2 >> 6) { case 0: this.generatePush("inst[", byte2 & 0x3F, "]"); return; case 1: this.generatePush("temp[", 6 + (byte2 & 0x3F), "]"); return; case 2: this.generatePush("lit[", 1 + (byte2 & 0x3F), "]"); return; case 3: this.generatePush("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return; } // extended store case 0x81: byte2 = this.method.bytes[this.pc++]; switch (byte2 >> 6) { case 0: this.generateStoreInto("inst[", byte2 & 0x3F, "]"); return; case 1: this.generateStoreInto("temp[", 6 + (byte2 & 0x3F), "]"); return; case 2: throw Error("illegal store into literal"); case 3: this.generateStoreInto("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return; } return; // extended pop into case 0x82: byte2 = this.method.bytes[this.pc++]; switch (byte2 >> 6) { case 0: this.generatePopInto("inst[", byte2 & 0x3F, "]"); return; case 1: this.generatePopInto("temp[", 6 + (byte2 & 0x3F), "]"); return; case 2: throw Error("illegal pop into literal"); case 3: this.generatePopInto("lit[", 1 + (byte2 & 0x3F), "].pointers[1]"); return; } // Single extended send case 0x83: byte2 = this.method.bytes[this.pc++]; this.generateSend("lit[", 1 + (byte2 & 0x1F), "]", byte2 >> 5, false); return; // Double extended do-anything case 0x84: byte2 = this.method.bytes[this.pc++]; byte3 = this.method.bytes[this.pc++]; switch (byte2 >> 5) { case 0: this.generateSend("lit[", 1 + byte3, "]", byte2 & 31, false); return; case 1: this.generateSend("lit[", 1 + byte3, "]", byte2 & 31, true); return; case 2: this.generatePush("inst[", byte3, "]"); return; case 3: this.generatePush("lit[", 1 + byte3, "]"); return; case 4: this.generatePush("lit[", 1 + byte3, "].pointers[1]"); return; case 5: this.generateStoreInto("inst[", byte3, "]"); return; case 6: this.generatePopInto("inst[", byte3, "]"); return; case 7: this.generateStoreInto("lit[", 1 + byte3, "].pointers[1]"); return; } // Single extended send to super case 0x85: byte2 = this.method.bytes[this.pc++]; this.generateSend("lit[", 1 + (byte2 & 0x1F), "]", byte2 >> 5, true); return; // Second extended send case 0x86: byte2 = this.method.bytes[this.pc++]; this.generateSend("lit[", 1 + (byte2 & 0x3F), "]", byte2 >> 6, false); return; // pop case 0x87: this.generateInstruction("pop", "vm.sp--"); return; // dup case 0x88: this.needsVar['stack'] = true; this.generateInstruction("dup", "var dup = stack[vm.sp]; stack[++vm.sp] = dup"); return; // thisContext case 0x89: this.needsVar['stack'] = true; this.generateInstruction("push thisContext", "stack[++vm.sp] = vm.exportThisContext()"); return; // closures case 0x8A: byte2 = this.method.bytes[this.pc++]; var popValues = byte2 > 127, count = byte2 & 127; this.generateClosureTemps(count, popValues); return; // call primitive case 0x8B: byte2 = this.method.bytes[this.pc++]; byte3 = this.method.bytes[this.pc++]; this.generateCallPrimitive(byte2 + 256 * byte3, 0x81); return // remote push from temp vector case 0x8C: byte2 = this.method.bytes[this.pc++]; byte3 = this.method.bytes[this.pc++]; this.generatePush("temp[", 6 + byte3, "].pointers[", byte2, "]"); return; // remote store into temp vector case 0x8D: byte2 = this.method.bytes[this.pc++]; byte3 = this.method.bytes[this.pc++]; this.generateStoreInto("temp[", 6 + byte3, "].pointers[", byte2, "]"); return; // remote store and pop into temp vector case 0x8E: byte2 = this.method.bytes[this.pc++]; byte3 = this.method.bytes[this.pc++]; this.generatePopInto("temp[", 6 + byte3, "].pointers[", byte2, "]"); return; // pushClosureCopy case 0x8F: byte2 = this.method.bytes[this.pc++]; byte3 = this.method.bytes[this.pc++]; var byte4 = this.method.bytes[this.pc++]; var numArgs = byte2 & 0xF, numCopied = byte2 >> 4, blockSize = byte3 << 8 | byte4; this.generateClosureCopy(numArgs, numCopied, blockSize); return; } }, generateSista: function() { var bytes = this.method.bytes, b, b2, b3, extA = 0, extB = 0; this.done = false; while (!this.done) { b = bytes[this.pc++]; switch (b) { // 1 Byte Bytecodes // load receiver variable case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: this.generatePush("inst[", b & 0x0F, "]"); break; // load literal variable case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: this.generatePush("lit[", 1 + (b & 0x0F), "].pointers[1]"); break; // load literal constant case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: this.generatePush("lit[", 1 + (b & 0x1F), "]"); break; // load temporary variable case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: this.generatePush("temp[", 6 + (b & 0x07), "]"); break; case 0x48: case 0x49: case 0x4A: case 0x4B: this.generatePush("temp[", 6 + (b & 0x03) + 8, "]"); break; case 0x4C: this.generatePush("rcvr"); break; case 0x4D: this.generatePush("vm.trueObj"); break; case 0x4E: this.generatePush("vm.falseObj"); break; case 0x4F: this.generatePush("vm.nilObj"); break; case 0x50: this.generatePush(0); break; case 0x51: this.generatePush(1); break; case 0x52: this.needsVar['stack'] = true; this.generateInstruction("push thisContext", "stack[++vm.sp] = vm.exportThisContext()"); break; case 0x53: this.needsVar['stack'] = true; this.generateInstruction("dup", "var dup = stack[vm.sp]; stack[++vm.sp] = dup"); break; case 0x54: case 0x55: case 0x56: case 0x57: throw Error("unusedBytecode " + b); case 0x58: this.generateReturn("rcvr"); break; case 0x59: this.generateReturn("vm.trueObj"); break; case 0x5A: this.generateReturn("vm.falseObj"); break; case 0x5B: this.generateReturn("vm.nilObj"); break; case 0x5C: this.generateReturn("stack[vm.sp]"); break; case 0x5D: this.generateBlockReturn("vm.nilObj"); break; case 0x5E: this.generateBlockReturn(); break; case 0x5F: break; // nop case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: this.generateNumericOp(b); break; case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: this.generateQuickPrim(b); break; case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: this.generateSend("lit[", 1 + (b & 0x0F), "]", 0, false); break; case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: this.generateSend("lit[", 1 + (b & 0x0F), "]", 1, false); break; case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: this.generateSend("lit[", 1 + (b & 0x0F), "]", 2, false); break; case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: this.generateJump((b & 0x07) + 1); break; case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: this.generateJumpIf(true, (b & 0x07) + 1); break; case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: this.generateJumpIf(false, (b & 0x07) + 1); break; case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: this.generatePopInto("inst[", b & 0x07, "]"); break; case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: this.generatePopInto("temp[", 6 + (b & 0x07), "]"); break; case 0xD8: this.generateInstruction("pop", "vm.sp--"); break; case 0xD9: throw Error("unumplementedBytecode: 0xD9 (unconditional trap)"); case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: throw Error("unusedBytecode " + b); // 2 Byte Bytecodes case 0xE0: b2 = bytes[this.pc++]; extA = extA * 256 + b2; continue; case 0xE1: b2 = bytes[this.pc++]; extB = extB * 256 + (b2 < 128 ? b2 : b2 - 256); continue; case 0xE2: b2 = bytes[this.pc++]; this.generatePush("inst[", b2 + extA * 256, "]"); break; case 0xE3: b2 = bytes[this.pc++]; this.generatePush("lit[", 1 + b2 + extA * 256, "].pointers[1]"); break; case 0xE4: b2 = bytes[this.pc++]; this.generatePush("lit[", 1 + b2 + extA * 256, "]"); break; case 0xE5: b2 = bytes[this.pc++]; this.generatePush("temp[", 6 + b2, "]"); break; case 0xE6: throw Error("unusedBytecode 0xE6"); case 0xE7: b2 = bytes[this.pc++]; var popValues = b2 > 127, count = b2 & 127; this.generateClosureTemps(count, popValues); break; case 0xE8: b2 = bytes[this.pc++]; this.generatePush(b2 + extB * 256); break; case 0xE9: b2 = bytes[this.pc++]; this.generatePush("vm.image.getCharacter(", b2 + extB * 256, ")"); break; case 0xEA: b2 = bytes[this.pc++]; this.generateSend("lit[", 1 + (b2 >> 3) + (extA << 5), "]", (b2 & 7) + (extB << 3), false); break; case 0xEB: b2 = bytes[this.pc++]; var lit = (b2 >> 3) + (extA << 5), numArgs = (b2 & 7) + ((extB & 63) << 3), directed = extB >= 64; this.generateSend("lit[", 1 + lit, "]", numArgs, directed ? "directed" : true); break; case 0xEC: throw Error("unimplemented bytecode: 0xEC (class trap)"); case 0xED: b2 = bytes[this.pc++]; this.generateJump(b2 + extB * 256); break; case 0xEE: b2 = bytes[this.pc++]; this.generateJumpIf(true, b2 + extB * 256); break; case 0xEF: b2 = bytes[this.pc++]; this.generateJumpIf(false, b2 + extB * 256); break; case 0xF0: b2 = bytes[this.pc++]; this.generatePopInto("inst[", b2 + extA * 256, "]"); break; case 0xF1: b2 = bytes[this.pc++]; this.generatePopInto("lit[", 1 + b2 + extA * 256, "].pointers[1]"); break; case 0xF2: b2 = bytes[this.pc++]; this.generatePopInto("temp[", 6 + b2, "]"); break; case 0xF3: b2 = bytes[this.pc++]; this.generateStoreInto("inst[", b2 + extA * 256, "]"); break; case 0xF4: b2 = bytes[this.pc++]; this.generateStoreInto("lit[", 1 + b2 + extA * 256, "].pointers[1]"); break; case 0xF5: b2 = bytes[this.pc++]; this.generateStoreInto("temp[", 6 + b2, "]"); break; case 0xF6: case 0xF7: throw Error("unusedBytecode " + b); // 3 Byte Bytecodes case 0xF8: b2 = bytes[this.pc++]; b3 = bytes[this.pc++]; this.generateCallPrimitive(b2 + b3 * 256, 0xF5); break; case 0xF9: b2 = bytes[this.pc++]; b3 = bytes[this.pc++]; this.generatePushFullClosure(b2 + extA * 255, b3); break; case 0xFA: b2 = bytes[this.pc++]; b3 = bytes[this.pc++]; var numArgs = (b2 & 0x07) + (extA & 0x0F) * 8, numCopied = (b2 >> 3 & 0x7) + (extA >> 4) * 8, blockSize = b3 + (extB << 8); this.generateClosureCopy(numArgs, numCopied, blockSize); break; case 0xFB: b2 = bytes[this.pc++]; b3 = bytes[this.pc++]; this.generatePush("temp[", 6 + b3, "].pointers[", b2, "]"); break; case 0xFC: b2 = bytes[this.pc++]; b3 = bytes[this.pc++]; this.generateStoreInto("temp[", 6 + b3, "].pointers[", b2, "]"); break; case 0xFD: b2 = bytes[this.pc++]; b3 = bytes[this.pc++]; this.generatePopInto("temp[", 6 + b3, "].pointers[", b2, "]"); break; case 0xFE: case 0xFF: throw Error("unusedBytecode " + b); default: throw Error("illegal bytecode: " + b); } extA = 0; extB = 0; } }, generatePush: function(target, arg1, suffix1, arg2, suffix2) { if (this.debug) this.generateDebugCode("push", target, arg1, suffix1, arg2, suffix2); this.generateLabel(); this.needsVar[target] = true; this.needsVar['stack'] = true; this.source.push("stack[++vm.sp] = ", target); if (arg1 !== undefined) { this.source.push(arg1, suffix1); if (arg2 !== undefined) { this.source.push(arg2, suffix2); } } this.source.push(";\n"); }, generateStoreInto: function(target, arg1, suffix1, arg2, suffix2) { if (this.debug) this.generateDebugCode("store into", target, arg1, suffix1, arg2, suffix2); this.generateLabel(); this.needsVar[target] = true; this.needsVar['stack'] = true; this.source.push(target); if (arg1 !== undefined) { this.source.push(arg1, suffix1); if (arg2 !== undefined) { this.source.push(arg2, suffix2); } } this.source.push(" = stack[vm.sp];\n"); this.generateDirty(target, arg1, suffix1); }, generatePopInto: function(target, arg1, suffix1, arg2, suffix2) { if (this.debug) this.generateDebugCode("pop into", target, arg1, suffix1, arg2, suffix2); this.generateLabel(); this.needsVar[target] = true; this.needsVar['stack'] = true; this.source.push(target); if (arg1 !== undefined) { this.source.push(arg1, suffix1); if (arg2 !== undefined) { this.source.push(arg2, suffix2); } } this.source.push(" = stack[vm.sp--];\n"); this.generateDirty(target, arg1, suffix1); }, generateReturn: function(what) { if (this.debug) this.generateDebugCode("return", what); this.generateLabel(); this.needsVar[what] = true; this.source.push( "vm.pc = ", this.pc, "; vm.doReturn(", what, "); return;\n"); this.needsBreak = false; // returning anyway this.done = this.pc > this.endPC; }, generateBlockReturn: function(retVal) { if (this.debug) this.generateDebugCode("block return"); this.generateLabel(); if (!retVal) { this.needsVar['stack'] = true; retVal = "stack[vm.sp--]"; } // actually stack === context.pointers but that would look weird this.needsVar['context'] = true; this.source.push( "vm.pc = ", this.pc, "; vm.doReturn(", retVal, ", context.pointers[0]); return;\n"); this.needsBreak = false; // returning anyway this.done = this.pc > this.endPC; }, generateJump: function(distance) { var destination = this.pc + distance; if (this.debug) this.generateDebugCode("jump to " + destination); this.generateLabel(); this.needsVar['context'] = true; this.source.push("vm.pc = ", destination, "; "); if (distance < 0) this.source.push( "\nif (vm.interruptCheckCounter-- <= 0) {\n", " vm.checkForInterrupts();\n", " if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n", "}\n"); if (this.singleStep) this.source.push("\nif (vm.breakOutOfInterpreter) return;\n"); this.source.push("continue;\n"); this.needsBreak = false; // already checked this.needsLabel[destination] = true; if (destination > this.endPC) this.endPC = destination; }, generateJumpIf: function(condition, distance) { var destination = this.pc + distance; if (this.debug) this.generateDebugCode("jump if " + condition + " to " + destination); this.generateLabel(); this.needsVar['stack'] = true; this.source.push( "var cond = stack[vm.sp--]; if (cond === vm.", condition, "Obj) {vm.pc = ", destination, "; "); if (this.singleStep) this.source.push("if (vm.breakOutOfInterpreter) return; else "); this.source.push("continue}\n", "else if (cond !== vm.", !condition, "Obj) {vm.sp++; vm.pc = ", this.pc, "; vm.send(vm.specialObjects[25], 0, false); return}\n"); this.needsLabel[this.pc] = true; // for coming back after #mustBeBoolean send this.needsLabel[destination] = true; // obviously if (destination > this.endPC) this.endPC = destination; }, generateQuickPrim: function(byte) { if (this.debug) this.generateDebugCode("quick send #" + this.specialSelectors[(byte & 0x0F) + 16]); this.generateLabel(); switch (byte & 0x0F) { case 0x0: // at: this.needsVar['stack'] = true; this.source.push( "var a, b; if ((a=stack[vm.sp-1]).sqClass === vm.specialObjects[7] && a.pointers && typeof (b=stack[vm.sp]) === 'number' && b>0 && b<=a.pointers.length) {\n", " stack[--vm.sp] = a.pointers[b-1];", "} else { var c = vm.primHandler.objectAt(true,true,false); if (vm.primHandler.success) stack[--vm.sp] = c; else {\n", " vm.pc = ", this.pc, "; vm.sendSpecial(16); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }}\n"); this.needsLabel[this.pc] = true; return; case 0x1: // at:put: this.needsVar['stack'] = true; this.source.push( "var a, b; if ((a=stack[vm.sp-2]).sqClass === vm.specialObjects[7] && a.pointers && typeof (b=stack[vm.sp-1]) === 'number' && b>0 && b<=a.pointers.length) {\n", " var c = stack[vm.sp]; stack[vm.sp-=2] = a.pointers[b-1] = c; a.dirty = true;", "} else { vm.primHandler.objectAtPut(true,true,false); if (vm.primHandler.success) stack[vm.sp-=2] = c; else {\n", " vm.pc = ", this.pc, "; vm.sendSpecial(17); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }}\n"); this.needsLabel[this.pc] = true; return; case 0x2: // size this.needsVar['stack'] = true; this.source.push( "if (stack[vm.sp].sqClass === vm.specialObjects[7]) stack[vm.sp] = stack[vm.sp].pointersSize();\n", // Array "else if (stack[vm.sp].sqClass === vm.specialObjects[6]) stack[vm.sp] = stack[vm.sp].bytesSize();\n", // ByteString "else { vm.pc = ", this.pc, "; vm.sendSpecial(18); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return; }\n"); this.needsLabel[this.pc] = true; return; //case 0x3: return false; // next //case 0x4: return false; // nextPut: //case 0x5: return false; // atEnd case 0x6: // == this.needsVar['stack'] = true; this.source.push("var cond = stack[vm.sp-1] === stack[vm.sp];\nstack[--vm.sp] = cond ? vm.trueObj : vm.falseObj;\n"); return; case 0x7: // class this.needsVar['stack'] = true; this.source.push("stack[vm.sp] = typeof stack[vm.sp] === 'number' ? vm.specialObjects[5] : stack[vm.sp].sqClass;\n"); return; case 0x8: // blockCopy: this.needsVar['rcvr'] = true; this.source.push( "vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), ")) ", "{vm.sendSpecial(", ((byte & 0x0F) + 16), "); return}\n"); this.needsLabel[this.pc] = true; // for send this.needsLabel[this.pc + 2] = true; // for start of block return; case 0x9: // value case 0xA: // value: case 0xB: // do: this.needsVar['rcvr'] = true; this.source.push( "vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), ")) vm.sendSpecial(", ((byte & 0x0F) + 16), "); return;\n"); this.needsLabel[this.pc] = true; return; //case 0xC: return false; // new //case 0xD: return false; // new: //case 0xE: return false; // x //case 0xF: return false; // y } // generic version for the bytecodes not yet handled above this.needsVar['rcvr'] = true; this.needsVar['context'] = true; this.source.push( "vm.pc = ", this.pc, "; if (!vm.primHandler.quickSendOther(rcvr, ", (byte & 0x0F), "))", " vm.sendSpecial(", ((byte & 0x0F) + 16), ");\n", "if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n"); this.needsBreak = false; // already checked // if falling back to a full send we need a label for coming back this.needsLabel[this.pc] = true; }, generateNumericOp: function(byte) { if (this.debug) this.generateDebugCode("quick send #" + this.specialSelectors[byte & 0x0F]); this.generateLabel(); // if the op cannot be executed here, do a full send and return to main loop // we need a label for coming back this.needsLabel[this.pc] = true; switch (byte & 0x0F) { case 0x0: // PLUS + this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = vm.primHandler.signed32BitIntegerFor(a + b);\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(0); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x1: // MINUS - this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = vm.primHandler.signed32BitIntegerFor(a - b);\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(1); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x2: // LESS < this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = a < b ? vm.trueObj : vm.falseObj;\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(2); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x3: // GRTR > this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = a > b ? vm.trueObj : vm.falseObj;\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(3); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x4: // LEQ <= this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = a <= b ? vm.trueObj : vm.falseObj;\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(4); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x5: // GEQ >= this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = a >= b ? vm.trueObj : vm.falseObj;\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(5); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x6: // EQU = this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = a === b ? vm.trueObj : vm.falseObj;\n", "} else if (a === b && a.float === a.float) {\n", // NaN check " stack[--vm.sp] = vm.trueObj;\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(6); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x7: // NEQ ~= this.needsVar['stack'] = true; this.source.push("var a = stack[vm.sp - 1], b = stack[vm.sp];\n", "if (typeof a === 'number' && typeof b === 'number') {\n", " stack[--vm.sp] = a !== b ? vm.trueObj : vm.falseObj;\n", "} else if (a === b && a.float === a.float) {\n", // NaN check " stack[--vm.sp] = vm.falseObj;\n", "} else { vm.pc = ", this.pc, "; vm.sendSpecial(7); if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return}\n"); return; case 0x8: // TIMES * this.source.push("vm.success = true; vm.resultIsFloat = false; if(!vm.pop2AndPushNumResult(vm.stackIntOrFloat(1) * vm.stackIntOrFloat(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(8); return}\n"); return; case 0x9: // DIV / this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.quickDivide(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(9); return}\n"); return; case 0xA: // MOD \ this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.mod(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(10); return}\n"); return; case 0xB: // MakePt int@int this.source.push("vm.success = true; if(!vm.primHandler.primitiveMakePoint(1, true)) { vm.pc = ", this.pc, "; vm.sendSpecial(11); return}\n"); return; case 0xC: // bitShift: this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.safeShift(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(12); return}\n"); return; case 0xD: // Divide // this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.div(vm.stackInteger(1),vm.stackInteger(0)))) { vm.pc = ", this.pc, "; vm.sendSpecial(13); return}\n"); return; case 0xE: // bitAnd: this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.stackInteger(1) & vm.stackInteger(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(14); return}\n"); return; case 0xF: // bitOr: this.source.push("vm.success = true; if(!vm.pop2AndPushIntResult(vm.stackInteger(1) | vm.stackInteger(0))) { vm.pc = ", this.pc, "; vm.sendSpecial(15); return}\n"); return; } }, generateSend: function(prefix, num, suffix, numArgs, superSend) { if (this.debug) this.generateDebugCode( (superSend === "directed" ? "directed super send " : superSend ? "super send " : "send ") + (prefix === "lit[" ? this.method.pointers[num].bytesAsString() : "...")); this.generateLabel(); this.needsVar[prefix] = true; this.needsVar['context'] = true; // set pc, activate new method, and return to main loop // unless the method was a successfull primitive call (no context change) this.source.push("vm.pc = ", this.pc); if (superSend === "directed") { this.source.push("; vm.sendSuperDirected(", prefix, num, suffix, ", ", numArgs, "); "); } else { this.source.push("; vm.send(", prefix, num, suffix, ", ", numArgs, ", ", superSend, "); "); } this.source.push("if (context !== vm.activeContext || vm.breakOutOfInterpreter !== false) return;\n"); this.needsBreak = false; // already checked // need a label for coming back after send this.needsLabel[this.pc] = true; }, generateClosureTemps: function(count, popValues) { if (this.debug) this.generateDebugCode("closure temps"); this.generateLabel(); this.needsVar['stack'] = true; this.source.push("var array = vm.instantiateClass(vm.specialObjects[7], ", count, ");\n"); if (popValues) { for (var i = 0; i < count; i++) this.source.push("array.pointers[", i, "] = stack[vm.sp - ", count - i - 1, "];\n"); this.source.push("stack[vm.sp -= ", count - 1, "] = array;\n"); } else { this.source.push("stack[++vm.sp] = array;\n"); } }, generateClosureCopy: function(numArgs, numCopied, blockSize) { var from = this.pc, to = from + blockSize; if (this.debug) this.generateDebugCode("push closure(" + from + "-" + (to-1) + "): " + numCopied + " copied, " + numArgs + " args"); this.generateLabel(); this.needsVar['stack'] = true; this.source.push( "var closure = vm.instantiateClass(vm.specialObjects[36], ", numCopied, ");\n", "closure.pointers[0] = context; vm.reclaimableContextCount = 0;\n", "closure.pointers[1] = ", from + this.method.pointers.length * 4 + 1, ";\n", // encodeSqueakPC "closure.pointers[2] = ", numArgs, ";\n"); if (numCopied > 0) { for (var i = 0; i < numCopied; i++) this.source.push("closure.pointers[", i + 3, "] = stack[vm.sp - ", numCopied - i - 1,"];\n"); this.source.push("stack[vm.sp -= ", numCopied - 1,"] = closure;\n"); } else { this.source.push("stack[++vm.sp] = closure;\n"); } this.source.push("vm.pc = ", to, ";\n"); if (this.singleStep) this.source.push("if (vm.breakOutOfInterpreter) return;\n"); this.source.push("continue;\n"); this.needsBreak = false; // already checked this.needsLabel[from] = true; // initial pc when activated this.needsLabel[to] = true; // for jump over closure if (to > this.endPC) this.endPC = to; }, generatePushFullClosure: function(index, b3) { if (this.debug) this.generateDebugCode("push full closure " + (index + 1)); this.generateLabel(); this.needsVar['lit['] = true; this.needsVar['rcvr'] = true; this.needsVar['stack'] = true; var numCopied = b3 & 63; var outer; if ((b3 >> 6 & 1) === 1) { outer = "vm.nilObj"; } else { outer = "context"; } if ((b3 >> 7 & 1) === 1) { throw Error("on-stack receiver not yet supported"); } this.source.push("var closure = vm.newFullClosure(", outer, ", ", numCopied, ", lit[", 1 + index, "]);\n"); this.source.push("closure.pointers[", Squeak.ClosureFull_receiver, "] = rcvr;\n"); if (outer === "context") this.source.push("vm.reclaimableContextCount = 0;\n"); if (numCopied > 0) { for (var i = 0; i < numCopied; i++) this.source.push("closure.pointers[", i + Squeak.ClosureFull_firstCopiedValue, "] = stack[vm.sp - ", numCopied - i - 1,"];\n"); this.source.push("stack[vm.sp -= ", numCopied - 1,"] = closure;\n"); } else { this.source.push("stack[++vm.sp] = closure;\n"); } }, generateCallPrimitive: function(index, extendedStoreBytecode) { if (this.debug) this.generateDebugCode("call primitive " + index); this.generateLabel(); if (this.method.bytes[this.pc] === extendedStoreBytecode) { this.needsVar['stack'] = true; this.source.push("if (vm.primFailCode) {stack[vm.sp] = vm.getErrorObjectFromPrimFailCode(); vm.primFailCode = 0;}\n"); } }, generateDirty: function(target, arg, suffix) { switch(target) { case "inst[": this.source.push("rcvr.dirty = true;\n"); break; case "lit[": this.source.push(target, arg, "].dirty = true;\n"); break; case "temp[": if (suffix !== "]") this.source.push(target, arg, "].dirty = true;\n"); break; default: throw Error("unexpected target " + target); } }, generateLabel: function() { // remember label position for deleteUnneededLabels() if (this.prevPC) { this.sourceLabels[this.prevPC] = this.source.length; this.source.push("case ", this.prevPC, ":\n"); // must match deleteUnneededLabels } this.prevPC = this.pc; }, generateDebugCode: function(command, what, arg1, suffix1, arg2, suffix2) { // single-step for previous instructiuon if (this.needsBreak) { this.source.push("if (vm.breakOutOfInterpreter) {vm.pc = ", this.prevPC, "; return}\n"); this.needsLabel[this.prevPC] = true; } // comment for this instruction var bytecodes = []; for (var i = this.prevPC; i < this.pc; i++) bytecodes.push((this.method.bytes[i] + 0x100).toString(16).slice(-2).toUpperCase()); this.source.push("// ", this.prevPC, " <", bytecodes.join(" "), "> ", command); // append argument to comment if (what !== undefined) { this.source.push(" "); switch (what) { case 'vm.nilObj': this.source.push('nil'); break; case 'vm.trueObj': this.source.push('true'); break; case 'vm.falseObj': this.source.push('false'); break; case 'rcvr': this.source.push('self'); break; case 'stack[vm.sp]': this.source.push('top of stack'); break; case 'inst[': if (!this.instVarNames) this.source.push('inst var ', arg1); else this.source.push(this.instVarNames[arg1]); break; case 'temp[': this.source.push('tmp', arg1 - 6); if (suffix1 !== ']') this.source.push('[', arg2, ']'); break; case 'lit[': var lit = this.method.pointers[arg1]; if (suffix1 === ']') this.source.push(lit); else this.source.push(lit.pointers[0].bytesAsString()); break; default: this.source.push(what); } } this.source.push("\n"); // enable single-step for next instruction this.needsBreak = this.singleStep; }, generateInstruction: function(comment, instr) { if (this.debug) this.generateDebugCode(comment); this.generateLabel(); this.source.push(instr, ";\n"); }, deleteUnneededLabels: function() { // switch statement is more efficient with fewer labels var hasAnyLabel = false; for (var i in this.sourceLabels) if (this.needsLabel[i]) hasAnyLabel = true; else for (var j = 0; j < 3; j++) this.source[this.sourceLabels[i] + j] = ""; if (!hasAnyLabel) { this.source[this.sourcePos['loop-start']] = ""; this.source[this.sourcePos['loop-end']] = ""; } }, deleteUnneededVariables: function() { if (this.needsVar['stack']) this.needsVar['context'] = true; if (this.needsVar['inst[']) this.needsVar['rcvr'] = true; for (var i = 0; i < this.allVars.length; i++) { var v = this.allVars[i]; if (!this.needsVar[v]) this.source[this.sourcePos[v]] = ""; } }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, "audio", { startAudioOut: function() { if (!this.audioOutContext) { this.audioOutContext = new AudioContext(); } return this.audioOutContext; }, stopAudioOut: function() { if (this.audioOutContext) { this.audioOutContext.close(); this.audioOutContext = null; } }, startAudioIn: function(thenDo, errorDo) { if (this.audioInContext) { this.audioInSource.disconnect(); return thenDo(this.audioInContext, this.audioInSource); } if (!navigator.mediaDevices) return errorDo("test: audio input not supported"); navigator.mediaDevices.getUserMedia({audio: true}) .then(stream => { this.audioInContext = new AudioContext(); this.audioInSource = this.audioInContext.createMediaStreamSource(stream); thenDo(this.audioInContext, this.audioInSource); }) .catch(err => errorDo("cannot access microphone. " + err.name + ": " + err.message)); }, stopAudioIn: function() { if (this.audioInSource) { this.audioInSource.disconnect(); this.audioInSource = null; this.audioInContext.close(); this.audioInContext = null; } }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, "known classes", { // BitBlt layout: BitBlt_dest: 0, BitBlt_source: 1, BitBlt_halftone: 2, BitBlt_combinationRule: 3, BitBlt_destX: 4, BitBlt_destY: 5, BitBlt_width: 6, BitBlt_height: 7, BitBlt_sourceX: 8, BitBlt_sourceY: 9, BitBlt_clipX: 10, BitBlt_clipY: 11, BitBlt_clipW: 12, BitBlt_clipH: 13, BitBlt_colorMap: 14, BitBlt_warpBase: 15, // Form layout: Form_bits: 0, Form_width: 1, Form_height: 2, Form_depth: 3, Form_offset: 4, }); Object.extend(Squeak.Primitives.prototype, 'display', { displayDirty: function() {}, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'display', { initDisplay: function(display) { this.display = display; this.display.vm = this.vm; this.indexedColors = [ 0xFFFFFFFF, 0xFF000001, 0xFFFFFFFF, 0xFF808080, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFF00FF, 0xFF202020, 0xFF404040, 0xFF606060, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, 0xFF080808, 0xFF101010, 0xFF181818, 0xFF282828, 0xFF303030, 0xFF383838, 0xFF484848, 0xFF505050, 0xFF585858, 0xFF686868, 0xFF707070, 0xFF787878, 0xFF878787, 0xFF8F8F8F, 0xFF979797, 0xFFA7A7A7, 0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7, 0xFF000001, 0xFF003300, 0xFF006600, 0xFF009900, 0xFF00CC00, 0xFF00FF00, 0xFF000033, 0xFF003333, 0xFF006633, 0xFF009933, 0xFF00CC33, 0xFF00FF33, 0xFF000066, 0xFF003366, 0xFF006666, 0xFF009966, 0xFF00CC66, 0xFF00FF66, 0xFF000099, 0xFF003399, 0xFF006699, 0xFF009999, 0xFF00CC99, 0xFF00FF99, 0xFF0000CC, 0xFF0033CC, 0xFF0066CC, 0xFF0099CC, 0xFF00CCCC, 0xFF00FFCC, 0xFF0000FF, 0xFF0033FF, 0xFF0066FF, 0xFF0099FF, 0xFF00CCFF, 0xFF00FFFF, 0xFF330000, 0xFF333300, 0xFF336600, 0xFF339900, 0xFF33CC00, 0xFF33FF00, 0xFF330033, 0xFF333333, 0xFF336633, 0xFF339933, 0xFF33CC33, 0xFF33FF33, 0xFF330066, 0xFF333366, 0xFF336666, 0xFF339966, 0xFF33CC66, 0xFF33FF66, 0xFF330099, 0xFF333399, 0xFF336699, 0xFF339999, 0xFF33CC99, 0xFF33FF99, 0xFF3300CC, 0xFF3333CC, 0xFF3366CC, 0xFF3399CC, 0xFF33CCCC, 0xFF33FFCC, 0xFF3300FF, 0xFF3333FF, 0xFF3366FF, 0xFF3399FF, 0xFF33CCFF, 0xFF33FFFF, 0xFF660000, 0xFF663300, 0xFF666600, 0xFF669900, 0xFF66CC00, 0xFF66FF00, 0xFF660033, 0xFF663333, 0xFF666633, 0xFF669933, 0xFF66CC33, 0xFF66FF33, 0xFF660066, 0xFF663366, 0xFF666666, 0xFF669966, 0xFF66CC66, 0xFF66FF66, 0xFF660099, 0xFF663399, 0xFF666699, 0xFF669999, 0xFF66CC99, 0xFF66FF99, 0xFF6600CC, 0xFF6633CC, 0xFF6666CC, 0xFF6699CC, 0xFF66CCCC, 0xFF66FFCC, 0xFF6600FF, 0xFF6633FF, 0xFF6666FF, 0xFF6699FF, 0xFF66CCFF, 0xFF66FFFF, 0xFF990000, 0xFF993300, 0xFF996600, 0xFF999900, 0xFF99CC00, 0xFF99FF00, 0xFF990033, 0xFF993333, 0xFF996633, 0xFF999933, 0xFF99CC33, 0xFF99FF33, 0xFF990066, 0xFF993366, 0xFF996666, 0xFF999966, 0xFF99CC66, 0xFF99FF66, 0xFF990099, 0xFF993399, 0xFF996699, 0xFF999999, 0xFF99CC99, 0xFF99FF99, 0xFF9900CC, 0xFF9933CC, 0xFF9966CC, 0xFF9999CC, 0xFF99CCCC, 0xFF99FFCC, 0xFF9900FF, 0xFF9933FF, 0xFF9966FF, 0xFF9999FF, 0xFF99CCFF, 0xFF99FFFF, 0xFFCC0000, 0xFFCC3300, 0xFFCC6600, 0xFFCC9900, 0xFFCCCC00, 0xFFCCFF00, 0xFFCC0033, 0xFFCC3333, 0xFFCC6633, 0xFFCC9933, 0xFFCCCC33, 0xFFCCFF33, 0xFFCC0066, 0xFFCC3366, 0xFFCC6666, 0xFFCC9966, 0xFFCCCC66, 0xFFCCFF66, 0xFFCC0099, 0xFFCC3399, 0xFFCC6699, 0xFFCC9999, 0xFFCCCC99, 0xFFCCFF99, 0xFFCC00CC, 0xFFCC33CC, 0xFFCC66CC, 0xFFCC99CC, 0xFFCCCCCC, 0xFFCCFFCC, 0xFFCC00FF, 0xFFCC33FF, 0xFFCC66FF, 0xFFCC99FF, 0xFFCCCCFF, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF3300, 0xFFFF6600, 0xFFFF9900, 0xFFFFCC00, 0xFFFFFF00, 0xFFFF0033, 0xFFFF3333, 0xFFFF6633, 0xFFFF9933, 0xFFFFCC33, 0xFFFFFF33, 0xFFFF0066, 0xFFFF3366, 0xFFFF6666, 0xFFFF9966, 0xFFFFCC66, 0xFFFFFF66, 0xFFFF0099, 0xFFFF3399, 0xFFFF6699, 0xFFFF9999, 0xFFFFCC99, 0xFFFFFF99, 0xFFFF00CC, 0xFFFF33CC, 0xFFFF66CC, 0xFFFF99CC, 0xFFFFCCCC, 0xFFFFFFCC, 0xFFFF00FF, 0xFFFF33FF, 0xFFFF66FF, 0xFFFF99FF, 0xFFFFCCFF, 0xFFFFFFFF]; }, primitiveBeCursor: function(argCount) { if (this.display.cursorCanvas) { var cursorForm = this.loadForm(this.stackNonInteger(argCount), true), maskForm = argCount === 1 ? this.loadForm(this.stackNonInteger(0)) : null; if (!this.success || !cursorForm) return false; var cursorCanvas = this.display.cursorCanvas, context = cursorCanvas.getContext("2d"), bounds = {left: 0, top: 0, right: cursorForm.width, bottom: cursorForm.height}; cursorCanvas.width = cursorForm.width; cursorCanvas.height = cursorForm.height; if (cursorForm.depth === 1) { if (maskForm) { cursorForm = this.cursorMergeMask(cursorForm, maskForm); this.showForm(context, cursorForm, bounds, [0x00000000, 0xFF0000FF, 0xFFFFFFFF, 0xFF000000]); } else { this.showForm(context, cursorForm, bounds, [0x00000000, 0xFF000000]); } } else { this.showForm(context, cursorForm, bounds, true); } var scale = this.display.scale || 1.0; if (cursorForm.width <= 16 && cursorForm.height <= 16) { scale = 1.0; } cursorCanvas.style.width = Math.round(cursorCanvas.width * scale) + "px"; cursorCanvas.style.height = Math.round(cursorCanvas.height * scale) + "px"; this.display.cursorOffsetX = cursorForm.offsetX * scale|0; this.display.cursorOffsetY = cursorForm.offsetY * scale|0; } this.vm.popN(argCount); return true; }, cursorMergeMask: function(cursor, mask) { // make 2-bit form from cursor and mask 1-bit forms var bits = new Uint32Array(16); for (var y = 0; y < 16; y++) { var c = cursor.bits[y], m = mask.bits[y], bit = 0x80000000, merged = 0; for (var x = 0; x < 16; x++) { merged = merged | ((m & bit) >> x) | ((c & bit) >> (x + 1)); bit = bit >>> 1; } bits[y] = merged; } return { obj: cursor.obj, bits: bits, depth: 2, width: 16, height: 16, offsetX: cursor.offsetX, offsetY: cursor.offsetY, msb: true, pixPerWord: 16, pitch: 1, } }, primitiveBeDisplay: function(argCount) { var displayObj = this.vm.stackValue(0); this.vm.specialObjects[Squeak.splOb_TheDisplay] = displayObj; this.vm.popN(argCount); // return self return true; }, primitiveReverseDisplay: function(argCount) { this.reverseDisplay = !this.reverseDisplay; this.redrawDisplay(); if (this.display.cursorCanvas) { var canvas = this.display.cursorCanvas, context = canvas.getContext("2d"), image = context.getImageData(0, 0, canvas.width, canvas.height), data = new Uint32Array(image.data.buffer); for (var i = 0; i < data.length; i++) data[i] = data[i] ^ 0x00FFFFFF; context.putImageData(image, 0, 0); } return true; }, primitiveShowDisplayRect: function(argCount) { // Force the given rectangular section of the Display to be copied to the screen. var rect = { left: this.stackInteger(3), top: this.stackInteger(1), right: this.stackInteger(2), bottom: this.stackInteger(0), }; if (!this.success) return false; this.redrawDisplay(rect); this.vm.popN(argCount); return true; }, redrawDisplay: function(rect) { var theDisplay = this.theDisplay(), bounds = {left: 0, top: 0, right: theDisplay.width, bottom: theDisplay.height}; if (rect) { if (rect.left > bounds.left) bounds.left = rect.left; if (rect.right < bounds.right) bounds.right = rect.right; if (rect.top > bounds.top) bounds.top = rect.top; if (rect.bottom < bounds.bottom) bounds.bottom = rect.bottom; } if (bounds.left < bounds.right && bounds.top < bounds.bottom) this.displayUpdate(theDisplay, bounds); }, showForm: function(ctx, form, rect, cursorColors) { if (!rect) return; var srcX = rect.left, srcY = rect.top, srcW = rect.right - srcX, srcH = rect.bottom - srcY, pixels = ctx.createImageData(srcW, srcH), pixelData = pixels.data; if (!pixelData.buffer) { // mobile IE uses a different data-structure pixelData = new Uint8Array(srcW * srcH * 4); } var dest = new Uint32Array(pixelData.buffer); switch (form.depth) { case 1: case 2: case 4: case 8: var colors = cursorColors || this.swappedColors; if (!colors) { colors = []; for (var i = 0; i < 256; i++) { var argb = this.indexedColors[i], abgr = (argb & 0xFF00FF00) // green and alpha + ((argb & 0x00FF0000) >> 16) // shift red down + ((argb & 0x000000FF) << 16); // shift blue up colors[i] = abgr; } this.swappedColors = colors; } if (this.reverseDisplay) { if (cursorColors) { colors = cursorColors.map(function(c){return c ^ 0x00FFFFFF}); } else { if (!this.reversedColors) this.reversedColors = colors.map(function(c){return c ^ 0x00FFFFFF}); colors = this.reversedColors; } } var mask = (1 << form.depth) - 1; var leftSrcShift = 32 - (srcX % form.pixPerWord + 1) * form.depth; for (var y = 0; y < srcH; y++) { var srcIndex = form.pitch * srcY + (srcX / form.pixPerWord | 0); var srcShift = leftSrcShift; var src = form.bits[srcIndex]; var dstIndex = pixels.width * y; for (var x = 0; x < srcW; x++) { dest[dstIndex++] = colors[(src >>> srcShift) & mask]; if ((srcShift -= form.depth) < 0) { srcShift = 32 - form.depth; src = form.bits[++srcIndex]; } } srcY++; } break; case 16: var leftSrcShift = srcX % 2 ? 0 : 16; for (var y = 0; y < srcH; y++) { var srcIndex = form.pitch * srcY + (srcX / 2 | 0); var srcShift = leftSrcShift; var src = form.bits[srcIndex]; var dstIndex = pixels.width * y; for (var x = 0; x < srcW; x++) { var rgb = src >>> srcShift; dest[dstIndex++] = ((rgb & 0x7C00) >> 7) // shift red down 2*5, up 0*8 + 3 + ((rgb & 0x03E0) << 6) // shift green down 1*5, up 1*8 + 3 + ((rgb & 0x001F) << 19) // shift blue down 0*5, up 2*8 + 3 + 0xFF000000; // set alpha to opaque if ((srcShift -= 16) < 0) { srcShift = 16; src = form.bits[++srcIndex]; } } srcY++; } break; case 32: var opaque = cursorColors ? 0 : 0xFF000000; // keep alpha for cursors for (var y = 0; y < srcH; y++) { var srcIndex = form.pitch * srcY + srcX; var dstIndex = pixels.width * y; for (var x = 0; x < srcW; x++) { var argb = form.bits[srcIndex++]; // convert ARGB -> ABGR var abgr = (argb & 0xFF00FF00) // green and alpha is okay | ((argb & 0x00FF0000) >> 16) // shift red down | ((argb & 0x000000FF) << 16) // shift blue up | opaque; // set alpha to opaque dest[dstIndex++] = abgr; } srcY++; } break; default: throw Error("depth not implemented"); } if (pixels.data !== pixelData) { pixels.data.set(pixelData); } ctx.putImageData(pixels, rect.left, rect.top); }, primitiveDeferDisplayUpdates: function(argCount) { var flag = this.stackBoolean(0); if (!this.success) return false; this.deferDisplayUpdates = flag; this.vm.popN(argCount); return true; }, primitiveForceDisplayUpdate: function(argCount) { this.vm.breakOut(); // show on screen this.vm.popN(argCount); return true; }, primitiveScanCharacters: function(argCount) { if (argCount !== 6) return false; // Load the arguments var kernDelta = this.stackInteger(0); var stops = this.stackNonInteger(1); var scanRightX = this.stackInteger(2); var sourceString = this.stackNonInteger(3); var scanStopIndex = this.stackInteger(4); var scanStartIndex = this.stackInteger(5); if (!this.success) return false; if (stops.pointersSize() < 258 || !sourceString.isBytes()) return false; if (!(scanStartIndex > 0 && scanStopIndex > 0 && scanStopIndex <= sourceString.bytesSize())) return false; // Load receiver and required instVars var rcvr = this.stackNonInteger(6); if (!this.success || rcvr.pointersSize() < 4) return false; var scanDestX = this.checkSmallInt(rcvr.pointers[0]); var scanLastIndex = this.checkSmallInt(rcvr.pointers[1]); var scanXTable = this.checkNonInteger(rcvr.pointers[2]); var scanMap = this.checkNonInteger(rcvr.pointers[3]); if (!this.success || scanMap.pointersSize() !== 256) return false; var maxGlyph = scanXTable.pointersSize() - 2; // Okay, here we go. We have eliminated nearly all failure // conditions, to optimize the inner fetches. var EndOfRun = 257; var CrossedX = 258; var scanLastIndex = scanStartIndex; while (scanLastIndex <= scanStopIndex) { // Known to be okay since scanStartIndex > 0 and scanStopIndex <= sourceString size var ascii = sourceString.bytes[scanLastIndex - 1]; // Known to be okay since stops size >= 258 var stopReason = stops.pointers[ascii]; if (!stopReason.isNil) { // Store everything back and get out of here since some stop conditionn needs to be checked" this.ensureSmallInt(scanDestX); if (!this.success) return false; rcvr.pointers[0] = scanDestX; rcvr.pointers[1] = scanLastIndex; return this.popNandPushIfOK(7, stopReason); } // Known to be okay since scanMap size = 256 var glyphIndex = this.checkSmallInt(scanMap.pointers[ascii]); // fail if the glyphIndex is out of range if (!this.success || glyphIndex < 0 || glyphIndex > maxGlyph) return false; var sourceX = this.checkSmallInt(scanXTable.pointers[glyphIndex]); var sourceX2 = this.checkSmallInt(scanXTable.pointers[glyphIndex + 1]); // Above may fail if non-integer entries in scanXTable if (!this.success) return false; var nextDestX = scanDestX + sourceX2 - sourceX; if (nextDestX > scanRightX) { // Store everything back and get out of here since we got to the right edge this.ensureSmallInt(scanDestX); if (!this.success) return false; rcvr.pointers[0] = scanDestX; rcvr.pointers[1] = scanLastIndex; stopReason = stops.pointers[CrossedX - 1]; return this.popNandPushIfOK(7, stopReason); } scanDestX = nextDestX + kernDelta; scanLastIndex = scanLastIndex + 1; } this.ensureSmallInt(scanDestX); if (!this.success) return false; rcvr.pointers[0] = scanDestX; rcvr.pointers[1] = scanStopIndex; stopReason = stops.pointers[EndOfRun - 1]; return this.popNandPushIfOK(7, stopReason); }, primitiveScreenSize: function(argCount) { var display = this.display, w = display.width || display.context.canvas.width, h = display.height || display.context.canvas.height; return this.popNandPushIfOK(argCount+1, this.makePointWithXandY(w, h)); }, primitiveScreenScaleFactor: function(argCount) { var scaleFactor = (this.display.highdpi ? window.devicePixelRatio : 1) || 1; return this.popNandPushIfOK(argCount+1, scaleFactor); }, primitiveSetFullScreen: function(argCount) { var flag = this.stackBoolean(0); if (!this.success) return false; if (this.display.fullscreen != flag) { if (this.display.fullscreenRequest) { // freeze until we get the right display size var unfreeze = this.vm.freeze(); this.display.fullscreenRequest(flag, function thenDo() { unfreeze(); }); } else { this.display.fullscreen = flag; this.vm.breakOut(); // let VM go into fullscreen mode } } this.vm.popN(argCount); return true; }, primitiveTestDisplayDepth: function(argCount) { var supportedDepths = [1, 2, 4, 8, 16, 32]; // match showForm return this.popNandPushBoolIfOK(argCount+1, supportedDepths.indexOf(this.stackInteger(0)) >= 0); }, loadForm: function(formObj, withOffset) { if (formObj.isNil) return null; var form = { obj: formObj, bits: formObj.pointers[Squeak.Form_bits].wordsOrBytes(), depth: formObj.pointers[Squeak.Form_depth], width: formObj.pointers[Squeak.Form_width], height: formObj.pointers[Squeak.Form_height], }; if (withOffset) { var offset = formObj.pointers[Squeak.Form_offset]; form.offsetX = offset.pointers ? offset.pointers[Squeak.Point_x] : 0; form.offsetY = offset.pointers ? offset.pointers[Squeak.Point_y] : 0; } if (form.width === 0 || form.height === 0) return form; if (!(form.width > 0 && form.height > 0)) return null; form.msb = form.depth > 0; if (!form.msb) form.depth = -form.depth; if (!(form.depth > 0)) return null; // happens if not int form.pixPerWord = 32 / form.depth; form.pitch = (form.width + (form.pixPerWord - 1)) / form.pixPerWord | 0; if (form.bits.length !== (form.pitch * form.height)) { if (form.bits.length > (form.pitch * form.height)) { this.vm.warnOnce("loadForm(): " + form.bits.length + " !== " + form.pitch + "*" + form.height + "=" + (form.pitch*form.height)); } else { return null; } } return form; }, theDisplay: function() { return this.loadForm(this.vm.specialObjects[Squeak.splOb_TheDisplay]); }, displayDirty: function(form, rect) { if (!this.deferDisplayUpdates && form == this.vm.specialObjects[Squeak.splOb_TheDisplay]) this.displayUpdate(this.theDisplay(), rect); }, displayUpdate: function(form, rect) { this.showForm(this.display.context, form, rect); this.display.lastTick = this.vm.lastTick; this.display.idle = 0; }, primitiveBeep: function(argCount) { var ctx = Squeak.startAudioOut(); if (ctx) { var beep = ctx.createOscillator(); beep.connect(ctx.destination); beep.type = 'square'; beep.frequency.value = 880; beep.start(); beep.stop(ctx.currentTime + 0.05); } else { this.vm.warnOnce("could not initialize audio"); } return this.popNIfOK(argCount); }, hostWindow_primitiveHostWindowTitle: function(argCount) { if (this.display.setTitle) { var utf8 = this.stackNonInteger(0).bytes; var title = (new TextDecoder()).decode(utf8); this.display.setTitle(title); } return this.popNIfOK(argCount); } }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, "files", { fsck: function(whenDone, dir, files, stale, stats) { dir = dir || ""; stale = stale || {dirs: [], files: []}; stats = stats || {dirs: 0, files: 0, bytes: 0, deleted: 0}; if (!files) { // find existing files files = {}; // ... in localStorage Object.keys(Squeak.Settings).forEach(function(key) { var match = key.match(/squeak-file(\.lz)?:(.*)$/); if (match) {files[match[2]] = true;} }); // ... or in memory if (window.SqueakDBFake) Object.keys(SqueakDBFake.bigFiles).forEach(function(path) { files[path] = true; }); // ... or in IndexedDB (the normal case) if (typeof indexedDB !== "undefined") { return this.dbTransaction("readonly", "fsck cursor", function(fileStore) { var cursorReq = fileStore.openCursor(); cursorReq.onsuccess = function(e) { var cursor = e.target.result; if (cursor) { files[cursor.key] = cursor.value.byteLength; cursor.continue(); } else { // got all files Squeak.fsck(whenDone, dir, files, stale, stats); } }; cursorReq.onerror = function(e) { console.error("fsck failed"); }; }); } // otherwise fall through } // check directories var entries = Squeak.dirList(dir); for (var name in entries) { var path = dir + "/" + name, isDir = entries[name][3]; if (isDir) { stats.dirs++; var exists = "squeak:" + path in Squeak.Settings; if (exists) { Squeak.fsck(null, path, files, stale, stats); } else { stale.dirs.push(path); } } else { stats.files++; if (path in files) { files[path] = null; // mark as visited stats.bytes += entries[name][4]; } else { stale.files.push(path); } } } if (dir === "") { // we're back at the root, almost done console.log("squeak fsck: " + stats.dirs + " directories, " + stats.files + " files, " + (stats.bytes/1000000).toFixed(1) + " MBytes"); // check orphaned files var orphaned = []; for (var path in files) { var size = files[path]; if (size !== null) orphaned.push({ path: path, size: size }); // not marked visited } // recreate directory entries for orphaned files for (var i = 0; i < orphaned.length; i++) { var path = Squeak.splitFilePath(orphaned[i].path); var size = orphaned[i].size; console.log("squeak fsck: restoring " + path.fullname + " (" + size + " bytes)"); Squeak.dirCreate(path.dirname, true, "force"); var directory = Squeak.dirList(path.dirname); var now = Squeak.totalSeconds(); var entry = [/*name*/ path.basename, /*ctime*/ now, /*mtime*/ 0, /*dir*/ false, size]; directory[path.basename] = entry; Squeak.Settings["squeak:" + path.dirname] = JSON.stringify(directory); } for (var i = 0; i < stale.dirs.length; i++) { var dir = stale.dirs[i]; if (Squeak.Settings["squeak:" + dir]) continue; // now contains orphaned files console.log("squeak fsck: cleaning up directory " + dir); Squeak.dirDelete(dir); stats.dirs--; stats.deleted++; } for (var i = 0; i < stale.files.length; i++) { var path = stale.files[i]; if (path in files) continue; // was orphaned if (Squeak.Settings["squeak:" + path]) continue; // now is a directory console.log("squeak fsck: cleaning up file entry " + path); Squeak.fileDelete(path); stats.files--; stats.deleted++; } if (whenDone) whenDone(stats); } }, dbTransaction: function(mode, description, transactionFunc, completionFunc) { // File contents is stored in the IndexedDB named "squeak" in object store "files" // and directory entries in localStorage with prefix "squeak:" function fakeTransaction() { transactionFunc(Squeak.dbFake()); if (completionFunc) completionFunc(); } if (typeof indexedDB == "undefined") { return fakeTransaction(); } function startTransaction() { var trans = SqueakDB.transaction("files", mode), fileStore = trans.objectStore("files"); trans.oncomplete = function(e) { if (completionFunc) completionFunc(); }; trans.onerror = function(e) { console.error("Transaction error during " + description, e); }; trans.onabort = function(e) { console.error("Transaction error: aborting " + description, e); // fall back to local/memory storage transactionFunc(Squeak.dbFake()); if (completionFunc) completionFunc(); }; transactionFunc(fileStore); } // if database connection already opened, just do transaction if (window.SqueakDB) return startTransaction(); // otherwise, open SqueakDB first var openReq; try { // fails in restricted iframe openReq = indexedDB.open("squeak"); } catch (err) {} // UIWebView implements the interface but only returns null // https://stackoverflow.com/questions/27415998/indexeddb-open-returns-null-on-safari-ios-8-1-1-and-halts-execution-on-cordova if (!openReq) { return fakeTransaction(); } openReq.onsuccess = function(e) { if (Squeak.debugFiles) console.log("Opened files database."); window.SqueakDB = this.result; SqueakDB.onversionchange = function(e) { delete window.SqueakDB; this.close(); }; SqueakDB.onerror = function(e) { console.error("Error accessing database", e); }; startTransaction(); }; openReq.onupgradeneeded = function (e) { // run only first time, or when version changed if (Squeak.debugFiles) console.log("Creating files database"); var db = e.target.result; db.createObjectStore("files"); }; openReq.onerror = function(e) { console.error("Error opening files database", e); console.warn("Falling back to local storage"); fakeTransaction(); }; openReq.onblocked = function(e) { // If some other tab is loaded with the database, then it needs to be closed // before we can proceed upgrading the database. console.log("Database upgrade needed, but was blocked."); console.warn("Falling back to local storage"); fakeTransaction(); }; }, dbFake: function() { // indexedDB is not supported by this browser, fake it using localStorage // since localStorage space is severly limited, use LZString if loaded // see https://github.com/pieroxy/lz-string if (typeof SqueakDBFake == "undefined") { if (typeof indexedDB == "undefined") console.warn("IndexedDB not supported by this browser, using localStorage"); window.SqueakDBFake = { bigFiles: {}, bigFileThreshold: 100000, get: function(filename) { var buffer = SqueakDBFake.bigFiles[filename]; if (!buffer) { var string = Squeak.Settings["squeak-file:" + filename]; if (!string) { var compressed = Squeak.Settings["squeak-file.lz:" + filename]; if (compressed) { if (typeof LZString == "object") { string = LZString.decompressFromUTF16(compressed); } else { console.error("LZString not loaded: cannot decompress " + filename); } } } if (string) { var bytes = new Uint8Array(string.length); for (var i = 0; i < bytes.length; i++) bytes[i] = string.charCodeAt(i) & 0xFF; buffer = bytes.buffer; } } var req = {result: buffer}; setTimeout(function(){ if (req.onsuccess) req.onsuccess({target: req}); }, 0); return req; }, put: function(buffer, filename) { if (buffer.byteLength > SqueakDBFake.bigFileThreshold) { if (!SqueakDBFake.bigFiles[filename]) console.log("File " + filename + " (" + buffer.byteLength + " bytes) too large, storing in memory only"); SqueakDBFake.bigFiles[filename] = buffer; } else { var string = Squeak.bytesAsString(new Uint8Array(buffer)); if (typeof LZString == "object") { var compressed = LZString.compressToUTF16(string); Squeak.Settings["squeak-file.lz:" + filename] = compressed; delete Squeak.Settings["squeak-file:" + filename]; } else { Squeak.Settings["squeak-file:" + filename] = string; } } var req = {}; setTimeout(function(){if (req.onsuccess) req.onsuccess();}, 0); return req; }, delete: function(filename) { delete Squeak.Settings["squeak-file:" + filename]; delete Squeak.Settings["squeak-file.lz:" + filename]; delete SqueakDBFake.bigFiles[filename]; var req = {}; setTimeout(function(){if (req.onsuccess) req.onsuccess();}, 0); return req; }, openCursor: function() { var req = {}; setTimeout(function(){if (req.onsuccess) req.onsuccess({target: req});}, 0); return req; }, }; } return SqueakDBFake; }, fileGet: function(filepath, thenDo, errorDo) { if (!errorDo) errorDo = function(err) { console.log(err); }; var path = this.splitFilePath(filepath); if (!path.basename) return errorDo("Invalid path: " + filepath); if (Squeak.debugFiles) { console.log("Reading " + path.fullname); var realThenDo = thenDo; thenDo = function(data) { console.log("Read " + data.byteLength + " bytes from " + path.fullname); realThenDo(data); }; } // if we have been writing to memory, return that version if (window.SqueakDBFake && SqueakDBFake.bigFiles[path.fullname]) return thenDo(SqueakDBFake.bigFiles[path.fullname]); this.dbTransaction("readonly", "get " + filepath, function(fileStore) { var getReq = fileStore.get(path.fullname); getReq.onerror = function(e) { errorDo(e); }; getReq.onsuccess = function(e) { if (this.result !== undefined) return thenDo(this.result); // might be a template Squeak.fetchTemplateFile(path.fullname, function gotTemplate(template) {thenDo(template);}, function noTemplate() { // if no indexedDB then we have checked fake db already if (typeof indexedDB == "undefined") return errorDo("file not found: " + path.fullname); // fall back on fake db, may be file is there var fakeReq = Squeak.dbFake().get(path.fullname); fakeReq.onerror = function(e) { errorDo("file not found: " + path.fullname); }; fakeReq.onsuccess = function(e) { thenDo(this.result); }; }); }; }); }, filePut: function(filepath, contents, optSuccess) { // store file, return dir entry if successful var path = this.splitFilePath(filepath); if (!path.basename) return null; var directory = this.dirList(path.dirname); if (!directory) return null; // get or create entry var entry = directory[path.basename], now = this.totalSeconds(); if (!entry) { // new file entry = [/*name*/ path.basename, /*ctime*/ now, /*mtime*/ 0, /*dir*/ false, /*size*/ 0]; directory[path.basename] = entry; } else if (entry[3]) // is a directory return null; if (Squeak.debugFiles) { console.log("Writing " + path.fullname + " (" + contents.byteLength + " bytes)"); if (contents.byteLength > 0 && filepath.endsWith(".log")) { console.log((new TextDecoder).decode(contents).replace(/\r/g, '\n')); } } // update directory entry entry[2] = now; // modification time entry[4] = contents.byteLength || contents.length || 0; Squeak.Settings["squeak:" + path.dirname] = JSON.stringify(directory); // put file contents (async) this.dbTransaction("readwrite", "put " + filepath, function(fileStore) { fileStore.put(contents, path.fullname); }, function transactionComplete() { if (optSuccess) optSuccess(); }); return entry; }, fileDelete: function(filepath, entryOnly) { var path = this.splitFilePath(filepath); if (!path.basename) return false; var directory = this.dirList(path.dirname); if (!directory) return false; var entry = directory[path.basename]; if (!entry || entry[3]) return false; // not found or is a directory // delete entry from directory delete directory[path.basename]; Squeak.Settings["squeak:" + path.dirname] = JSON.stringify(directory); if (Squeak.debugFiles) console.log("Deleting " + path.fullname); if (entryOnly) return true; // delete file contents (async) this.dbTransaction("readwrite", "delete " + filepath, function(fileStore) { fileStore.delete(path.fullname); }); return true; }, fileRename: function(from, to) { var oldpath = this.splitFilePath(from); if (!oldpath.basename) return false; var newpath = this.splitFilePath(to); if (!newpath.basename) return false; var olddir = this.dirList(oldpath.dirname); if (!olddir) return false; var entry = olddir[oldpath.basename]; if (!entry || entry[3]) return false; // not found or is a directory var samedir = oldpath.dirname == newpath.dirname; var newdir = samedir ? olddir : this.dirList(newpath.dirname); if (!newdir) return false; if (newdir[newpath.basename]) return false; // exists already if (Squeak.debugFiles) console.log("Renaming " + oldpath.fullname + " to " + newpath.fullname); delete olddir[oldpath.basename]; // delete old entry entry[0] = newpath.basename; // rename entry newdir[newpath.basename] = entry; // add new entry Squeak.Settings["squeak:" + newpath.dirname] = JSON.stringify(newdir); if (!samedir) Squeak.Settings["squeak:" + oldpath.dirname] = JSON.stringify(olddir); // move file contents (async) this.fileGet(oldpath.fullname, function success(contents) { this.dbTransaction("readwrite", "rename " + oldpath.fullname + " to " + newpath.fullname, function(fileStore) { fileStore.delete(oldpath.fullname); fileStore.put(contents, newpath.fullname); }); }.bind(this), function error(msg) { console.log("File rename failed: " + msg); }.bind(this)); return true; }, fileExists: function(filepath) { var path = this.splitFilePath(filepath); if (!path.basename) return false; var directory = this.dirList(path.dirname); if (!directory) return false; var entry = directory[path.basename]; if (!entry || entry[3]) return false; // not found or is a directory return true; }, dirCreate: function(dirpath, withParents, force) { var path = this.splitFilePath(dirpath); if (!path.basename) return false; if (withParents && !Squeak.Settings["squeak:" + path.dirname]) Squeak.dirCreate(path.dirname, true); var parent = this.dirList(path.dirname); if (!parent) return false; var existing = parent[path.basename]; if (existing) { if (!existing[3]) { // already exists and is not a directory if (!force) return false; existing[3] = true; // force it to be a directory Squeak.Settings["squeak:" + path.dirname] = JSON.stringify(parent); } if (Squeak.Settings["squeak:" + path.fullname]) return true; // already exists // directory exists but is not in localStorage, so create it // (this is not supposed to happen but deal with it anyways) } if (Squeak.debugFiles) console.log("Creating directory " + path.fullname); var now = this.totalSeconds(), entry = [/*name*/ path.basename, /*ctime*/ now, /*mtime*/ now, /*dir*/ true, /*size*/ 0]; parent[path.basename] = entry; Squeak.Settings["squeak:" + path.fullname] = JSON.stringify({}); Squeak.Settings["squeak:" + path.dirname] = JSON.stringify(parent); return true; }, dirDelete: function(dirpath) { var path = this.splitFilePath(dirpath); if (!path.basename) return false; var directory = this.dirList(path.dirname); if (!directory) return false; if (!directory[path.basename]) return false; var children = this.dirList(path.fullname); if (children) for (var child in children) return false; // not empty if (Squeak.debugFiles) console.log("Deleting directory " + path.fullname); // delete from parent delete directory[path.basename]; Squeak.Settings["squeak:" + path.dirname] = JSON.stringify(directory); // delete itself delete Squeak.Settings["squeak:" + path.fullname]; return true; }, dirList: function(dirpath, includeTemplates) { // return directory entries or null var path = this.splitFilePath(dirpath), localEntries = Squeak.Settings["squeak:" + path.fullname], template = includeTemplates && Squeak.Settings["squeak-template:" + path.fullname]; function addEntries(dir, entries) { for (var key in entries) { if (entries.hasOwnProperty(key)) { var entry = entries[key]; dir[entry[0]] = entry; } } } if (localEntries || template) { // local entries override templates var dir = {}; if (template) addEntries(dir, JSON.parse(template).entries); if (localEntries) addEntries(dir, JSON.parse(localEntries)); return dir; } if (path.fullname == "/") return {}; return null; }, splitFilePath: function(filepath) { if (filepath[0] !== '/') filepath = '/' + filepath; filepath = filepath.replace(/\/\//g, '/'); // replace double-slashes var matches = filepath.match(/(.*)\/(.*)/), dirname = matches[1] ? matches[1] : '/', basename = matches[2] ? matches[2] : null; return {fullname: filepath, dirname: dirname, basename: basename}; }, splitUrl: function(url, base) { var matches = url.match(/(.*\/)?(.*)/), uptoslash = matches[1] || '', filename = matches[2] || ''; if (!uptoslash.match(/^[a-z]+:\/\//)) { if (base && !base.match(/\/$/)) base += '/'; uptoslash = (base || '') + uptoslash; url = uptoslash + filename; } return {full: url, uptoslash: uptoslash, filename: filename}; }, flushFile: function(file) { if (file.modified) { var buffer = file.contents.buffer; if (buffer.byteLength !== file.size) { buffer = new ArrayBuffer(file.size); (new Uint8Array(buffer)).set(file.contents.subarray(0, file.size)); } Squeak.filePut(file.name, buffer); file.modified = false; } }, flushAllFiles: function() { if (typeof SqueakFiles == 'undefined') return; for (var name in SqueakFiles) this.flushFile(SqueakFiles[name]); }, closeAllFiles: function() { // close the files held open in memory Squeak.flushAllFiles(); delete window.SqueakFiles; }, fetchTemplateDir: function(path, url) { // Called on app startup. Fetch url/sqindex.json and // cache all subdirectory entries in Squeak.Settings. // File contents is only fetched on demand path = Squeak.splitFilePath(path).fullname; function ensureTemplateParent(template) { var path = Squeak.splitFilePath(template); if (path.dirname !== "/") ensureTemplateParent(path.dirname); var template = JSON.parse(Squeak.Settings["squeak-template:" + path.dirname] || '{"entries": {}}'); if (!template.entries[path.basename]) { var now = Squeak.totalSeconds(); template.entries[path.basename] = [path.basename, now, now, true, 0]; Squeak.Settings["squeak-template:" + path.dirname] = JSON.stringify(template); } } function checkSubTemplates(path, url) { var template = JSON.parse(Squeak.Settings["squeak-template:" + path]); for (var key in template.entries) { var entry = template.entries[key]; if (entry[3]) Squeak.fetchTemplateDir(path + "/" + entry[0], url + "/" + entry[0]); } } if (Squeak.Settings["squeak-template:" + path]) { checkSubTemplates(path, url); } else { var index = url + "/sqindex.json"; var rq = new XMLHttpRequest(); rq.open('GET', index, true); rq.onload = function(e) { if (rq.status == 200) { console.log("adding template dir " + path); ensureTemplateParent(path); var entries = JSON.parse(rq.response), template = {url: url, entries: {}}; for (var key in entries) { var entry = entries[key]; template.entries[entry[0]] = entry; } Squeak.Settings["squeak-template:" + path] = JSON.stringify(template); checkSubTemplates(path, url); } else rq.onerror(rq.statusText); }; rq.onerror = function(e) { console.log("cannot load template index " + index); }; rq.send(); } }, fetchTemplateFile: function(path, ifFound, ifNotFound) { path = Squeak.splitFilePath(path); var template = Squeak.Settings["squeak-template:" + path.dirname]; if (!template) return ifNotFound(); var url = JSON.parse(template).url; if (!url) return ifNotFound(); url += "/" + path.basename; var rq = new XMLHttpRequest(); rq.open("get", url, true); rq.responseType = "arraybuffer"; rq.timeout = 30000; rq.onreadystatechange = function() { if (this.readyState != this.DONE) return; if (this.status == 200) { var buffer = this.response; console.log("Got " + buffer.byteLength + " bytes from " + url); Squeak.dirCreate(path.dirname, true); Squeak.filePut(path.fullname, buffer); ifFound(buffer); } else { console.error("Download failed (" + this.status + ") " + url); ifNotFound(); } }; console.log("Fetching " + url); rq.send(); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, "events", { Mouse_Blue: 1, Mouse_Yellow: 2, Mouse_Red: 4, Keyboard_Shift: 8, Keyboard_Ctrl: 16, Keyboard_Alt: 32, Keyboard_Cmd: 64, Mouse_All: 1 + 2 + 4, Keyboard_All: 8 + 16 + 32 + 64, EventTypeNone: 0, EventTypeMouse: 1, EventTypeKeyboard: 2, EventTypeDragDropFiles: 3, EventKeyChar: 0, EventKeyDown: 1, EventKeyUp: 2, EventDragEnter: 1, EventDragMove: 2, EventDragLeave: 3, EventDragDrop: 4, EventTypeWindow: 5, EventTypeComplex: 6, EventTypeMouseWheel: 7, WindowEventMetricChange: 1, WindowEventClose: 2, WindowEventIconise: 3, WindowEventActivated: 4, WindowEventPaint: 5, WindowEventScreenChange: 6, EventTouchDown: 1, EventTouchUp: 2, EventTouchMoved: 3, EventTouchStationary: 4, EventTouchCancelled: 5, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'input', { primitiveClipboardText: function(argCount) { // There are two ways this primitive is invoked: // 1: via the DOM keyboard event thandler in squeak.js that intercepts cmd-c/cmd-v, // reads/writes the system clipboard from/to display.clipboardString // and then the interpreter calls the primitive // 2: via the image code e.g. a menu copy/paste item, which calls the primitive // and we try to read/write the system clipboard directly. // To support this, squeak.js keeps running the interpreter for 100 ms within // the DOM event 'mouseup' handler so the code below runs in the click-handler context, // (otherwise the browser would block access to the clipboard) if (argCount === 0) { // read from clipboard // Try to read from system clipboard, which is async if available. // It will likely fail outside of an event handler. var clipBoardPromise = null; if (this.display.readFromSystemClipboard) clipBoardPromise = this.display.readFromSystemClipboard(); if (clipBoardPromise) { var unfreeze = this.vm.freeze(); clipBoardPromise .then(() => this.vm.popNandPush(1, this.makeStString(this.display.clipboardString))) .catch(() => this.vm.popNandPush(1, this.vm.nilObj)) .finally(unfreeze); } else { if (typeof(this.display.clipboardString) !== 'string') return false; this.vm.popNandPush(1, this.makeStString(this.display.clipboardString)); } } else if (argCount === 1) { // write to clipboard var stringObj = this.vm.top(); if (stringObj.bytes) { this.display.clipboardString = stringObj.bytesAsString(); this.display.clipboardStringChanged = true; // means it should be written to system clipboard if (this.display.writeToSystemClipboard) { // no need to wait for the promise this.display.writeToSystemClipboard(); } } this.vm.pop(); } return true; }, primitiveKeyboardNext: function(argCount) { return this.popNandPushIfOK(argCount+1, this.ensureSmallInt(this.display.keys.shift())); }, primitiveKeyboardPeek: function(argCount) { var length = this.display.keys.length; return this.popNandPushIfOK(argCount+1, length ? this.ensureSmallInt(this.display.keys[0] || 0) : this.vm.nilObj); }, primitiveMouseButtons: function(argCount) { // only used in non-event based (old MVC) images this.popNandPushIfOK(argCount+1, this.ensureSmallInt(this.display.buttons)); // if the image calls this primitive it means it's done displaying // we break out of the VM so the browser shows it quickly this.vm.breakOut(); // if nothing was drawn but the image looks at the buttons rapidly, // it must be idle. if (this.display.idle++ > 20) this.vm.goIdle(); // might switch process, so must be after pop return true; }, primitiveMousePoint: function(argCount) { var x = this.ensureSmallInt(this.display.mouseX), y = this.ensureSmallInt(this.display.mouseY); return this.popNandPushIfOK(argCount+1, this.makePointWithXandY(x, y)); }, primitiveInputSemaphore: function(argCount) { var semaIndex = this.stackInteger(0); if (!this.success) return false; this.inputEventSemaIndex = semaIndex; this.display.signalInputEvent = function() { this.signalSemaphoreWithIndex(this.inputEventSemaIndex); }.bind(this); return this.popNIfOK(argCount); }, primitiveInputWord: function(argCount) { // Return an integer indicating the reason for the most recent input interrupt return this.popNandPushIfOK(1, 0); // noop for now }, primitiveGetNextEvent: function(argCount) { this.display.idle++; var evtBuf = this.stackNonInteger(0); if (!this.display.getNextEvent) return false; this.display.getNextEvent(evtBuf.pointers, this.vm.startupTime); return this.popNIfOK(argCount); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'initialization', { initPlugins: function() { Object.extend(this.builtinModules, { JavaScriptPlugin: this.findPluginFunctions("js_"), FilePlugin: this.findPluginFunctions("", "primitive(Disable)?(File|Directory)"), DropPlugin: this.findPluginFunctions("", "primitiveDropRequest"), SoundPlugin: this.findPluginFunctions("snd_"), JPEGReadWriter2Plugin: this.findPluginFunctions("jpeg2_"), SqueakFFIPrims: this.findPluginFunctions("ffi_", "", true), HostWindowPlugin: this.findPluginFunctions("hostWindow_"), SecurityPlugin: { primitiveDisableImageWrite: this.fakePrimitive.bind(this, "SecurityPlugin.primitiveDisableImageWrite", 0), primitiveGetUntrustedUserDirectory: this.fakePrimitive.bind(this, "SecurityPlugin.primitiveGetUntrustedUserDirectory", "/SqueakJS"), }, LocalePlugin: { primitiveTimezoneOffset: this.fakePrimitive.bind(this, "LocalePlugin.primitiveTimezoneOffset", 0), }, }); Object.extend(this.patchModules, { ScratchPlugin: this.findPluginFunctions("scratch_"), }); }, findPluginFunctions: function(prefix, match, bindLate) { match = match || "(initialise|shutdown|prim)"; var plugin = {}, regex = new RegExp("^" + prefix + match, "i"); for (var funcName in this) if (regex.test(funcName) && typeof this[funcName] == "function") { var primName = funcName; if (prefix) primName = funcName[prefix.length].toLowerCase() + funcName.slice(prefix.length + 1); plugin[primName] = bindLate ? funcName : this[funcName].bind(this); } return plugin; }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, "known classes", { // ExternalLibraryFunction layout: ExtLibFunc_handle: 0, ExtLibFunc_flags: 1, ExtLibFunc_argTypes: 2, ExtLibFunc_name: 3, ExtLibFunc_module: 4, ExtLibFunc_errorCodeName: 5, }, "FFI error codes", { FFINoCalloutAvailable: -1, // No callout mechanism available FFIErrorGenericError: 0, // generic error FFIErrorNotFunction: 1, // primitive invoked without ExternalFunction FFIErrorBadArgs: 2, // bad arguments to primitive call FFIErrorBadArg: 3, // generic bad argument FFIErrorIntAsPointer: 4, // int passed as pointer FFIErrorBadAtomicType: 5, // bad atomic type (e.g., unknown) FFIErrorCoercionFailed: 6, // argument coercion failed FFIErrorWrongType: 7, // Type check for non-atomic types failed FFIErrorStructSize: 8, // struct size wrong or too large FFIErrorCallType: 9, // unsupported calling convention FFIErrorBadReturn: 10, // cannot return the given type FFIErrorBadAddress: 11, // bad function address FFIErrorNoModule: 12, // no module given but required for finding address FFIErrorAddressNotFound: 13, // function address not found FFIErrorAttemptToPassVoid: 14, // attempt to pass 'void' parameter FFIErrorModuleNotFound: 15, // module not found FFIErrorBadExternalLibrary: 16, // external library invalid FFIErrorBadExternalFunction: 17, // external function invalid FFIErrorInvalidPointer: 18, // ExternalAddress points to ST memory (don't you dare to do this!) FFIErrorCallFrameTooBig: 19, // Stack frame required more than 16k bytes to pass arguments. }, "FFI types", { // type void FFITypeVoid: 0, // type bool FFITypeBool: 1, // basic integer types. // note: (integerType anyMask: 1) = integerType isSigned FFITypeUnsignedInt8: 2, FFITypeSignedInt8: 3, FFITypeUnsignedInt16: 4, FFITypeSignedInt16: 5, FFITypeUnsignedInt32: 6, FFITypeSignedInt32: 7, FFITypeUnsignedInt64: 8, FFITypeSignedInt64: 9, // original character types // note: isCharacterType ^type >> 1 >= 5 and: [(type >> 1) odd] FFITypeUnsignedChar8: 10, FFITypeSignedChar8: 11, // N.B. misnomer! // float types // note: isFloatType ^type >> 1 = 6 FFITypeSingleFloat: 12, FFITypeDoubleFloat: 13, // new character types // note: isCharacterType ^type >> 1 >= 5 and: [(type >> 1) odd] FFITypeUnsignedChar16: 14, FFITypeUnsignedChar32: 15, // type flags FFIFlagAtomic: 0x40000, // type is atomic FFIFlagPointer: 0x20000, // type is pointer to base type (a.k.a. array) FFIFlagStructure: 0x10000, // baseType is structure of 64k length FFIFlagAtomicPointer: 0x60000, // baseType is atomic and pointer (array) FFIFlagMask: 0x70000, // mask for flags FFIStructSizeMask: 0xFFFF, // mask for max size of structure FFIAtomicTypeMask: 0x0F000000, // mask for atomic type spec FFIAtomicTypeShift: 24, // shift for atomic type }); Object.extend(Squeak.Primitives.prototype, 'FFI', { // naming: // - ffi_* for public methods of SqueakFFIPrims module // (see vm.plugins.js) // - other ffi* for private methods of this module // - primitiveCalloutToFFI: old callout primitive (not in SqueakFFIPrims) ffi_lastError: 0, ffiModules: {}, // map library name to module name ffiFuncs: [], // functions loaded via dlsym (index + 1 is handle) // create an external address as handle for a function dynamically // this is a hook for other modules equivalent to dlsym() in C. // Later we can retrieve the module and func by handle ffiLookupFunc: function(mod, funcName) { var handle = this.ffiFuncs.findIndex(func => func.funcName === funcName) + 1; if (!handle) { if (!mod[funcName]) return 0; // we could keep a reverse map, but this is not time-critical var modName = Object.keys(this.loadedModules).find(name => this.loadedModules[name] === mod); if (modName === undefined) throw Error("FFI: module not loaded?! " + mod.getModuleName()); var libName = Object.keys(this.ffiModules).find(name => this.ffiModules[name] === modName); if (libName === undefined) throw Error("FFI: library not found?! " + mod.getModuleName()); this.ffiFuncs.push({libName: libName, modName: modName, funcName: funcName}); handle = this.ffiFuncs.length; } return handle; }, ffiDoCallout: function(argCount, extLibFunc, stArgs) { this.ffi_lastError = Squeak.FFIErrorGenericError; var libName = extLibFunc.pointers[Squeak.ExtLibFunc_module].bytesAsString(); var funcName = extLibFunc.pointers[Squeak.ExtLibFunc_name].bytesAsString(); var funcAddr = extLibFunc.pointers[Squeak.ExtLibFunc_handle].wordsOrBytes()[0]; var modName = this.ffiModules[libName]; if (funcAddr) { // this func was looked up originally via ffiLookupFunc var func = this.ffiFuncs[funcAddr - 1]; if (!func) throw Error("FFI: not a valid External Address: " + funcAddr); libName = func.libName; modName = func.modName; funcName = func.funcName; } if (!libName) libName = "libc"; // default to libc if (modName === undefined) { if (!Squeak.externalModules[libName]) { var prefixes = ["", "lib"]; var suffixes = ["", ".so", ".so.9", ".9", ".so.8", ".8", ".so.7", ".7", ".so.6", ".6", ".so.5", ".5", ".so.4", ".4", ".so.3", ".3", ".so.2", ".2", ".so.1", ".1"]; loop: for (var p = 0; p < prefixes.length; p++) { var prefix = prefixes[p]; for (var s = 0; s < suffixes.length; s++) { var suffix = suffixes[s]; if (Squeak.externalModules[prefix + libName + suffix]) { modName = prefix + libName + suffix; break loop; } if (prefix && libName.startsWith(prefix) && Squeak.externalModules[libName.slice(prefix.length) + suffix]) { modName = libName.slice(prefix.length) + suffix; break loop; } if (suffix && libName.endsWith(suffix) && Squeak.externalModules[prefix + libName.slice(0, -suffix.length)]) { modName = prefix + libName.slice(0, -suffix.length); break loop; } } } if (modName) console.log("FFI: found library " + libName + " as module " + modName); // there still is a chance loadModuleDynamically will find it under libName } if (!modName) modName = libName; // default to libName this.ffiModules[libName] = modName; } var mod = this.loadedModules[modName]; if (mod === undefined) { // null if earlier load failed mod = this.loadModule(modName); this.loadedModules[modName] = mod; if (!mod) { this.vm.warnOnce('FFI: library not found: ' + libName); } } if (!mod) { this.ffi_lastError = Squeak.FFIErrorModuleNotFound; return false; } // types[0] is return type, types[1] is first arg type, etc. var stTypes = extLibFunc.pointers[Squeak.ExtLibFunc_argTypes].pointers; var jsArgs = []; for (var i = 0; i < stArgs.length; i++) { jsArgs.push(this.ffiArgFromSt(stArgs[i], stTypes[i+1])); } var jsResult; if (!(funcName in mod)) { if (this.vm.warnOnce('FFI: function not found: ' + libName + '::' + funcName)) { console.warn(jsArgs); } if (mod.ffiFunctionNotFoundHandler) { jsResult = mod.ffiFunctionNotFoundHandler(funcName, jsArgs); } if (jsResult === undefined) { this.ffi_lastError = Squeak.FFIErrorAddressNotFound; return false; } } else { jsResult = mod[funcName].apply(mod, jsArgs); } var stResult = this.ffiResultToSt(jsResult, stTypes[0]); return this.popNandPushIfOK(argCount + 1, stResult); }, ffiArgFromSt: function(stObj, stType) { var typeSpec = stType.pointers[0].words[0]; switch (typeSpec & Squeak.FFIFlagMask) { case Squeak.FFIFlagAtomic: // single value var atomicType = (typeSpec & Squeak.FFIAtomicTypeMask) >> Squeak.FFIAtomicTypeShift; switch (atomicType) { case Squeak.FFITypeVoid: return null; case Squeak.FFITypeBool: if (stObj.isTrue) return true; if (stObj.isFalse) return false; if (typeof stObj === "number") return !!stObj; if (stObj.isFloat) return !!stObj.float; throw Error("FFI: expected bool, got " + stObj); case Squeak.FFITypeUnsignedInt8: case Squeak.FFITypeSignedInt8: case Squeak.FFITypeUnsignedInt16: case Squeak.FFITypeSignedInt16: case Squeak.FFITypeUnsignedInt32: case Squeak.FFITypeSignedInt32: case Squeak.FFITypeUnsignedInt64: case Squeak.FFITypeSignedInt64: case Squeak.FFITypeUnsignedChar8: case Squeak.FFITypeSignedChar8: case Squeak.FFITypeUnsignedChar16: case Squeak.FFITypeUnsignedChar32: // we ignore the signedness and size of the integer for now if (typeof stObj === "number") return stObj; if (stObj.isTrue) return 1; if (stObj.isFalse) return 0; throw Error("FFI: expected integer, got " + stObj); case Squeak.FFITypeSingleFloat: case Squeak.FFITypeDoubleFloat: if (typeof stObj === "number") return stObj; if (stObj.isFloat) return stObj.float; throw Error("FFI: expected float, got " + stObj); default: throw Error("FFI: unimplemented atomic arg type: " + atomicType); } case Squeak.FFIFlagAtomicPointer: // array of values var atomicType = (typeSpec & Squeak.FFIAtomicTypeMask) >> Squeak.FFIAtomicTypeShift; switch (atomicType) { case Squeak.FFITypeUnsignedChar8: case Squeak.FFITypeUnsignedInt8: if (stObj.bytes) return stObj.bytes; if (stObj.words) return stObj.wordsAsUint8Array(); if (this.interpreterProxy.isWordsOrBytes(stObj)) return new Uint8Array(0); if (stObj.pointers && stObj.pointers[0].jsData) { var data = stObj.pointers[0].jsData; if (data instanceof Uint8Array) return data; if (data instanceof ArrayBuffer) return new Uint8Array(data); } throw Error("FFI: expected bytes, got " + stObj); case Squeak.FFITypeUnsignedInt32: if (stObj.words) return stObj.words; if (this.interpreterProxy.isWords(stObj)) return new Uint32Array(0); if (stObj.pointers && stObj.pointers[0].jsData) { var data = stObj.pointers[0].jsData; if (data instanceof Uint32Array) return data; if (data instanceof ArrayBuffer) return new Uint32Array(data); } throw Error("FFI: expected words, got " + stObj); case Squeak.FFITypeSignedInt32: if (stObj.words) return stObj.wordsAsInt32Array(); if (this.interpreterProxy.isWords(stObj)) return new Int32Array(0); if (stObj.pointers && stObj.pointers[0].jsData) { var data = stObj.pointers[0].jsData; if (data instanceof Int32Array) return data; if (data instanceof ArrayBuffer) return new Int32Array(data); } throw Error("FFI: expected words, got " + stObj); case Squeak.FFITypeSingleFloat: if (stObj.words) return stObj.wordsAsFloat32Array(); if (stObj.isFloat) return new Float32Array([stObj.float]); if (this.interpreterProxy.isWords(stObj)) return new Float32Array(0); if (stObj.pointers && stObj.pointers[0].jsData) { var data = stObj.pointers[0].jsData; if (data instanceof Float32Array) return data; if (data instanceof ArrayBuffer) return new Float32Array(data); } throw Error("FFI: expected floats, got " + stObj); case Squeak.FFITypeDoubleFloat: if (stObj.words) return stObj.wordsAsFloat64Array(); if (stObj.isFloat) return new Float64Array([stObj.float]); if (this.interpreterProxy.isWords(stObj)) return new Float64Array(0); if (stObj.pointers && stObj.pointers[0].jsData) { var data = stObj.pointers[0].jsData; if (data instanceof Float64Array) return data; if (data instanceof ArrayBuffer) return new Float64Array(data); } throw Error("FFI: expected floats, got " + stObj); case Squeak.FFITypeVoid: // void* is passed as buffer if (stObj.words) return stObj.words.buffer; if (stObj.bytes) return stObj.bytes.buffer; if (stObj.isNil || this.interpreterProxy.isWordsOrBytes(stObj)) return new ArrayBuffer(0); // null pointer if (stObj.pointers && stObj.pointers[0].jsData) { var data = stObj.pointers[0].jsData; if (data instanceof ArrayBuffer) return data; } throw Error("FFI: expected words or bytes, got " + stObj); default: throw Error("FFI: unimplemented atomic array arg type: " + atomicType); } default: throw Error("FFI: unimplemented arg type flags: " + typeSpec); } }, ffiResultToSt: function(jsResult, stType) { var typeSpec = stType.pointers[0].words[0]; switch (typeSpec & Squeak.FFIFlagMask) { case Squeak.FFIFlagAtomic: // single value var atomicType = (typeSpec & Squeak.FFIAtomicTypeMask) >> Squeak.FFIAtomicTypeShift; switch (atomicType) { case Squeak.FFITypeVoid: return this.vm.nilObj; case Squeak.FFITypeBool: return jsResult ? this.vm.trueObj : this.vm.falseObj; case Squeak.FFITypeUnsignedInt8: case Squeak.FFITypeSignedInt8: case Squeak.FFITypeUnsignedInt16: case Squeak.FFITypeSignedInt16: case Squeak.FFITypeUnsignedInt32: case Squeak.FFITypeSignedInt32: case Squeak.FFITypeUnsignedInt64: case Squeak.FFITypeSignedInt64: case Squeak.FFITypeUnsignedChar8: case Squeak.FFITypeSignedChar8: case Squeak.FFITypeUnsignedChar16: case Squeak.FFITypeUnsignedChar32: case Squeak.FFITypeSingleFloat: case Squeak.FFITypeDoubleFloat: if (typeof jsResult !== "number") throw Error("FFI: expected number, got " + jsResult); return this.makeStObject(jsResult); default: throw Error("FFI: unimplemented atomic return type: " + atomicType); } case Squeak.FFIFlagAtomicPointer: // array of values if (!jsResult) return this.vm.nilObj; var atomicType = (typeSpec & Squeak.FFIAtomicTypeMask) >> Squeak.FFIAtomicTypeShift; switch (atomicType) { // char* is returned as string case Squeak.FFITypeSignedChar8: case Squeak.FFITypeUnsignedChar8: if (typeof jsResult === "string") return this.makeStString(jsResult); else return this.makeStStringFromBytes(jsResult, true); // all other arrays are returned as ExternalData default: return this.ffiMakeStExternalData(jsResult, stType); } default: throw Error("FFI: unimplemented return type flags: " + typeSpec); } }, ffiNextExtAddr: 0, // fake addresses for ExternalAddress objects ffiMakeStExternalAddress: function() { var extAddr = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassExternalAddress], 4); new (Uint32Array)(extAddr.bytes.buffer)[0] = ++this.ffiNextExtAddr; return extAddr; }, ffiMakeStExternalData: function(jsData, stType) { var extAddr = this.ffiMakeStExternalAddress(); extAddr.jsData = jsData; // save for later var extData = this.vm.instantiateClass(this.vm.specialObjects[Squeak.splOb_ClassExternalData], 0); extData.pointers[0] = extAddr; extData.pointers[1] = stType; return extData; }, ffiDataFromStack: function(arg) { var oop = this.stackNonInteger(arg); if (oop.jsData !== undefined) return oop.jsData; if (oop.bytes) return oop.bytes; if (oop.words) return oop.words; this.vm.warnOnce("FFI: expected ExternalAddress with jsData, got " + oop); this.success = false; }, ffi_primitiveFFIAllocate: function(argCount) { var size = this.stackInteger(0); if (!this.success) return false; var extAddr = this.ffiMakeStExternalAddress(); extAddr.jsData = new ArrayBuffer(size); return this.popNandPushIfOK(argCount + 1, extAddr); }, ffi_primitiveFFIFree: function(argCount) { var extAddr = this.stackNonInteger(0); if (!this.success) return false; if (extAddr.jsData === undefined) { this.vm.warnOnce("primitiveFFIFree: expected ExternalAddress with jsData, got " + extAddr); return false; } delete extAddr.jsData; return true; }, primitiveCalloutToFFI: function(argCount, method) { var extLibFunc = method.pointers[1]; if (!this.isKindOf(extLibFunc, Squeak.splOb_ClassExternalFunction)) return false; var args = []; for (var i = argCount - 1; i >= 0; i--) args.push(this.vm.stackValue(i)); return this.ffiDoCallout(argCount, extLibFunc, args); }, ffi_primitiveCalloutWithArgs: function(argCount) { var extLibFunc = this.stackNonInteger(1), argsObj = this.stackNonInteger(0); if (!this.isKindOf(extLibFunc, Squeak.splOb_ClassExternalFunction)) return false; return this.ffiDoCallout(argCount, extLibFunc, argsObj.pointers); }, ffi_primitiveFFIGetLastError: function(argCount) { return this.popNandPushIfOK(argCount + 1, this.ffi_lastError); }, ffi_primitiveFFIIntegerAt: function(argCount) { var data = this.ffiDataFromStack(3), byteOffset = this.stackInteger(2), byteSize = this.stackInteger(1), isSigned = this.stackBoolean(0); if (!this.success) return false; byteOffset--; // 1-based indexing if (byteOffset < 0 || byteSize < 1 || byteSize > 8 || (byteSize & (byteSize - 1)) !== 0) return false; var result; if (byteSize === 1 && !isSigned) { if (typeof data === "string") { result = data.charCodeAt(byteOffset) || 0; // 0 if out of bounds } else if (data instanceof Uint8Array) { result = data[byteOffset] || 0; // 0 if out of bounds } else { this.vm.warnOnce("FFI: expected string or Uint8Array, got " + typeof data); return false; } } else if (byteSize === 4 && !isSigned) { if (data instanceof Uint32Array) { result = data[byteOffset] || 0; // 0 if out of bounds } else if (data instanceof Uint8Array) { result = new DataView(data.buffer).getUint32(data.byteOffset + byteOffset, true) || 0; // 0 if out of bounds } else { this.vm.warnOnce("FFI: expected Uint32Array, got " + typeof data); return false; } } else { this.vm.warnOnce("FFI: unimplemented integer type size: " + byteSize + " signed: " + isSigned); return false; } return this.popNandPushIfOK(argCount + 1, result); }, ffi_primitiveFFIIntegerAtPut: function(argCount) { var data = this.ffiDataFromStack(4), byteOffset = this.stackInteger(3), value = this.stackSigned53BitInt(2), // good up to int32 / uint32 byteSize = this.stackInteger(1), isSigned = this.stackBoolean(0); if (!this.success) return false; byteOffset--; // 1-based indexing if (byteOffset < 0 || byteSize < 1 || byteSize > 8 || (byteSize & (byteSize - 1)) !== 0) return false; if (byteSize === 4) { if (( isSigned && (value < -2147483648 || value > 0x7FFFFFFF)) || (!isSigned && (value < 0 || value > 0xFFFFFFFF))) return false; if (data instanceof Uint32Array) { data[byteOffset] = value >>> 0; // storage is always unsigned } else if (data instanceof Uint8Array) { new DataView(data.buffer).setUint32(data.byteOffset + byteOffset, value >>> 0, true); } else { this.vm.warnOnce("FFI: expected Uint32Array, got " + typeof data); return false; } } else { this.vm.warnOnce("FFI: unimplemented integer type size: " + byteSize + " signed: " + isSigned); return false; } return this.popNandPushIfOK(argCount + 1, value); }, ffi_primitiveFFIDoubleAtPut: function(argCount) { var data = this.ffiDataFromStack(2), byteOffset = this.stackInteger(1), valueOop = this.vm.stackValue(0), value = valueOop.isFloat ? valueOop.float : valueOop; if (!this.success || typeof value !== "number") return false; byteOffset--; // 1-based indexing if (byteOffset & 7) new DataView(data).setFloat64(byteOffset, value, true); else new Float64Array(data)[byteOffset / 8] = value; return this.popNandPushIfOK(argCount + 1, valueOop); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'JavaScriptPlugin', { js_primitiveDoUnderstand: function(argCount) { // This is JSObjectProxy's doesNotUnderstand handler. // Property name is the selector up to first colon. // If it is 'new', create an instance; // otherwise if the property is a function, call it; // otherwise if the property exists, get/set it; // otherwise, fail. var rcvr = this.stackNonInteger(1), obj = this.js_objectOrGlobal(rcvr), message = this.stackNonInteger(0).pointers, selector = message[0].bytesAsString(), args = message[1].pointers || [], isGlobal = !('jsObject' in rcvr), jsResult = null; try { var propName = selector.match(/([^:]*)/)[0]; if (!isGlobal && propName === "new") { if (args.length === 0) { // new this() jsResult = new obj(); } else { // new this(arg0, arg1, ...) var newArgs = [null].concat(this.js_fromStArray(args)); jsResult = new (Function.prototype.bind.apply(obj, newArgs)); } } else { if (!(propName in obj)) return this.js_setError("Property not found: " + propName); var propValue = obj[propName]; if (typeof propValue == "function" && (!isGlobal || args.length > 0)) { // do this[selector](arg0, arg1, ...) jsResult = propValue.apply(obj, this.js_fromStArray(args)); } else { if (args.length == 0) { // do this[selector] jsResult = propValue; } else if (args.length == 1) { // do this[selector] = arg0 obj[propName] = this.js_fromStObject(args[0]); } else { // cannot do this[selector] = arg0, arg1, ... return this.js_setError("Property " + propName + " is not a function"); } } } } catch(err) { return this.js_setError(err); } var stResult = this.makeStObject(jsResult, rcvr.sqClass); return this.popNandPushIfOK(argCount + 1, stResult); }, js_primitiveAsString: function(argCount) { var obj = this.js_objectOrGlobal(this.stackNonInteger(0)); return this.popNandPushIfOK(argCount + 1, this.makeStString(String(obj))); }, js_primitiveTypeof: function(argCount) { var obj = this.js_objectOrGlobal(this.stackNonInteger(0)); return this.popNandPushIfOK(argCount + 1, this.makeStString(typeof obj)); }, js_primitiveAt: function(argCount) { var rcvr = this.stackNonInteger(1), propName = this.vm.stackValue(0), propValue; try { var jsRcvr = this.js_objectOrGlobal(rcvr), jsPropName = this.js_fromStObject(propName), jsPropValue = jsRcvr[jsPropName]; propValue = this.makeStObject(jsPropValue, rcvr.sqClass); } catch(err) { return this.js_setError(err); } return this.popNandPushIfOK(argCount + 1, propValue); }, js_primitiveAtPut: function(argCount) { var rcvr = this.stackNonInteger(2), propName = this.vm.stackValue(1), propValue = this.vm.stackValue(0); try { var jsRcvr = this.js_objectOrGlobal(rcvr), jsPropName = this.js_fromStObject(propName), jsPropValue = this.js_fromStObject(propValue); jsRcvr[jsPropName] = jsPropValue; } catch(err) { return this.js_setError(err); } return this.popNandPushIfOK(argCount + 1, propValue); }, js_primitiveSqueakAsJSObject: function(argCount) { var rcvr = this.stackNonInteger(1), arg = this.vm.stackValue(0); if (this.success) rcvr.jsObject = arg; return this.popNIfOK(argCount); }, js_primitiveInitCallbacks: function(argCount) { // set callback semaphore for js_fromStBlock() this.js_callbackSema = this.stackInteger(0); this.js_activeCallback = null; return this.popNIfOK(argCount); }, js_primitiveGetActiveCallbackBlock: function(argCount) { // we're inside an active callback, get block var callback = this.js_activeCallback; if (!callback) return this.js_setError("No active callback"); return this.popNandPushIfOK(argCount+1, callback.block); }, js_primitiveGetActiveCallbackArgs: function(argCount) { // we're inside an active callback, get args var proxyClass = this.stackNonInteger(argCount), callback = this.js_activeCallback; if (!callback) return this.js_setError("No active callback"); try { // make array with expected number of arguments for block var array = this.makeStArray(callback.args, proxyClass); return this.popNandPushIfOK(argCount+1, array); } catch(err) { return this.js_setError(err); } }, js_primitiveReturnFromCallback: function(argCount) { if (argCount !== 1) return false; if (!this.js_activeCallback) return this.js_setError("No active callback"); // set result so the interpret loop in js_executeCallback() terminates this.js_activeCallback.result = this.vm.pop(); this.vm.breakOut(); // stop interpreting ASAP return true; }, js_primitiveGetError: function(argCount) { if (this.js_error == null) return false; var msg = this.js_error; if (msg instanceof Error) msg = msg.message; var error = this.makeStString("" + msg); this.js_error = null; return this.popNandPushIfOK(argCount + 1, error); }, js_primitiveGetErrorObject: function(argCount) { if (this.js_error == null) return false; var rcvr = this.stackNonInteger(argCount); var error = this.makeStObject(this.js_error, rcvr.sqClass); this.js_error = null; return this.popNandPushIfOK(argCount + 1, error); }, js_setError: function(err) { this.js_error = err; return false; }, js_fromStObject: function(obj) { if (typeof obj === "number") return obj; if (obj.jsObject) return obj.jsObject; if (obj.isFloat) return obj.float; if (obj.isNil) return null; if (obj.isTrue) return true; if (obj.isFalse) return false; if (obj.bytes || obj.sqClass === this.vm.specialObjects[Squeak.splOb_ClassString]) return obj.bytesAsString(); if (obj.sqClass === this.vm.specialObjects[Squeak.splOb_ClassArray]) return this.js_fromStArray(obj.pointers || [], true); if (obj.sqClass === this.vm.specialObjects[Squeak.splOb_ClassBlockContext] || obj.sqClass === this.vm.specialObjects[Squeak.splOb_ClassBlockClosure]) return this.js_fromStBlock(obj); throw Error("asJSArgument needed for " + obj); // image recognizes error string and will try again }, js_fromStArray: function(objs, maybeDict) { if (objs.length > 0 && maybeDict && objs.every(obj => this.isAssociation(obj))) return this.js_fromStDict(objs); var jsArray = []; for (var i = 0; i < objs.length; i++) jsArray.push(this.js_fromStObject(objs[i])); return jsArray; }, js_fromStDict: function(objs) { var jsDict = {}; for (var i = 0; i < objs.length; i++) { var assoc = objs[i].pointers; if (!assoc || assoc.length !== 2) throw Error(assoc + " is not an Association"); var jsKey = this.js_fromStObject(assoc[0]), jsValue = this.js_fromStObject(assoc[1]); jsDict[jsKey] = jsValue; } return jsDict; }, js_fromStBlock: function(block) { // create callback function from block or closure if (!this.js_callbackSema) // image recognizes error string and will try again throw Error("CallbackSemaphore not set"); // block holds onto thisContext this.vm.reclaimableContextCount = 0; var numArgs = block.pointers[Squeak.BlockContext_argumentCount]; if (typeof numArgs !== 'number') numArgs = block.pointers[Squeak.Closure_numArgs]; var squeak = this; return function evalSqueakBlock(/* arguments */) { var args = []; for (var i = 0; i < numArgs; i++) args.push(arguments[i]); return new Promise(function(resolve, reject) { function evalAsync() { squeak.js_executeCallbackAsync(block, args, resolve, reject); } self.setTimeout(evalAsync, 0); }); } }, js_executeCallbackAsync: function(block, args, resolve, reject) { var squeak = this; function again() {squeak.js_executeCallbackAsync(block, args, resolve, reject);} if (!this.js_activeCallback && !this.vm.frozen) { this.js_executeCallback(block, args, resolve, reject); } else { self.setTimeout(again, 0); } }, js_executeCallback: function(block, args, resolve, reject) { if (this.js_activeCallback) return console.error("Callback: already active"); // make block and args available to primitiveGetActiveCallback this.js_activeCallback = { block: block, args: args, result: null, }; // switch to callback handler process ASAP this.signalSemaphoreWithIndex(this.js_callbackSema); this.vm.forceInterruptCheck(); // interpret until primitiveReturnFromCallback sets result var timeout = Date.now() + 500; while (Date.now() < timeout && !this.js_activeCallback.result) this.vm.interpret(); var result = this.js_activeCallback.result; this.js_activeCallback = null; if (result) { // return result to JS caller as JS object or string try { resolve(this.js_fromStObject(result)); } catch(err) { resolve(result.toString()); } } else { reject(Error("SqueakJS timeout")); } }, js_objectOrGlobal: function(sqObject) { return 'jsObject' in sqObject ? sqObject.jsObject : self; }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'Obsolete', { primitiveFloatArrayAt: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveAt", argCount); }, primitiveFloatArrayMulFloatArray: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveMulFloatArray", argCount); }, primitiveFloatArrayAddScalar: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveAddScalar", argCount); }, primitiveFloatArrayDivFloatArray: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveDivFloatArray", argCount); }, primitiveFloatArrayDivScalar: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveDivScalar", argCount); }, primitiveFloatArrayHash: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveHash", argCount); }, primitiveFloatArrayAtPut: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveAtPut", argCount); }, primitiveFloatArrayMulScalar: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveMulScalar", argCount); }, primitiveFloatArrayAddFloatArray: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveAddFloatArray", argCount); }, primitiveFloatArraySubScalar: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveSubScalar", argCount); }, primitiveFloatArraySubFloatArray: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveSubFloatArray", argCount); }, primitiveFloatArrayEqual: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveEqual", argCount); }, primitiveFloatArrayDotProduct: function(argCount) { return this.namedPrimitive("FloatArrayPlugin", "primitiveDotProduct", argCount); }, m23PrimitiveInvertRectInto: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveInvertRectInto", argCount); }, m23PrimitiveTransformPoint: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveTransformPoint", argCount); }, m23PrimitiveIsPureTranslation: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveIsPureTranslation", argCount); }, m23PrimitiveComposeMatrix: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveComposeMatrix", argCount); }, m23PrimitiveTransformRectInto: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveTransformRectInto", argCount); }, m23PrimitiveIsIdentity: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveIsIdentity", argCount); }, m23PrimitiveInvertPoint: function(argCount) { return this.namedPrimitive("Matrix2x3Plugin", "primitiveInvertPoint", argCount); }, primitiveDeflateBlock: function(argCount) { return this.namedPrimitive("ZipPlugin", "primitiveDeflateBlock", argCount); }, primitiveDeflateUpdateHashTable: function(argCount) { return this.namedPrimitive("ZipPlugin", "primitiveDeflateUpdateHashTable", argCount); }, primitiveUpdateGZipCrc32: function(argCount) { return this.namedPrimitive("ZipPlugin", "primitiveUpdateGZipCrc32", argCount); }, primitiveInflateDecompressBlock: function(argCount) { return this.namedPrimitive("ZipPlugin", "primitiveInflateDecompressBlock", argCount); }, primitiveZipSendBlock: function(argCount) { return this.namedPrimitive("ZipPlugin", "primitiveZipSendBlock", argCount); }, primitiveFFTTransformData: function(argCount) { return this.namedPrimitive("FFTPlugin", "primitiveFFTTransformData", argCount); }, primitiveFFTScaleData: function(argCount) { return this.namedPrimitive("FFTPlugin", "primitiveFFTScaleData", argCount); }, primitiveFFTPermuteData: function(argCount) { return this.namedPrimitive("FFTPlugin", "primitiveFFTPermuteData", argCount); }, gePrimitiveMergeFillFrom: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveMergeFillFrom", argCount); }, gePrimitiveCopyBuffer: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveCopyBuffer", argCount); }, gePrimitiveAddRect: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddRect", argCount); }, gePrimitiveAddGradientFill: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddGradientFill", argCount); }, gePrimitiveSetClipRect: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetClipRect", argCount); }, gePrimitiveSetBitBltPlugin: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetBitBltPlugin", argCount); }, gePrimitiveRegisterExternalEdge: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveRegisterExternalEdge", argCount); }, gePrimitiveGetClipRect: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetClipRect", argCount); }, gePrimitiveAddBezier: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddBezier", argCount); }, gePrimitiveInitializeProcessing: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveInitializeProcessing", argCount); }, gePrimitiveRenderImage: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveRenderImage", argCount); }, gePrimitiveGetOffset: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetOffset", argCount); }, gePrimitiveSetDepth: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetDepth", argCount); }, gePrimitiveAddBezierShape: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddBezierShape", argCount); }, gePrimitiveSetEdgeTransform: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetEdgeTransform", argCount); }, gePrimitiveGetTimes: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetTimes", argCount); }, gePrimitiveNextActiveEdgeEntry: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveNextActiveEdgeEntry", argCount); }, gePrimitiveAddBitmapFill: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddBitmapFill", argCount); }, gePrimitiveGetDepth: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetDepth", argCount); }, gePrimitiveAbortProcessing: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAbortProcessing", argCount); }, gePrimitiveNextGlobalEdgeEntry: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveNextGlobalEdgeEntry", argCount); }, gePrimitiveGetFailureReason: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetFailureReason", argCount); }, gePrimitiveDisplaySpanBuffer: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveDisplaySpanBuffer", argCount); }, gePrimitiveGetCounts: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetCounts", argCount); }, gePrimitiveChangedActiveEdgeEntry: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveChangedActiveEdgeEntry", argCount); }, gePrimitiveRenderScanline: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveRenderScanline", argCount); }, gePrimitiveGetBezierStats: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetBezierStats", argCount); }, gePrimitiveFinishedProcessing: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveFinishedProcessing", argCount); }, gePrimitiveNeedsFlush: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveNeedsFlush", argCount); }, gePrimitiveAddLine: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddLine", argCount); }, gePrimitiveSetOffset: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetOffset", argCount); }, gePrimitiveNextFillEntry: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveNextFillEntry", argCount); }, gePrimitiveInitializeBuffer: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveInitializeBuffer", argCount); }, gePrimitiveDoProfileStats: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveDoProfileStats", argCount); }, gePrimitiveAddActiveEdgeEntry: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddActiveEdgeEntry", argCount); }, gePrimitiveSetAALevel: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetAALevel", argCount); }, gePrimitiveNeedsFlushPut: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveNeedsFlushPut", argCount); }, gePrimitiveAddCompressedShape: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddCompressedShape", argCount); }, gePrimitiveSetColorTransform: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveSetColorTransform", argCount); }, gePrimitiveAddOval: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddOval", argCount); }, gePrimitiveRegisterExternalFill: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveRegisterExternalFill", argCount); }, gePrimitiveAddPolygon: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveAddPolygon", argCount); }, gePrimitiveGetAALevel: function(argCount) { return this.namedPrimitive("B2DPlugin", "primitiveGetAALevel", argCount); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'DropPlugin', { primitiveDropRequestFileHandle: function(argCount) { var index = this.stackInteger(0), fileNames = this.display.droppedFiles || []; if (index < 1 || index > fileNames.length) return false; // same code as primitiveFileOpen() var fileName = fileNames[index - 1], file = this.fileOpen(fileName, false); if (!file) return false; var handle = this.makeFileHandle(fileName, file, false); this.popNandPushIfOK(argCount+1, handle); return true; }, primitiveDropRequestFileName: function(argCount) { var index = this.stackInteger(0), fileNames = this.display.droppedFiles || []; if (index < 1 || index > fileNames.length) return false; var result = this.makeStString(this.filenameToSqueak(fileNames[index - 1])); return this.popNandPushIfOK(argCount+1, result); }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'FilePlugin', { primitiveDirectoryCreate: function(argCount) { var dirNameObj = this.stackNonInteger(0); if (!this.success) return false; var dirName = this.filenameFromSqueak(dirNameObj.bytesAsString()); this.success = Squeak.dirCreate(dirName); if (!this.success) { var path = Squeak.splitFilePath(dirName); if (Squeak.debugFiles) console.warn("Directory not created: " + path.fullname); } return this.popNIfOK(argCount); }, primitiveDirectoryDelete: function(argCount) { var dirNameObj = this.stackNonInteger(0); if (!this.success) return false; var dirName = this.filenameFromSqueak(dirNameObj.bytesAsString()); this.success = Squeak.dirDelete(dirName); return this.popNIfOK(argCount); }, primitiveDirectoryDelimitor: function(argCount) { var delimitor = this.emulateMac ? ':' : '/'; return this.popNandPushIfOK(1, this.charFromInt(delimitor.charCodeAt(0))); }, primitiveDirectoryEntry: function(argCount) { var dirNameObj = this.stackNonInteger(1), fileNameObj = this.stackNonInteger(0); if (!this.success) return false; var sqFileName = fileNameObj.bytesAsString(); var fileName = this.filenameFromSqueak(sqFileName); var sqDirName = dirNameObj.bytesAsString(); var dirName = this.filenameFromSqueak(sqDirName); var entries = Squeak.dirList(dirName, true); if (!entries) { var path = Squeak.splitFilePath(dirName); if (Squeak.debugFiles) console.log("Directory not found: " + path.fullname); return false; } var entry = fileName === "." ? [".", 0, 0, true, 0] // current directory : fileName === ".." ? ["..", 0, 0, true, 0] // parent directory : fileName === "/" && dirName === "/" ? [sqFileName, 0, 0, true, 0] // fake top-level dir : entries[fileName]; this.popNandPushIfOK(argCount+1, this.makeStObject(entry)); // entry or nil return true; }, primitiveDirectoryLookup: function(argCount) { var index = this.stackInteger(0), dirNameObj = this.stackNonInteger(1); if (!this.success) return false; var sqDirName = dirNameObj.bytesAsString(); var dirName = this.filenameFromSqueak(sqDirName); var entries = Squeak.dirList(dirName, true); if (!entries) { var path = Squeak.splitFilePath(dirName); if (Squeak.debugFiles) console.log("Directory not found: " + path.fullname); return false; } if (Squeak.debugFiles && index === 1) { console.log("Reading directory " + dirName + " with " + Object.keys(entries).length + " entries"); } var keys = Object.keys(entries).sort(), entry = entries[keys[index - 1]]; if (sqDirName === "/") { // fake top-level dir if (index === 1) { if (!entry) entry = [0, 0, 0, 0, 0]; entry[0] = "SqueakJS"; entry[3] = true; } else entry = null; } this.popNandPushIfOK(argCount+1, this.makeStObject(entry)); // entry or nil return true; }, primitiveDirectorySetMacTypeAndCreator: function(argCount) { return this.popNIfOK(argCount); }, primitiveFileAtEnd: function(argCount) { var handle = this.stackNonInteger(0); if (!this.success || !handle.file) return false; this.popNandPushIfOK(argCount+1, this.makeStObject(handle.filePos >= handle.file.size)); return true; }, primitiveFileClose: function(argCount) { var handle = this.stackNonInteger(0); if (!this.success || !handle.file) return false; if (typeof handle.file === "string") { this.fileConsoleFlush(handle.file); } else { this.fileClose(handle.file); this.vm.breakOut(); // return to JS asap so async file handler can run handle.file = null; } return this.popNIfOK(argCount); }, primitiveFileDelete: function(argCount) { var fileNameObj = this.stackNonInteger(0); if (!this.success) return false; var fileName = this.filenameFromSqueak(fileNameObj.bytesAsString()); this.success = Squeak.fileDelete(fileName); return this.popNIfOK(argCount); }, primitiveFileFlush: function(argCount) { var handle = this.stackNonInteger(0); if (!this.success || !handle.file) return false; if (typeof handle.file === "string") { this.fileConsoleFlush(handle.file); } else { Squeak.flushFile(handle.file); this.vm.breakOut(); // return to JS asap so async file handler can run } return this.popNIfOK(argCount); }, primitiveFileGetPosition: function(argCount) { var handle = this.stackNonInteger(0); if (!this.success || !handle.file) return false; this.popNandPushIfOK(argCount + 1, this.makeLargeIfNeeded(handle.filePos)); return true; }, makeFileHandle: function(filename, file, writeFlag) { var handle = this.makeStString("squeakjs:" + filename); handle.file = file; // shared between handles handle.fileWrite = writeFlag; // specific to this handle handle.filePos = 0; // specific to this handle return handle; }, primitiveFileOpen: function(argCount) { var writeFlag = this.stackBoolean(0), fileNameObj = this.stackNonInteger(1); if (!this.success) return false; var fileName = this.filenameFromSqueak(fileNameObj.bytesAsString()), file = this.fileOpen(fileName, writeFlag); if (!file) return false; var handle = this.makeFileHandle(file.name, file, writeFlag); this.popNandPushIfOK(argCount+1, handle); return true; }, primitiveFileRead: function(argCount) { var count = this.stackInteger(0), startIndex = this.stackInteger(1) - 1, // make zero based arrayObj = this.stackNonInteger(2), handle = this.stackNonInteger(3); if (!this.success || !arrayObj.isWordsOrBytes() || !handle.file) return false; if (!count) return this.popNandPushIfOK(argCount+1, 0); var array = arrayObj.bytes; if (!array) { array = arrayObj.wordsAsUint8Array(); startIndex *= 4; count *= 4; } if (startIndex < 0 || startIndex + count > array.length) return false; if (typeof handle.file === "string") { //this.fileConsoleRead(handle.file, array, startIndex, count); this.popNandPushIfOK(argCount+1, 0); return true; } return this.fileContentsDo(handle.file, function(file) { if (!file.contents) return this.popNandPushIfOK(argCount+1, 0); var srcArray = file.contents, dstArray = array; count = Math.min(count, file.size - handle.filePos); for (var i = 0; i < count; i++) dstArray[startIndex + i] = srcArray[handle.filePos++]; if (!arrayObj.bytes) count >>= 2; // words this.popNandPushIfOK(argCount+1, Math.max(0, count)); }.bind(this)); }, primitiveFileRename: function(argCount) { var oldNameObj = this.stackNonInteger(1), newNameObj = this.stackNonInteger(0); if (!this.success) return false; var oldName = this.filenameFromSqueak(oldNameObj.bytesAsString()), newName = this.filenameFromSqueak(newNameObj.bytesAsString()); this.success = Squeak.fileRename(oldName, newName); this.vm.breakOut(); // return to JS asap so async file handler can run return this.popNIfOK(argCount); }, primitiveFileSetPosition: function(argCount) { var pos = this.stackPos32BitInt(0), handle = this.stackNonInteger(1); if (!this.success || !handle.file) return false; handle.filePos = pos; return this.popNIfOK(argCount); }, primitiveFileSize: function(argCount) { var handle = this.stackNonInteger(0); if (!this.success || !handle.file) return false; this.popNandPushIfOK(argCount+1, this.makeLargeIfNeeded(handle.file.size)); return true; }, primitiveFileStdioHandles: function(argCount) { var handles = [ null, // stdin this.makeFileHandle('console.log', 'log', true), this.makeFileHandle('console.error', 'error', true), ]; this.popNandPushIfOK(argCount + 1, this.makeStArray(handles)); return true; }, primitiveFileTruncate: function(argCount) { var pos = this.stackPos32BitInt(0), handle = this.stackNonInteger(1); if (!this.success || !handle.file || !handle.fileWrite) return false; if (handle.file.size > pos) { handle.file.size = pos; handle.file.modified = true; if (handle.filePos > handle.file.size) handle.filePos = handle.file.size; } return this.popNIfOK(argCount); }, primitiveDisableFileAccess: function(argCount) { return this.fakePrimitive("FilePlugin.primitiveDisableFileAccess", 0, argCount); }, primitiveFileWrite: function(argCount) { var count = this.stackInteger(0), startIndex = this.stackInteger(1) - 1, // make zero based arrayObj = this.stackNonInteger(2), handle = this.stackNonInteger(3); if (!this.success || !handle.file || !handle.fileWrite) return false; if (!count) return this.popNandPushIfOK(argCount+1, 0); var array = arrayObj.bytes; if (!array) { array = arrayObj.wordsAsUint8Array(); startIndex *= 4; count *= 4; } if (!array) return false; if (startIndex < 0 || startIndex + count > array.length) return false; if (typeof handle.file === "string") { this.fileConsoleWrite(handle.file, array, startIndex, count); this.popNandPushIfOK(argCount+1, count); return true; } return this.fileContentsDo(handle.file, function(file) { var srcArray = array, dstArray = file.contents || []; if (handle.filePos + count > dstArray.length) { var newSize = dstArray.length === 0 ? handle.filePos + count : Math.max(handle.filePos + count, dstArray.length + 10000); file.contents = new Uint8Array(newSize); file.contents.set(dstArray); dstArray = file.contents; } for (var i = 0; i < count; i++) dstArray[handle.filePos++] = srcArray[startIndex + i]; if (handle.filePos > file.size) file.size = handle.filePos; file.modified = true; if (!arrayObj.bytes) count >>= 2; // words this.popNandPushIfOK(argCount+1, count); }.bind(this)); }, fileOpen: function(filename, writeFlag) { // if a file is opened for read and write at the same time, // they must share the contents. That's why all open files // are held in the ref-counted global SqueakFiles if (typeof SqueakFiles == 'undefined') window.SqueakFiles = {}; var path = Squeak.splitFilePath(filename); if (!path.basename) return null; // malformed filename // fetch or create directory entry var directory = Squeak.dirList(path.dirname, true); if (!directory) return null; var entry = directory[path.basename], contents = null; if (entry) { // if it is open already, return it var file = SqueakFiles[path.fullname]; if (file) { ++file.refCount; return file; } } else { if (!writeFlag) { if (Squeak.debugFiles) console.log("File not found: " + path.fullname); return null; } contents = new Uint8Array(); entry = Squeak.filePut(path.fullname, contents.buffer); if (!entry) { if (Squeak.debugFiles) console.log("Cannot create file: " + path.fullname); return null; } } // make the file object var file = { name: path.fullname, size: entry[4], // actual file size, may differ from contents.length contents: contents, // possibly null, fetched when needed modified: false, refCount: 1 }; SqueakFiles[file.name] = file; return file; }, fileClose: function(file) { Squeak.flushFile(file); if (--file.refCount == 0) delete SqueakFiles[file.name]; }, fileContentsDo: function(file, func) { if (file.contents) { func(file); } else { if (file.contents === false) // failed to get contents before return false; this.vm.freeze(function(unfreeze) { var error = (function(msg) { console.warn("File get failed: " + msg); file.contents = false; unfreeze(); func(file); }).bind(this), success = (function(contents) { if (contents == null) return error(file.name); file.contents = this.asUint8Array(contents); unfreeze(); func(file); }).bind(this); Squeak.fileGet(file.name, success, error); }.bind(this)); } return true; }, fileConsoleBuffer: { log: '', error: '' }, fileConsoleWrite: function(logOrError, array, startIndex, count) { // buffer until there is a newline var bytes = array.subarray(startIndex, startIndex + count), buffer = this.fileConsoleBuffer[logOrError] + Squeak.bytesAsString(bytes), lines = buffer.match('([^]*)\n(.*)'); if (lines) { console[logOrError](lines[1]); // up to last newline buffer = lines[2]; // after last newline } this.fileConsoleBuffer[logOrError] = buffer; }, fileConsoleFlush: function(logOrError) { var buffer = this.fileConsoleBuffer[logOrError]; if (buffer) { console[logOrError](buffer); this.fileConsoleBuffer[logOrError] = ''; } }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'JPEGReadWriter2Plugin', { jpeg2_primJPEGPluginIsPresent: function(argCount) { return this.popNandPushIfOK(argCount + 1, this.vm.trueObj); }, jpeg2_primImageHeight: function(argCount) { var decompressStruct = this.stackNonInteger(0).wordsOrBytes(); if (!decompressStruct) return false; var height = decompressStruct[1]; return this.popNandPushIfOK(argCount + 1, height); }, jpeg2_primImageWidth: function(argCount) { var decompressStruct = this.stackNonInteger(0).wordsOrBytes(); if (!decompressStruct) return false; var width = decompressStruct[0]; return this.popNandPushIfOK(argCount + 1, width); }, jpeg2_primJPEGCompressStructSize: function(argCount) { // no struct needed return this.popNandPushIfOK(argCount + 1, 0); }, jpeg2_primJPEGDecompressStructSize: function(argCount) { // width, height, 32 bits each return this.popNandPushIfOK(argCount + 1, 8); }, jpeg2_primJPEGErrorMgr2StructSize: function(argCount) { // no struct needed return this.popNandPushIfOK(argCount + 1, 0); }, jpeg2_primJPEGReadHeaderfromByteArrayerrorMgr: function(argCount) { var decompressStruct = this.stackNonInteger(2).wordsOrBytes(), source = this.stackNonInteger(1).bytes; if (!decompressStruct || !source) return false; var unfreeze = this.vm.freeze(); this.jpeg2_readImageFromBytes(source, function success(image) { this.jpeg2state = {src: source, img: image}; decompressStruct[0] = image.width; decompressStruct[1] = image.height; unfreeze(); }.bind(this), function error() { decompressStruct[0] = 0; decompressStruct[1] = 0; unfreeze(); }.bind(this)); return this.popNIfOK(argCount); }, jpeg2_primJPEGReadImagefromByteArrayonFormdoDitheringerrorMgr: function(argCount) { var source = this.stackNonInteger(3).bytes, form = this.stackNonInteger(2).pointers, ditherFlag = this.stackBoolean(1); if (!this.success || !source || !form) return false; var state = this.jpeg2state; if (!state || state.src !== source) { console.error("jpeg read did not match header info"); return false; } var depth = form[Squeak.Form_depth], image = this.jpeg2_getPixelsFromImage(state.img), formBits = form[Squeak.Form_bits].words; if (depth === 32) { this.jpeg2_copyPixelsToForm32(image, formBits); } else if (depth === 16) { if (ditherFlag) this.jpeg2_ditherPixelsToForm16(image, formBits); else this.jpeg2_copyPixelsToForm16(image, formBits); } else return false; return this.popNIfOK(argCount); }, jpeg2_primJPEGWriteImageonByteArrayformqualityprogressiveJPEGerrorMgr: function(argCount) { if (argCount < 6) return false; var destination = this.stackNonInteger(4).bytes, form = this.stackNonInteger(3).pointers, quality = this.stackInteger(2); // rest ignored if (!this.success || !destination || !form) return false; var formWidth = form[Squeak.Form_width], formHeight = form[Squeak.Form_height], formDepth = form[Squeak.Form_depth], formBits = form[Squeak.Form_bits].words; if (formDepth !== 32) { this.vm.warnOnce("JPEG2WriteImage: only 32 bit depth supported"); return false; } var bytesCount = this.jpeg2_writeFormToBytes(formBits, formWidth, formHeight, quality, destination); return this.popNandPushIfOK(argCount + 1, bytesCount); }, jpeg2_writeFormToBytes: function(formBits, width, height, quality, destination) { var canvas = document.createElement("canvas"), context = canvas.getContext("2d"); canvas.width = width; canvas.height = height; var imageData = context.createImageData(width, height), pixels = imageData.data; for (var i = 0; i < formBits.length; i++) { var pix = formBits[i]; pixels[i*4 + 0] = (pix >> 16) & 255; pixels[i*4 + 1] = (pix >> 8) & 255; pixels[i*4 + 2] = pix & 255; pixels[i*4 + 3] = 255; } context.putImageData(imageData, 0, 0); var jpeg = canvas.toDataURL("image/jpeg", quality / 100); return this.jpeg2_dataURLToBytes(jpeg, destination); }, jpeg2_dataURLToBytes: function(dataURL, destination) { var base64 = dataURL.split(',')[1]; if (!base64) return 0; var needed = base64.length * 3 / 4; if (needed - 3 > destination.length) return 0; var bytes = atob(base64); if (bytes.length > destination.length) return 0; for (var i = 0; i < bytes.length; i++) destination[i] = bytes.charCodeAt(i); return bytes.length; }, jpeg2_readImageFromBytes: function(bytes, thenDo, errorDo) { var blob = new Blob([bytes], {type: "image/jpeg"}), image = new Image(); image.onload = function() { thenDo(image); URL.revokeObjectURL(image.src); }; image.onerror = function() { console.warn("could not render JPEG"); errorDo(); URL.revokeObjectURL(image.src); }; image.src = URL.createObjectURL(blob); }, jpeg2_getPixelsFromImage: function(image) { var canvas = document.createElement("canvas"), context = canvas.getContext("2d"); canvas.width = image.width; canvas.height = image.height; context.drawImage(image, 0, 0); return context.getImageData(0, 0, image.width, image.height); }, jpeg2_copyPixelsToForm32: function(image, formBits) { var pixels = image.data; for (var i = 0; i < formBits.length; i++) { var r = pixels[i*4 + 0], g = pixels[i*4 + 1], b = pixels[i*4 + 2]; formBits[i] = 0xFF000000 | (r << 16) | (g << 8) | b; } }, jpeg2_copyPixelsToForm16: function(image, formBits) { var width = image.width, height = image.height, pixels = image.data; for (var y = 0; y < height; y++) for (var x = 0; x < width; x += 2) { var i = y * height + x, r1 = pixels[i*4 + 0] >> 3, g1 = pixels[i*4 + 1] >> 3, b1 = pixels[i*4 + 2] >> 3, r2 = pixels[i*4 + 4] >> 3, g2 = pixels[i*4 + 5] >> 3, b2 = pixels[i*4 + 6] >> 3, formPix = (r1 << 10) | (g1 << 5) | b1; if (formPix === 0) formPix = 1; formPix = (formPix << 16) | (r2 << 10) | (g2 << 5) | b2; if ((formPix & 65535) === 0) formPix = formPix | 1; formBits[i >> 1] = formPix; } }, jpeg2_ditherPixelsToForm16: function(image, formBits) { var width = image.width >> 1, // 2 pix a time height = image.height, pixels = image.data, ditherMatrix1 = [2, 0, 14, 12, 1, 3, 13, 15], ditherMatrix2 = [10, 8, 6, 4, 9, 11, 5, 7]; for (var y = 0; y < height; y++) for (var x = 0; x < width; x++) { var i = (y * height + 2 * x) << 2, r1 = pixels[i + 0], g1 = pixels[i + 1], b1 = pixels[i + 2], r2 = pixels[i + 4], g2 = pixels[i + 5], b2 = pixels[i + 6]; /* Do 4x4 ordered dithering. Taken from Form>>orderedDither32To16 */ var v = ((y & 3) << 1) | (x & 1), dmv1 = ditherMatrix1[v], dmv2 = ditherMatrix2[v], di, dmi, dmo; di = (r1 * 496) >> 8, dmi = di & 15, dmo = di >> 4; if (dmv1 < dmi) { r1 = dmo+1; } else { r1 = dmo; } di = (g1 * 496) >> 8; dmi = di & 15; dmo = di >> 4; if (dmv1 < dmi) { g1 = dmo+1; } else { g1 = dmo; } di = (b1 * 496) >> 8; dmi = di & 15; dmo = di >> 4; if (dmv1 < dmi) { b1 = dmo+1; } else { b1 = dmo; } di = (r2 * 496) >> 8; dmi = di & 15; dmo = di >> 4; if (dmv2 < dmi) { r2 = dmo+1; } else { r2 = dmo; } di = (g2 * 496) >> 8; dmi = di & 15; dmo = di >> 4; if (dmv2 < dmi) { g2 = dmo+1; } else { g2 = dmo; } di = (b2 * 496) >> 8; dmi = di & 15; dmo = di >> 4; if (dmv2 < dmi) { b2 = dmo+1; } else { b2 = dmo; } var formPix = (r1 << 10) | (g1 << 5) | b1; if (formPix === 0) formPix = 1; formPix = (formPix << 16) | (r2 << 10) | (g2 << 5) | b2; if ((formPix & 65535) === 0) formPix = formPix | 1; formBits[i >> 3] = formPix; } }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'ScratchPluginAdditions', { // methods not handled by generated ScratchPlugin scratch_primitiveOpenURL: function(argCount) { var url = this.stackNonInteger(0).bytesAsString(); if (url == "") return false; if (/^\/SqueakJS\//.test(url)) { url = url.slice(10); // remove file root var path = Squeak.splitFilePath(url), template = Squeak.Settings["squeak-template:" + path.dirname]; if (template) url = JSON.parse(template).url + "/" + path.basename; } window.open(url, "_blank"); // likely blocked as pop-up, but what can we do? return this.popNIfOK(argCount); }, scratch_primitiveGetFolderPath: function(argCount) { var index = this.stackInteger(0); if (!this.success) return false; var path; switch (index) { case 1: path = '/'; break; // home dir // case 2: path = '/desktop'; break; // desktop // case 3: path = '/documents'; break; // documents // case 4: path = '/pictures'; break; // my pictures // case 5: path = '/music'; break; // my music } if (!path) return false; this.vm.popNandPush(argCount + 1, this.makeStString(this.filenameToSqueak(path))); return true; }, }); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak.Primitives.prototype, 'SoundPlugin', { snd_primitiveSoundStart: function(argCount) { return this.snd_primitiveSoundStartWithSemaphore(argCount); }, snd_primitiveSoundStartWithSemaphore: function(argCount) { var bufFrames = this.stackInteger(argCount-1), samplesPerSec = this.stackInteger(argCount-2), stereoFlag = this.stackBoolean(argCount-3), semaIndex = argCount > 3 ? this.stackInteger(argCount-4) : 0; if (!this.success) return false; this.audioContext = Squeak.startAudioOut(); if (!this.audioContext) { this.vm.warnOnce("could not initialize audio"); return false; } this.audioSema = semaIndex; // signal when ready to accept another buffer of samples this.audioNextTimeSlot = 0; this.audioBuffersReady = []; this.audioBuffersUnused = [ this.audioContext.createBuffer(stereoFlag ? 2 : 1, bufFrames, samplesPerSec), this.audioContext.createBuffer(stereoFlag ? 2 : 1, bufFrames, samplesPerSec), ]; // console.log("sound: started"); return this.popNIfOK(argCount); }, snd_playNextBuffer: function() { if (!this.audioContext || this.audioBuffersReady.length === 0) return; var source = this.audioContext.createBufferSource(); source.buffer = this.audioBuffersReady.shift(); source.connect(this.audioContext.destination); if (this.audioNextTimeSlot < this.audioContext.currentTime) { // if (this.audioNextTimeSlot > 0) // console.log("sound " + this.audioContext.currentTime.toFixed(3) + // ": buffer underrun by " + (this.audioContext.currentTime - this.audioNextTimeSlot).toFixed(3) + " s"); this.audioNextTimeSlot = this.audioContext.currentTime; } source.start(this.audioNextTimeSlot); //console.log("sound " + this.audioContext.currentTime.toFixed(3) + // ": scheduling from " + this.audioNextTimeSlot.toFixed(3) + // " to " + (this.audioNextTimeSlot + source.buffer.duration).toFixed(3)); this.audioNextTimeSlot += source.buffer.duration; // source.onended is unreliable, using a timeout instead window.setTimeout(function() { // if the vm was shut down, forceInterruptCheck will be null if (!this.audioContext || !this.vm.forceInterruptCheck) return; // console.log("sound " + this.audioContext.currentTime.toFixed(3) + // ": done, next time slot " + this.audioNextTimeSlot.toFixed(3)); this.audioBuffersUnused.push(source.buffer); if (this.audioSema) this.signalSemaphoreWithIndex(this.audioSema); this.vm.forceInterruptCheck(); }.bind(this), (this.audioNextTimeSlot - this.audioContext.currentTime) * 1000); this.snd_playNextBuffer(); }, snd_primitiveSoundAvailableSpace: function(argCount) { if (!this.audioContext) { console.warn("sound: no audio context"); return false; } var available = 0; if (this.audioBuffersUnused.length > 0) { var buf = this.audioBuffersUnused[0]; available = buf.length * buf.numberOfChannels * 2; } return this.popNandPushIfOK(argCount + 1, available); }, snd_primitiveSoundPlaySamples: function(argCount) { if (!this.audioContext || this.audioBuffersUnused.length === 0) { console.warn("sound: play but no free buffers"); return false; } var count = this.stackInteger(2), sqSamples = this.stackNonInteger(1).wordsAsInt16Array(), startIndex = this.stackInteger(0) - 1; if (!this.success || !sqSamples) return false; var buffer = this.audioBuffersUnused.shift(), channels = buffer.numberOfChannels; for (var channel = 0; channel < channels; channel++) { var jsSamples = buffer.getChannelData(channel), index = startIndex + channel; for (var i = 0; i < count; i++) { jsSamples[i] = sqSamples[index] / 32768; // int16 -> float32 index += channels; } } this.audioBuffersReady.push(buffer); this.snd_playNextBuffer(); return this.popNIfOK(argCount); }, snd_primitiveSoundPlaySilence: function(argCount) { if (!this.audioContext || this.audioBuffersUnused.length === 0) { console.warn("sound: play but no free buffers"); return false; } var buffer = this.audioBuffersUnused.shift(), channels = buffer.numberOfChannels, count = buffer.length; for (var channel = 0; channel < channels; channel++) { var jsSamples = buffer.getChannelData(channel); for (var i = 0; i < count; i++) jsSamples[i] = 0; } this.audioBuffersReady.push(buffer); this.snd_playNextBuffer(); return this.popNandPushIfOK(argCount + 1, count); }, snd_primitiveSoundStop: function(argCount) { if (this.audioContext) { this.audioContext = null; this.audioBuffersReady = null; this.audioBuffersUnused = null; this.audioNextTimeSlot = 0; this.audioSema = 0; // console.log("sound: stopped"); } return this.popNIfOK(argCount); }, snd_primitiveSoundStartRecording: function(argCount) { if (argCount !== 3) return false; var rcvr = this.stackNonInteger(3), samplesPerSec = this.stackInteger(2), stereoFlag = this.stackBoolean(1), semaIndex = this.stackInteger(0); if (!this.success) return false; var method = this.primMethod, unfreeze = this.vm.freeze(), self = this; Squeak.startAudioIn( function onSuccess(audioContext, source) { // console.log("sound: recording started") self.audioInContext = audioContext; self.audioInSource = source; self.audioInSema = semaIndex; self.audioInBuffers = []; self.audioInBufferIndex = 0; self.audioInOverSample = 1; // if sample rate is still too high, adjust oversampling while (samplesPerSec * self.audioInOverSample < self.audioInContext.sampleRate) self.audioInOverSample *= 2; // make a buffer of at least 100 ms var bufferSize = self.audioInOverSample * 1024; while (bufferSize / self.audioInContext.sampleRate < 0.1) bufferSize *= 2; self.audioInProcessor = audioContext.createScriptProcessor(bufferSize, stereoFlag ? 2 : 1, stereoFlag ? 2 : 1); self.audioInProcessor.onaudioprocess = function(event) { self.snd_recordNextBuffer(event.inputBuffer); }; self.audioInSource.connect(self.audioInProcessor); self.audioInProcessor.connect(audioContext.destination); self.vm.popN(argCount); window.setTimeout(unfreeze, 0); }, function onError(msg) { console.warn(msg); self.vm.sendAsPrimitiveFailure(rcvr, method, argCount); window.setTimeout(unfreeze, 0); }); return true; }, snd_recordNextBuffer: function(audioBuffer) { if (!this.audioInContext) return; // console.log("sound " + this.audioInContext.currentTime.toFixed(3) + // ": recorded " + audioBuffer.duration.toFixed(3) + " s"); if (this.audioInBuffers.length > 5) this.audioInBuffers.shift(); this.audioInBuffers.push(audioBuffer); if (this.audioInSema) this.signalSemaphoreWithIndex(this.audioInSema); this.vm.forceInterruptCheck(); }, snd_primitiveSoundGetRecordingSampleRate: function(argCount) { if (!this.audioInContext) return false; var actualRate = this.audioInContext.sampleRate / this.audioInOverSample | 0; // console.log("sound: actual recording rate " + actualRate + "x" + this.audioInOverSample); return this.popNandPushIfOK(argCount + 1, actualRate); }, snd_primitiveSoundRecordSamples: function(argCount) { var sqSamples = this.stackNonInteger(1).wordsAsInt16Array(), sqStartIndex = this.stackInteger(0) - 1; if (!this.success) return false; var sampleCount = 0; while (sqStartIndex < sqSamples.length) { if (this.audioInBuffers.length === 0) break; var buffer = this.audioInBuffers[0], channels = buffer.numberOfChannels, sqStep = channels, jsStep = this.audioInOverSample, sqCount = (sqSamples.length - sqStartIndex) / sqStep, jsCount = (buffer.length - this.audioInBufferIndex) / jsStep, count = Math.min(jsCount, sqCount); for (var channel = 0; channel < channels; channel++) { var jsSamples = buffer.getChannelData(channel), jsIndex = this.audioInBufferIndex, sqIndex = sqStartIndex + channel; for (var i = 0; i < count; i++) { sqSamples[sqIndex] = jsSamples[jsIndex] * 32768 & 0xFFFF; // float32 -> int16 sqIndex += sqStep; jsIndex += jsStep; } } sampleCount += count * channels; sqStartIndex += count * channels; if (jsIndex < buffer.length) { this.audioInBufferIndex = jsIndex; } else { this.audioInBufferIndex = 0; this.audioInBuffers.shift(); } } return this.popNandPushIfOK(argCount + 1, sampleCount); }, snd_primitiveSoundStopRecording: function(argCount) { if (this.audioInContext) { this.audioInSource.disconnect(); this.audioInProcessor.disconnect(); this.audioInContext = null; this.audioInSema = 0; this.audioInBuffers = null; this.audioInSource = null; this.audioInProcessor = null; console.log("sound recording stopped"); } Squeak.stopAudioIn(); return this.popNIfOK(argCount); }, snd_primitiveSoundSetRecordLevel: function(argCount) { this.vm.warnOnce("sound set record level not supported"); return this.popNIfOK(argCount); }, }); /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:20 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from ADPCMCodecPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function ADPCMCodecPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift /*** Variables ***/ var bitPosition = 0; var byteIndex = 0; var currentByte = 0; var encodedBytes = null; var interpreterProxy = null; var moduleName = "ADPCMCodecPlugin 3 November 2014 (e)"; var stepSizeTable = null; /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Answer the best index to use for the difference between the given samples. */ /* Details: Scan stepSizeTable for the first entry >= the absolute value of the difference between sample values. Since indexes are zero-based, the index used during decoding will be the one in the following stepSizeTable entry. Since the index field of a Flash frame header is only six bits, the maximum index value is 63. */ /* Note: Since there does not appear to be any documentation of how Flash actually computes the indices used in its frame headers, this algorithm was guessed by reverse-engineering the Flash ADPCM decoder. */ function indexForDeltaFromto(thisSample, nextSample) { var bestIndex; var diff; var j; diff = nextSample - thisSample; if (diff < 0) { diff = 0 - diff; } bestIndex = 63; for (j = 1; j <= 62; j++) { if (bestIndex === 63) { if (stepSizeTable[j - 1] >= diff) { bestIndex = j; } } } return bestIndex; } /* Answer the next n bits of my bit stream as an unsigned integer. */ function nextBits(n) { var remaining; var result; var shift; result = 0; remaining = n; while(true) { shift = remaining - bitPosition; if (shift > 0) { /* consumed currentByte buffer; fetch next byte */ result += SHL(currentByte, shift); remaining -= bitPosition; currentByte = encodedBytes[((++byteIndex)) - 1]; bitPosition = 8; } else { /* still some bits left in currentByte buffer */ result += SHR(currentByte, (0 - shift)); /* mask out the consumed bits: */ bitPosition -= remaining; currentByte = currentByte & (SHR(255, (8 - bitPosition))); return result; } } } /* Write the next n bits to my bit stream. */ function nextBitsput(n, anInteger) { var bitsAvailable; var buf; var bufBits; var shift; buf = anInteger; bufBits = n; while(true) { bitsAvailable = 8 - bitPosition; /* either left or right shift */ /* append high bits of buf to end of currentByte: */ shift = bitsAvailable - bufBits; if (shift < 0) { /* currentByte buffer filled; output it */ currentByte += SHR(buf, (0 - shift)); encodedBytes[((++byteIndex)) - 1] = currentByte; bitPosition = 0; /* clear saved high bits of buf: */ currentByte = 0; buf = buf & ((SHL(1, (0 - shift))) - 1); bufBits -= bitsAvailable; } else { /* still some bits available in currentByte buffer */ currentByte += SHL(buf, shift); bitPosition += bufBits; return self; } } } function primitiveDecodeMono() { var rcvr; var count; var bit; var delta; var i; var predictedDelta; var step; var bitsPerSample; var deltaSignMask; var deltaValueHighBit; var deltaValueMask; var frameSizeMask; var index; var indexTable; var predicted; var sampleIndex; var samples; rcvr = interpreterProxy.stackValue(1); count = interpreterProxy.stackIntegerValue(0); predicted = interpreterProxy.fetchIntegerofObject(0, rcvr); index = interpreterProxy.fetchIntegerofObject(1, rcvr); deltaSignMask = interpreterProxy.fetchIntegerofObject(2, rcvr); deltaValueMask = interpreterProxy.fetchIntegerofObject(3, rcvr); deltaValueHighBit = interpreterProxy.fetchIntegerofObject(4, rcvr); frameSizeMask = interpreterProxy.fetchIntegerofObject(5, rcvr); currentByte = interpreterProxy.fetchIntegerofObject(6, rcvr); bitPosition = interpreterProxy.fetchIntegerofObject(7, rcvr); byteIndex = interpreterProxy.fetchIntegerofObject(8, rcvr); encodedBytes = interpreterProxy.fetchBytesofObject(9, rcvr); samples = interpreterProxy.fetchInt16ArrayofObject(10, rcvr); sampleIndex = interpreterProxy.fetchIntegerofObject(12, rcvr); bitsPerSample = interpreterProxy.fetchIntegerofObject(13, rcvr); stepSizeTable = interpreterProxy.fetchInt16ArrayofObject(14, rcvr); indexTable = interpreterProxy.fetchInt16ArrayofObject(15, rcvr); if (interpreterProxy.failed()) { return null; } for (i = 1; i <= count; i++) { if ((i & frameSizeMask) === 1) { /* start of frame; read frame header */ predicted = nextBits(16); if (predicted > 32767) { predicted -= 65536; } index = nextBits(6); samples[((++sampleIndex)) - 1] = predicted; } else { delta = nextBits(bitsPerSample); step = stepSizeTable[index]; predictedDelta = 0; bit = deltaValueHighBit; while (bit > 0) { if ((delta & bit) > 0) { predictedDelta += step; } step = step >>> 1; bit = bit >>> 1; } predictedDelta += step; if ((delta & deltaSignMask) > 0) { predicted -= predictedDelta; } else { predicted += predictedDelta; } if (predicted > 32767) { predicted = 32767; } else { if (predicted < -32768) { predicted = -32768; } } index += indexTable[delta & deltaValueMask]; if (index < 0) { index = 0; } else { if (index > 88) { index = 88; } } samples[((++sampleIndex)) - 1] = predicted; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(0, rcvr, predicted); interpreterProxy.storeIntegerofObjectwithValue(1, rcvr, index); interpreterProxy.storeIntegerofObjectwithValue(6, rcvr, currentByte); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, bitPosition); interpreterProxy.storeIntegerofObjectwithValue(8, rcvr, byteIndex); interpreterProxy.storeIntegerofObjectwithValue(12, rcvr, sampleIndex); interpreterProxy.pop(1); } function primitiveDecodeStereo() { var rcvr; var count; var bit; var deltaLeft; var deltaRight; var i; var indexLeft; var indexRight; var predictedDeltaLeft; var predictedDeltaRight; var predictedLeft; var predictedRight; var stepLeft; var stepRight; var bitsPerSample; var deltaSignMask; var deltaValueHighBit; var deltaValueMask; var frameSizeMask; var index; var indexTable; var predicted; var rightSamples; var sampleIndex; var samples; /* make local copies of decoder state variables */ rcvr = interpreterProxy.stackValue(1); count = interpreterProxy.stackIntegerValue(0); predicted = interpreterProxy.fetchInt16ArrayofObject(0, rcvr); index = interpreterProxy.fetchInt16ArrayofObject(1, rcvr); deltaSignMask = interpreterProxy.fetchIntegerofObject(2, rcvr); deltaValueMask = interpreterProxy.fetchIntegerofObject(3, rcvr); deltaValueHighBit = interpreterProxy.fetchIntegerofObject(4, rcvr); frameSizeMask = interpreterProxy.fetchIntegerofObject(5, rcvr); currentByte = interpreterProxy.fetchIntegerofObject(6, rcvr); bitPosition = interpreterProxy.fetchIntegerofObject(7, rcvr); byteIndex = interpreterProxy.fetchIntegerofObject(8, rcvr); encodedBytes = interpreterProxy.fetchBytesofObject(9, rcvr); samples = interpreterProxy.fetchInt16ArrayofObject(10, rcvr); rightSamples = interpreterProxy.fetchInt16ArrayofObject(11, rcvr); sampleIndex = interpreterProxy.fetchIntegerofObject(12, rcvr); bitsPerSample = interpreterProxy.fetchIntegerofObject(13, rcvr); stepSizeTable = interpreterProxy.fetchInt16ArrayofObject(14, rcvr); indexTable = interpreterProxy.fetchInt16ArrayofObject(15, rcvr); if (interpreterProxy.failed()) { return null; } predictedLeft = predicted[1 - 1]; predictedRight = predicted[2 - 1]; indexLeft = index[1 - 1]; indexRight = index[2 - 1]; for (i = 1; i <= count; i++) { if ((i & frameSizeMask) === 1) { /* start of frame; read frame header */ predictedLeft = nextBits(16); indexLeft = nextBits(6); predictedRight = nextBits(16); indexRight = nextBits(6); if (predictedLeft > 32767) { predictedLeft -= 65536; } if (predictedRight > 32767) { predictedRight -= 65536; } samples[((++sampleIndex)) - 1] = predictedLeft; rightSamples[sampleIndex - 1] = predictedRight; } else { deltaLeft = nextBits(bitsPerSample); deltaRight = nextBits(bitsPerSample); stepLeft = stepSizeTable[indexLeft]; stepRight = stepSizeTable[indexRight]; predictedDeltaLeft = (predictedDeltaRight = 0); bit = deltaValueHighBit; while (bit > 0) { if ((deltaLeft & bit) > 0) { predictedDeltaLeft += stepLeft; } if ((deltaRight & bit) > 0) { predictedDeltaRight += stepRight; } stepLeft = stepLeft >>> 1; stepRight = stepRight >>> 1; bit = bit >>> 1; } predictedDeltaLeft += stepLeft; predictedDeltaRight += stepRight; if ((deltaLeft & deltaSignMask) > 0) { predictedLeft -= predictedDeltaLeft; } else { predictedLeft += predictedDeltaLeft; } if ((deltaRight & deltaSignMask) > 0) { predictedRight -= predictedDeltaRight; } else { predictedRight += predictedDeltaRight; } if (predictedLeft > 32767) { predictedLeft = 32767; } else { if (predictedLeft < -32768) { predictedLeft = -32768; } } if (predictedRight > 32767) { predictedRight = 32767; } else { if (predictedRight < -32768) { predictedRight = -32768; } } indexLeft += indexTable[deltaLeft & deltaValueMask]; if (indexLeft < 0) { indexLeft = 0; } else { if (indexLeft > 88) { indexLeft = 88; } } indexRight += indexTable[deltaRight & deltaValueMask]; if (indexRight < 0) { indexRight = 0; } else { if (indexRight > 88) { indexRight = 88; } } samples[((++sampleIndex)) - 1] = predictedLeft; rightSamples[sampleIndex - 1] = predictedRight; } } predicted[1 - 1] = predictedLeft; predicted[2 - 1] = predictedRight; index[1 - 1] = indexLeft; index[2 - 1] = indexRight; if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(6, rcvr, currentByte); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, bitPosition); interpreterProxy.storeIntegerofObjectwithValue(8, rcvr, byteIndex); interpreterProxy.storeIntegerofObjectwithValue(12, rcvr, sampleIndex); interpreterProxy.pop(1); } function primitiveEncodeMono() { var rcvr; var count; var bit; var delta; var diff; var i; var p; var predictedDelta; var sign; var step; var bitsPerSample; var deltaSignMask; var deltaValueHighBit; var frameSizeMask; var index; var indexTable; var predicted; var sampleIndex; var samples; rcvr = interpreterProxy.stackValue(1); count = interpreterProxy.stackIntegerValue(0); predicted = interpreterProxy.fetchIntegerofObject(0, rcvr); index = interpreterProxy.fetchIntegerofObject(1, rcvr); deltaSignMask = interpreterProxy.fetchIntegerofObject(2, rcvr); deltaValueHighBit = interpreterProxy.fetchIntegerofObject(4, rcvr); frameSizeMask = interpreterProxy.fetchIntegerofObject(5, rcvr); currentByte = interpreterProxy.fetchIntegerofObject(6, rcvr); bitPosition = interpreterProxy.fetchIntegerofObject(7, rcvr); byteIndex = interpreterProxy.fetchIntegerofObject(8, rcvr); encodedBytes = interpreterProxy.fetchBytesofObject(9, rcvr); samples = interpreterProxy.fetchInt16ArrayofObject(10, rcvr); sampleIndex = interpreterProxy.fetchIntegerofObject(12, rcvr); bitsPerSample = interpreterProxy.fetchIntegerofObject(13, rcvr); stepSizeTable = interpreterProxy.fetchInt16ArrayofObject(14, rcvr); indexTable = interpreterProxy.fetchInt16ArrayofObject(15, rcvr); if (interpreterProxy.failed()) { return null; } step = stepSizeTable[1 - 1]; for (i = 1; i <= count; i++) { if ((i & frameSizeMask) === 1) { predicted = samples[((++sampleIndex)) - 1]; if (((p = predicted)) < 0) { p += 65536; } nextBitsput(16, p); if (i < count) { index = indexForDeltaFromto(predicted, samples[sampleIndex]); } nextBitsput(6, index); } else { /* compute sign and magnitude of difference from the predicted sample */ sign = 0; diff = samples[((++sampleIndex)) - 1] - predicted; if (diff < 0) { sign = deltaSignMask; diff = 0 - diff; } delta = 0; predictedDelta = 0; bit = deltaValueHighBit; while (bit > 0) { if (diff >= step) { delta += bit; predictedDelta += step; diff -= step; } step = step >>> 1; bit = bit >>> 1; } /* compute and clamp new prediction */ predictedDelta += step; if (sign > 0) { predicted -= predictedDelta; } else { predicted += predictedDelta; } if (predicted > 32767) { predicted = 32767; } else { if (predicted < -32768) { predicted = -32768; } } index += indexTable[delta]; if (index < 0) { index = 0; } else { if (index > 88) { index = 88; } } /* output encoded, signed delta */ step = stepSizeTable[index]; nextBitsput(bitsPerSample, sign | delta); } } if (bitPosition > 0) { /* flush the last output byte, if necessary */ encodedBytes[((++byteIndex)) - 1] = currentByte; } if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(0, rcvr, predicted); interpreterProxy.storeIntegerofObjectwithValue(1, rcvr, index); interpreterProxy.storeIntegerofObjectwithValue(6, rcvr, currentByte); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, bitPosition); interpreterProxy.storeIntegerofObjectwithValue(8, rcvr, byteIndex); interpreterProxy.storeIntegerofObjectwithValue(12, rcvr, sampleIndex); interpreterProxy.pop(1); } /* not yet implemented */ function primitiveEncodeStereo() { var rcvr; rcvr = interpreterProxy.stackValue(1); interpreterProxy.stackIntegerValue(0); currentByte = interpreterProxy.fetchIntegerofObject(6, rcvr); bitPosition = interpreterProxy.fetchIntegerofObject(7, rcvr); byteIndex = interpreterProxy.fetchIntegerofObject(8, rcvr); encodedBytes = interpreterProxy.fetchIntegerofObject(9, rcvr); stepSizeTable = interpreterProxy.fetchIntegerofObject(14, rcvr); if (interpreterProxy.failed()) { return null; } success(false); if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(6, rcvr, currentByte); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, bitPosition); interpreterProxy.storeIntegerofObjectwithValue(8, rcvr, byteIndex); interpreterProxy.pop(1); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("ADPCMCodecPlugin", { primitiveDecodeStereo: primitiveDecodeStereo, primitiveEncodeStereo: primitiveEncodeStereo, setInterpreter: setInterpreter, primitiveEncodeMono: primitiveEncodeMono, primitiveDecodeMono: primitiveDecodeMono, getModuleName: getModuleName, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 14 November 2014 12:21:50 am */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.17 uuid: 399be48b-95d8-4722-bdcc-39a94a12c486 from BalloonEnginePlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function B2DPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; /*** Functions ***/ function CLASSOF(obj) { return typeof obj === "number" ? interpreterProxy.classSmallInteger() : obj.sqClass } function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function BYTESIZEOF(obj) { return obj.bytes ? obj.bytes.length : obj.words ? obj.words.length * 4 : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift function PTR_ADD(p, n) { return new Int32Array(p.buffer, p.byteOffset + n * 4); } function FPTR_ADD(p, n) { return new Float32Array(p.buffer, p.byteOffset + n * 4); } /*** Constants ***/ var BEBalloonEngineSize = 12; var BEBitBltIndex = 2; var BEFormsIndex = 3; var BESpanIndex = 1; var BEWorkBufferIndex = 0; var ETBalloonEdgeDataSize = 6; var ETIndexIndex = 0; var ETLinesIndex = 4; var ETXValueIndex = 1; var ETYValueIndex = 2; var ETZValueIndex = 3; var FTBalloonFillDataSize = 6; var FTIndexIndex = 0; var FTMaxXIndex = 2; var FTMinXIndex = 1; var FTYValueIndex = 3; var GBBaseSize = 16; var GBBitmapDepth = 12; var GBBitmapHeight = 11; var GBBitmapRaster = 14; var GBBitmapSize = 13; var GBBitmapWidth = 10; var GBColormapOffset = 18; var GBColormapSize = 15; var GBEndX = 14; var GBEndY = 15; var GBFinalX = 21; var GBMBaseSize = 18; var GBTileFlag = 16; var GBUpdateDDX = 4; var GBUpdateDDY = 5; var GBUpdateDX = 2; var GBUpdateDY = 3; var GBUpdateData = 10; var GBUpdateX = 0; var GBUpdateY = 1; var GBViaX = 12; var GBViaY = 13; var GBWideEntry = 18; var GBWideExit = 19; var GBWideExtent = 20; var GBWideFill = 16; var GBWideSize = 28; var GBWideUpdateData = 22; var GBWideWidth = 17; var GEBaseEdgeSize = 10; var GEBaseFillSize = 4; var GEEdgeFillsInvalid = 65536; var GEFAlreadyFailed = 100; var GEFBadPoint = 121; var GEFBitBltLoadFailed = 122; var GEFClassMismatch = 114; var GEFEdgeDataTooSmall = 112; var GEFEngineIsInteger = 101; var GEFEngineIsWords = 102; var GEFEngineStopped = 104; var GEFEngineTooSmall = 103; var GEFEntityCheckFailed = 120; var GEFEntityLoadFailed = 119; var GEFFillDataTooSmall = 113; var GEFFormLoadFailed = 123; var GEFSizeMismatch = 115; var GEFWorkBufferBadMagic = 108; var GEFWorkBufferIsInteger = 105; var GEFWorkBufferIsPointers = 106; var GEFWorkBufferStartWrong = 110; var GEFWorkBufferTooSmall = 107; var GEFWorkBufferWrongSize = 109; var GEFWorkTooBig = 111; var GEFWrongEdge = 118; var GEFWrongFill = 117; var GEFWrongState = 116; var GEFillIndexLeft = 8; var GEFillIndexRight = 9; var GENumLines = 7; var GEObjectIndex = 2; var GEObjectLength = 1; var GEObjectType = 0; var GEPrimitiveBezier = 6; var GEPrimitiveClippedBitmapFill = 1024; var GEPrimitiveEdge = 2; var GEPrimitiveEdgeMask = 255; var GEPrimitiveFill = 256; var GEPrimitiveFillMask = 65280; var GEPrimitiveLine = 4; var GEPrimitiveLinearGradientFill = 512; var GEPrimitiveRadialGradientFill = 768; var GEPrimitiveTypeMask = 65535; var GEPrimitiveWide = 1; var GEPrimitiveWideBezier = 7; var GEPrimitiveWideLine = 5; var GEPrimitiveWideMask = 254; var GEStateAddingFromGET = 1; var GEStateBlitBuffer = 5; var GEStateCompleted = 8; var GEStateScanningAET = 3; var GEStateUnlocked = 0; var GEStateUpdateEdges = 6; var GEStateWaitingChange = 7; var GEStateWaitingForEdge = 2; var GEStateWaitingForFill = 4; var GEXValue = 4; var GEYValue = 5; var GEZValue = 6; var GErrorAETEntry = 6; var GErrorBadState = 2; var GErrorFillEntry = 5; var GErrorGETEntry = 4; var GErrorNeedFlush = 3; var GErrorNoMoreSpace = 1; var GFDirectionX = 6; var GFDirectionY = 7; var GFNormalX = 8; var GFNormalY = 9; var GFOriginX = 4; var GFOriginY = 5; var GFRampLength = 10; var GFRampOffset = 12; var GGBaseSize = 12; var GLBaseSize = 16; var GLEndX = 14; var GLEndY = 15; var GLError = 13; var GLErrorAdjDown = 15; var GLErrorAdjUp = 14; var GLWideEntry = 18; var GLWideExit = 19; var GLWideExtent = 20; var GLWideFill = 16; var GLWideSize = 21; var GLWideWidth = 17; var GLXDirection = 10; var GLXIncrement = 12; var GLYDirection = 11; var GWAAColorMask = 51; var GWAAColorShift = 50; var GWAAHalfPixel = 53; var GWAALevel = 48; var GWAAScanMask = 52; var GWAAShift = 49; var GWAETStart = 13; var GWAETUsed = 14; var GWBezierHeightSubdivisions = 109; var GWBezierLineConversions = 111; var GWBezierMonotonSubdivisions = 108; var GWBezierOverflowSubdivisions = 110; var GWBufferTop = 10; var GWClearSpanBuffer = 69; var GWClipMaxX = 43; var GWClipMaxY = 45; var GWClipMinX = 42; var GWClipMinY = 44; var GWColorTransform = 24; var GWCountAddAETEntry = 97; var GWCountChangeAETEntry = 107; var GWCountDisplaySpan = 103; var GWCountFinishTest = 93; var GWCountInitializing = 91; var GWCountMergeFill = 101; var GWCountNextAETEntry = 105; var GWCountNextFillEntry = 99; var GWCountNextGETEntry = 95; var GWCurrentY = 88; var GWCurrentZ = 113; var GWDestOffsetX = 46; var GWDestOffsetY = 47; var GWEdgeTransform = 18; var GWFillMaxX = 37; var GWFillMaxY = 39; var GWFillMinX = 36; var GWFillMinY = 38; var GWGETStart = 11; var GWGETUsed = 12; var GWHasColorTransform = 17; var GWHasEdgeTransform = 16; var GWHeaderSize = 128; var GWLastExportedEdge = 65; var GWLastExportedFill = 66; var GWLastExportedLeftX = 67; var GWLastExportedRightX = 68; var GWMagicIndex = 0; var GWMagicNumber = 1097753705; var GWMinimalSize = 256; var GWNeedsFlush = 63; var GWObjStart = 8; var GWObjUsed = 9; var GWPoint1 = 80; var GWPoint2 = 82; var GWPoint3 = 84; var GWPoint4 = 86; var GWSize = 1; var GWSpanEnd = 34; var GWSpanEndAA = 35; var GWSpanSize = 33; var GWSpanStart = 32; var GWState = 2; var GWStopReason = 64; var GWTimeAddAETEntry = 96; var GWTimeChangeAETEntry = 106; var GWTimeDisplaySpan = 102; var GWTimeFinishTest = 92; var GWTimeInitializing = 90; var GWTimeMergeFill = 100; var GWTimeNextAETEntry = 104; var GWTimeNextFillEntry = 98; var GWTimeNextGETEntry = 94; var PrimErrBadArgument = 3; var PrimErrBadNumArgs = 5; /*** Variables ***/ var aetBuffer = null; var bbPluginName = "BitBltPlugin"; var copyBitsFn = null; var dispatchReturnValue = 0; var dispatchedValue = 0; var doProfileStats = 0; var engine = 0; var engineStopped = 0; var formArray = 0; var geProfileTime = 0; var getBuffer = null; var interpreterProxy = null; var loadBBFn = null; var moduleName = "B2DPlugin 14 November 2014 (e)"; var objBuffer = null; var objUsed = 0; var spanBuffer = null; var workBuffer = null; function aaColorMaskGet() { return workBuffer[GWAAColorMask]; } function aaColorMaskPut(value) { return workBuffer[GWAAColorMask] = value; } function aaColorShiftGet() { return workBuffer[GWAAColorShift]; } function aaColorShiftPut(value) { return workBuffer[GWAAColorShift] = value; } /* Common function to compute the first full pixel for AA drawing */ function aaFirstPixelFromto(leftX, rightX) { var firstPixel; firstPixel = ((leftX + aaLevelGet()) - 1) & ~(aaLevelGet() - 1); if (firstPixel > rightX) { return rightX; } else { return firstPixel; } } function aaHalfPixelPut(value) { return workBuffer[GWAAHalfPixel] = value; } /* Common function to compute the last full pixel for AA drawing */ function aaLastPixelFromto(leftX, rightX) { return (rightX - 1) & ~(aaLevelGet() - 1); } function aaLevelGet() { return workBuffer[GWAALevel]; } function aaLevelPut(value) { return workBuffer[GWAALevel] = value; } function aaScanMaskGet() { return workBuffer[GWAAScanMask]; } function aaScanMaskPut(value) { return workBuffer[GWAAScanMask] = value; } function aaShiftGet() { return workBuffer[GWAAShift]; } function aaShiftPut(value) { return workBuffer[GWAAShift] = value; } /* Compute the squared value of a 8.24 number with 0.0 <= value < 1.0, e.g., compute (value * value) bitShift: -24 */ function absoluteSquared8Dot24(value) { var word1; var word2; word1 = value & 65535; word2 = (value >>> 16) & 255; return ((((word1 * word1) >>> 16) + ((word1 * word2) * 2)) + ((word2 * word2) << 16)) >>> 8; } /* Return the accurate length of the vector described by deltaX and deltaY */ function accurateLengthOfwith(deltaX, deltaY) { var length2; if (deltaX === 0) { if (deltaY < 0) { return 0 - deltaY; } else { return deltaY; } } if (deltaY === 0) { if (deltaX < 0) { return 0 - deltaX; } else { return deltaX; } } length2 = (deltaX * deltaX) + (deltaY * deltaY); return computeSqrt(length2); } function addEdgeToGET(edge) { if (!allocateGETEntry(1)) { return 0; } getBuffer[getUsedGet()] = edge; getUsedPut(getUsedGet() + 1); } /* Adjust the wide bezier curve (dx < 0) to start/end at the right point */ function adjustWideBezierLeftwidthoffsetendX(bezier, lineWidth, lineOffset, endX) { var lastX; var lastY; bezierUpdateDataOf(bezier)[GBUpdateX] = (bezierUpdateDataOf(bezier)[GBUpdateX] - (lineOffset * 256)); lastX = wideBezierUpdateDataOf(bezier)[GBUpdateX]; wideBezierUpdateDataOf(bezier)[GBUpdateX] = (lastX + ((lineWidth - lineOffset) * 256)); lastY = wideBezierUpdateDataOf(bezier)[GBUpdateY]; wideBezierUpdateDataOf(bezier)[GBUpdateY] = (lastY + (lineWidth * 256)); bezierFinalXOfput(bezier, endX - lineOffset); } /* Adjust the wide bezier curve (dx >= 0) to start/end at the right point */ function adjustWideBezierRightwidthoffsetendX(bezier, lineWidth, lineOffset, endX) { var lastX; var lastY; bezierUpdateDataOf(bezier)[GBUpdateX] = (bezierUpdateDataOf(bezier)[GBUpdateX] + (lineOffset * 256)); lastX = wideBezierUpdateDataOf(bezier)[GBUpdateX]; wideBezierUpdateDataOf(bezier)[GBUpdateX] = (lastX - ((lineWidth - lineOffset) * 256)); /* Set lineWidth pixels down */ lastY = wideBezierUpdateDataOf(bezier)[GBUpdateY]; wideBezierUpdateDataOf(bezier)[GBUpdateY] = (lastY + (lineWidth * 256)); bezierFinalXOfput(bezier, (endX - lineOffset) + lineWidth); } /* Adjust the wide line after it has been stepped from lastX to nextX. Special adjustments of line width and start position are made here to simulate a rectangular brush */ function adjustWideLineafterSteppingFromto(line, lastX, nextX) { var baseWidth; var deltaX; var lineOffset; var lineWidth; var xDir; var yEntry; var yExit; /* Don't inline this */ /* Fetch the values the adjustment decisions are based on */ yEntry = wideLineEntryOf(line); yExit = wideLineExitOf(line); baseWidth = wideLineExtentOf(line); lineOffset = offsetFromWidth(baseWidth); lineWidth = wideLineWidthOf(line); xDir = lineXDirectionOf(line); /* Adjust the start of the line to fill an entire rectangle */ deltaX = nextX - lastX; if (yEntry < baseWidth) { if (xDir < 0) { /* effectively adding */ lineWidth -= deltaX; } else { lineWidth += deltaX; edgeXValueOfput(line, lastX); } } if ((yExit + lineOffset) === 0) { if (xDir > 0) { lineWidth -= lineXIncrementOf(line); } else { /* effectively subtracting */ lineWidth += lineXIncrementOf(line); edgeXValueOfput(line, lastX); } } if ((yExit + lineOffset) > 0) { if (xDir < 0) { /* effectively subtracting */ lineWidth += deltaX; edgeXValueOfput(line, lastX); } else { lineWidth -= deltaX; } } wideLineWidthOfput(line, lineWidth); } function aetStartGet() { return workBuffer[GWAETStart]; } function aetStartPut(value) { return workBuffer[GWAETStart] = value; } function aetUsedGet() { return workBuffer[GWAETUsed]; } function aetUsedPut(value) { return workBuffer[GWAETUsed] = value; } /* Allocate n slots in the active edge table */ function allocateAETEntry(nSlots) { return needAvailableSpace(nSlots); } function allocateBezier() { var bezier; if (!allocateObjEntry(GBBaseSize)) { return 0; } bezier = objUsed; objUsed = bezier + GBBaseSize; objectTypeOfput(bezier, GEPrimitiveBezier); objectIndexOfput(bezier, 0); objectLengthOfput(bezier, GBBaseSize); return bezier; } function allocateBezierStackEntry() { wbStackPush(6); return wbStackSize(); } function allocateBitmapFillcolormap(cmSize, cmBits) { var cm; var fill; var fillSize; var i; fillSize = GBMBaseSize + cmSize; if (!allocateObjEntry(fillSize)) { return 0; } fill = objUsed; objUsed = fill + fillSize; objectTypeOfput(fill, GEPrimitiveClippedBitmapFill); objectIndexOfput(fill, 0); objectLengthOfput(fill, fillSize); cm = colormapOf(fill); if (hasColorTransform()) { for (i = 0; i <= (cmSize - 1); i++) { cm[i] = transformColor(cmBits[i]); } } else { for (i = 0; i <= (cmSize - 1); i++) { cm[i] = cmBits[i]; } } bitmapCmSizeOfput(fill, cmSize); return fill; } /* Allocate n slots in the global edge table */ function allocateGETEntry(nSlots) { var dstIndex; var i; var srcIndex; var iLimiT; /* First allocate nSlots in the AET */ if (!allocateAETEntry(nSlots)) { return false; } if (aetUsedGet() !== 0) { /* Then move the AET upwards */ srcIndex = aetUsedGet(); dstIndex = aetUsedGet() + nSlots; for (i = 1, iLimiT = aetUsedGet(); i <= iLimiT; i++) { aetBuffer[(--dstIndex)] = aetBuffer[(--srcIndex)]; } } aetBuffer = PTR_ADD(aetBuffer, nSlots); return true; } function allocateGradientFillrampWidthisRadial(ramp, rampWidth, isRadial) { var fill; var fillSize; var i; var rampPtr; fillSize = GGBaseSize + rampWidth; if (!allocateObjEntry(fillSize)) { return 0; } fill = objUsed; objUsed = fill + fillSize; if (isRadial) { objectTypeOfput(fill, GEPrimitiveRadialGradientFill); } else { objectTypeOfput(fill, GEPrimitiveLinearGradientFill); } objectIndexOfput(fill, 0); objectLengthOfput(fill, fillSize); rampPtr = gradientRampOf(fill); if (hasColorTransform()) { for (i = 0; i <= (rampWidth - 1); i++) { rampPtr[i] = transformColor(ramp[i]); } } else { for (i = 0; i <= (rampWidth - 1); i++) { rampPtr[i] = ramp[i]; } } gradientRampLengthOfput(fill, rampWidth); return fill; } function allocateLine() { var line; if (!allocateObjEntry(GLBaseSize)) { return 0; } line = objUsed; objUsed = line + GLBaseSize; objectTypeOfput(line, GEPrimitiveLine); objectIndexOfput(line, 0); objectLengthOfput(line, GLBaseSize); return line; } /* Allocate n slots in the object buffer */ function allocateObjEntry(nSlots) { var dstIndex; var i; var srcIndex; var iLimiT; /* First allocate nSlots in the GET */ if (!allocateGETEntry(nSlots)) { return false; } if (getUsedGet() !== 0) { /* Then move the GET upwards */ srcIndex = getUsedGet(); dstIndex = getUsedGet() + nSlots; for (i = 1, iLimiT = getUsedGet(); i <= iLimiT; i++) { getBuffer[(--dstIndex)] = getBuffer[(--srcIndex)]; } } getBuffer = PTR_ADD(getBuffer, nSlots); return true; } /* AET and Stack allocation are symmetric */ function allocateStackEntry(nSlots) { return needAvailableSpace(nSlots); } function allocateStackFillEntry() { return wbStackPush(stackFillEntryLength()); } function allocateWideBezier() { var bezier; if (!allocateObjEntry(GBWideSize)) { return 0; } bezier = objUsed; objUsed = bezier + GBWideSize; objectTypeOfput(bezier, GEPrimitiveWideBezier); objectIndexOfput(bezier, 0); objectLengthOfput(bezier, GBWideSize); return bezier; } function allocateWideLine() { var line; if (!allocateObjEntry(GLWideSize)) { return 0; } line = objUsed; objUsed = line + GLWideSize; objectTypeOfput(line, GEPrimitiveWideLine); objectIndexOfput(line, 0); objectLengthOfput(line, GLWideSize); return line; } function areEdgeFillsValid(edge) { return (objectHeaderOf(edge) & GEEdgeFillsInvalid) === 0; } /* Make sure that val1 is between val2 and val3. */ function assureValuebetweenand(val1, val2, val3) { if (val2 > val3) { if (val1 > val2) { return val2; } if (val1 < val3) { return val3; } } else { if (val1 < val2) { return val2; } if (val1 > val3) { return val3; } } return val1; } function bezierEndXOf(bezier) { return objat(bezier, GBEndX); } function bezierEndXOfput(bezier, value) { return objatput(bezier, GBEndX, value); } function bezierEndYOf(bezier) { return objat(bezier, GBEndY); } function bezierEndYOfput(bezier, value) { return objatput(bezier, GBEndY, value); } function bezierFinalXOf(bezier) { return objat(bezier, GBFinalX); } function bezierFinalXOfput(bezier, value) { return objatput(bezier, GBFinalX, value); } function bezierUpdateDataOf(bezier) { return PTR_ADD(objBuffer, bezier + GBUpdateData); } function bezierViaXOf(bezier) { return objat(bezier, GBViaX); } function bezierViaXOfput(bezier, value) { return objatput(bezier, GBViaX, value); } function bezierViaYOf(bezier) { return objat(bezier, GBViaY); } function bezierViaYOfput(bezier, value) { return objatput(bezier, GBViaY, value); } function bitmapCmSizeOf(bmFill) { return objat(bmFill, GBColormapSize); } function bitmapCmSizeOfput(bmFill, value) { return objatput(bmFill, GBColormapSize, value); } function bitmapDepthOf(bmFill) { return objat(bmFill, GBBitmapDepth); } function bitmapDepthOfput(bmFill, value) { return objatput(bmFill, GBBitmapDepth, value); } function bitmapHeightOf(bmFill) { return objat(bmFill, GBBitmapHeight); } function bitmapHeightOfput(bmFill, value) { return objatput(bmFill, GBBitmapHeight, value); } function bitmapRasterOf(bmFill) { return objat(bmFill, GBBitmapRaster); } function bitmapRasterOfput(bmFill, value) { return objatput(bmFill, GBBitmapRaster, value); } function bitmapSizeOf(bmFill) { return objat(bmFill, GBBitmapSize); } function bitmapSizeOfput(bmFill, value) { return objatput(bmFill, GBBitmapSize, value); } function bitmapTileFlagOf(bmFill) { return objat(bmFill, GBTileFlag); } function bitmapTileFlagOfput(bmFill, value) { return objatput(bmFill, GBTileFlag, value); } function bitmapValuebitsatXy(bmFill, bits, xp, yp) { var a; var b; var bmDepth; var bmRaster; var cMask; var g; var r; var rShift; var value; bmDepth = bitmapDepthOf(bmFill); bmRaster = bitmapRasterOf(bmFill); if (bmDepth === 32) { value = (bits[(bmRaster * yp) + xp]|0); if ((value !== 0) && ((value & 4278190080) === 0)) { value = value | 4278190080; } return uncheckedTransformColor(value); } rShift = rShiftTable()[bmDepth]; /* cMask - mask out the pixel from the word */ value = bits[(bmRaster * yp) + (SHR(xp, rShift))]; /* rShift - shift value to move the pixel in the word to the lowest bit position */ cMask = (SHL(1, bmDepth)) - 1; rShift = (32 - bmDepth) - ((xp & ((SHL(1, rShift)) - 1)) * bmDepth); value = (SHR(value, rShift)) & cMask; if (bmDepth === 16) { /* Must convert by expanding bits */ if (value !== 0) { b = (value & 31) << 3; b += b >>> 5; g = ((value >>> 5) & 31) << 3; g += g >>> 5; r = ((value >>> 10) & 31) << 3; r += r >>> 5; a = 255; value = ((b + (g << 8)) + (r << 16)) + (a << 24); } } else { /* Must convert by using color map */ if (bitmapCmSizeOf(bmFill) === 0) { value = 0; } else { value = colormapOf(bmFill)[value]; } } return uncheckedTransformColor(value); } function bitmapWidthOf(bmFill) { return objat(bmFill, GBBitmapWidth); } function bitmapWidthOfput(bmFill, value) { return objatput(bmFill, GBBitmapWidth, value); } function bzEndX(index) { return wbStackValue((wbStackSize() - index) + 4); } function bzEndXput(index, value) { return wbStackValueput((wbStackSize() - index) + 4, value); } function bzEndY(index) { return wbStackValue((wbStackSize() - index) + 5); } function bzEndYput(index, value) { return wbStackValueput((wbStackSize() - index) + 5, value); } function bzStartX(index) { return wbStackValue((wbStackSize() - index) + 0); } function bzStartXput(index, value) { return wbStackValueput((wbStackSize() - index) + 0, value); } function bzStartY(index) { return wbStackValue((wbStackSize() - index) + 1); } function bzStartYput(index, value) { return wbStackValueput((wbStackSize() - index) + 1, value); } function bzViaX(index) { return wbStackValue((wbStackSize() - index) + 2); } function bzViaXput(index, value) { return wbStackValueput((wbStackSize() - index) + 2, value); } function bzViaY(index) { return wbStackValue((wbStackSize() - index) + 3); } function bzViaYput(index, value) { return wbStackValueput((wbStackSize() - index) + 3, value); } /* Check the fill indexes in the run-length encoded fillList */ function checkCompressedFillIndexListmaxsegments(fillList, maxIndex, nSegs) { var fillPtr; var i; var length; var nFills; var runLength; var runValue; length = SIZEOF(fillList); fillPtr = fillList.wordsAsInt32Array(); nFills = 0; for (i = 0; i <= (length - 1); i++) { runLength = shortRunLengthAtfrom(i, fillPtr); runValue = shortRunValueAtfrom(i, fillPtr); if (!((runValue >= 0) && (runValue <= maxIndex))) { return false; } nFills += runLength; } return nFills === nSegs; } /* Check if the indexList (containing fill handles) is okay. */ function checkCompressedFills(indexList) { var fillIndex; var fillPtr; var i; var length; /* First check if the oops have the right format */ if (!interpreterProxy.isWords(indexList)) { return false; } length = SIZEOF(indexList); fillPtr = indexList.wordsAsInt32Array(); for (i = 0; i <= (length - 1); i++) { /* Make sure the fill is okay */ fillIndex = fillPtr[i]; if (!isFillOkay(fillIndex)) { return false; } } return true; } /* Check the run-length encoded lineWidthList matches nSegments */ function checkCompressedLineWidthssegments(lineWidthList, nSegments) { var i; var length; var nItems; var ptr; var runLength; length = SIZEOF(lineWidthList); ptr = lineWidthList.wordsAsInt32Array(); nItems = 0; for (i = 0; i <= (length - 1); i++) { runLength = shortRunLengthAtfrom(i, ptr); nItems += runLength; } return nItems === nSegments; } /* Check if the given point array can be handled by the engine. */ function checkCompressedPointssegments(points, nSegments) { var pSize; if (!interpreterProxy.isWords(points)) { return false; } /* The points must be either in PointArray format or ShortPointArray format. Also, we currently handle only quadratic segments (e.g., 3 points each) and thus either pSize = nSegments * 3, for ShortPointArrays or, pSize = nSegments * 6, for PointArrays */ pSize = SIZEOF(points); if (!((pSize === (nSegments * 3)) || (pSize === (nSegments * 6)))) { return false; } return true; } /* Check if the given shape can be handled by the engine. Since there are a number of requirements this is an extra method. */ function checkCompressedShapesegmentsleftFillsrightFillslineWidthslineFillsfillIndexList(points, nSegments, leftFills, rightFills, lineWidths, lineFills, fillIndexList) { var maxFillIndex; if (!checkCompressedPointssegments(points, nSegments)) { return false; } if (!checkCompressedFills(fillIndexList)) { return false; } maxFillIndex = SIZEOF(fillIndexList); if (!checkCompressedFillIndexListmaxsegments(leftFills, maxFillIndex, nSegments)) { return false; } if (!checkCompressedFillIndexListmaxsegments(rightFills, maxFillIndex, nSegments)) { return false; } if (!checkCompressedFillIndexListmaxsegments(lineFills, maxFillIndex, nSegments)) { return false; } if (!checkCompressedLineWidthssegments(lineWidths, nSegments)) { return false; } return true; } /* Add the bezier to the global edge table if it intersects the clipping region */ function checkedAddBezierToGET(bezier) { var lineWidth; if (isWide(bezier)) { lineWidth = wideBezierExtentOf(bezier); } else { lineWidth = 0; } if ((bezierEndYOf(bezier) + lineWidth) < fillMinYGet()) { return 0; } if (((edgeXValueOf(bezier) - lineWidth) >= fillMaxXGet()) && ((bezierEndXOf(bezier) - lineWidth) >= fillMaxXGet())) { return 0; } addEdgeToGET(bezier); } /* Add the edge to the global edge table. For known edge types, check if the edge intersects the visible region */ function checkedAddEdgeToGET(edge) { if (isLine(edge)) { return checkedAddLineToGET(edge); } if (isBezier(edge)) { return checkedAddBezierToGET(edge); } addEdgeToGET(edge); } /* Add the line to the global edge table if it intersects the clipping region */ function checkedAddLineToGET(line) { var lineWidth; if (isWide(line)) { lineWidth = wideLineExtentOf(line); } else { lineWidth = 0; } if ((lineEndYOf(line) + lineWidth) < fillMinYGet()) { return 0; } if (((edgeXValueOf(line) - lineWidth) >= fillMaxXGet()) && ((lineEndXOf(line) - lineWidth) >= fillMaxXGet())) { return 0; } addEdgeToGET(line); } function circleCosTable() { var theTable = [1.0, 0.98078528040323, 0.923879532511287, 0.831469612302545, 0.7071067811865475, 0.555570233019602, 0.38268343236509, 0.1950903220161286, 0.0, -0.1950903220161283, -0.3826834323650896, -0.555570233019602, -0.707106781186547, -0.831469612302545, -0.9238795325112865, -0.98078528040323, -1, -0.98078528040323, -0.923879532511287, -0.831469612302545, -0.707106781186548, -0.555570233019602, -0.3826834323650903, -0.1950903220161287, 0.0, 0.1950903220161282, 0.38268343236509, 0.555570233019602, 0.707106781186547, 0.831469612302545, 0.9238795325112865, 0.98078528040323, 1.0 ]; return theTable; } function circleSinTable() { var theTable = [0.0, 0.1950903220161282, 0.3826834323650897, 0.555570233019602, 0.707106781186547, 0.831469612302545, 0.923879532511287, 0.98078528040323, 1.0, 0.98078528040323, 0.923879532511287, 0.831469612302545, 0.7071067811865475, 0.555570233019602, 0.38268343236509, 0.1950903220161286, 0.0, -0.1950903220161283, -0.3826834323650896, -0.555570233019602, -0.707106781186547, -0.831469612302545, -0.9238795325112865, -0.98078528040323, -1, -0.98078528040323, -0.923879532511287, -0.831469612302545, -0.707106781186548, -0.555570233019602, -0.3826834323650903, -0.1950903220161287, 0.0 ]; return theTable; } function clampValuemax(value, maxValue) { if (value < 0) { return 0; } else { if (value >= maxValue) { return maxValue - 1; } else { return value; } } } /* Clear the current span buffer. The span buffer is only cleared in the area that has been used by the previous scan line. */ function clearSpanBuffer() { var x0; var x1; x0 = SHR(spanStartGet(), aaShiftGet()); x1 = (SHR(spanEndGet(), aaShiftGet())) + 1; if (x0 < 0) { x0 = 0; } if (x1 > spanSizeGet()) { x1 = spanSizeGet(); } while (x0 < x1) { spanBuffer[x0] = 0; ++x0; } spanStartPut(spanSizeGet()); spanEndPut(0); } function clearSpanBufferGet() { return workBuffer[GWClearSpanBuffer]; } function clearSpanBufferPut(value) { return workBuffer[GWClearSpanBuffer] = value; } function clipMaxXGet() { return workBuffer[GWClipMaxX]; } function clipMaxXPut(value) { return workBuffer[GWClipMaxX] = value; } function clipMaxYGet() { return workBuffer[GWClipMaxY]; } function clipMaxYPut(value) { return workBuffer[GWClipMaxY] = value; } function clipMinXGet() { return workBuffer[GWClipMinX]; } function clipMinXPut(value) { return workBuffer[GWClipMinX] = value; } function clipMinYGet() { return workBuffer[GWClipMinY]; } function clipMinYPut(value) { return workBuffer[GWClipMinY] = value; } function colorTransform() { return FPTR_ADD(workBuffer, GWColorTransform); } function colormapOf(bmFill) { return PTR_ADD(objBuffer, bmFill + GBColormapOffset); } /* Split the bezier curve at the given parametric value. Note: Since this method is only invoked to make non-monoton beziers monoton we must check for the resulting y values to be *really* between the start and end value. */ function computeBeziersplitAt(index, param) { var endX; var endY; var leftViaX; var leftViaY; var newIndex; var rightViaX; var rightViaY; var sharedX; var sharedY; var startX; var startY; var viaX; var viaY; leftViaX = (startX = bzStartX(index)); leftViaY = (startY = bzStartY(index)); rightViaX = (viaX = bzViaX(index)); rightViaY = (viaY = bzViaY(index)); endX = bzEndX(index); /* Compute intermediate points */ endY = bzEndY(index); sharedX = (leftViaX += (((viaX - startX) * param)|0)); sharedY = (leftViaY += (((viaY - startY) * param)|0)); rightViaX += (((endX - viaX) * param)|0); /* Compute new shared point */ rightViaY += (((endY - viaY) * param)|0); sharedX += (((rightViaX - leftViaX) * param)|0); /* Check the new via points */ sharedY += (((rightViaY - leftViaY) * param)|0); leftViaY = assureValuebetweenand(leftViaY, startY, sharedY); rightViaY = assureValuebetweenand(rightViaY, sharedY, endY); newIndex = allocateBezierStackEntry(); if (engineStopped) { return 0; } bzViaXput(index, leftViaX); bzViaYput(index, leftViaY); bzEndXput(index, sharedX); bzEndYput(index, sharedY); bzStartXput(newIndex, sharedX); bzStartYput(newIndex, sharedY); bzViaXput(newIndex, rightViaX); bzViaYput(newIndex, rightViaY); bzEndXput(newIndex, endX); bzEndYput(newIndex, endY); return newIndex; } /* Split the bezier curve at 0.5. */ function computeBezierSplitAtHalf(index) { var endX; var endY; var leftViaX; var leftViaY; var newIndex; var rightViaX; var rightViaY; var sharedX; var sharedY; var startX; var startY; var viaX; var viaY; newIndex = allocateBezierStackEntry(); if (engineStopped) { return 0; } leftViaX = (startX = bzStartX(index)); leftViaY = (startY = bzStartY(index)); rightViaX = (viaX = bzViaX(index)); rightViaY = (viaY = bzViaY(index)); endX = bzEndX(index); /* Compute intermediate points */ endY = bzEndY(index); leftViaX += (viaX - startX) >> 1; leftViaY += (viaY - startY) >> 1; sharedX = (rightViaX += (endX - viaX) >> 1); /* Compute new shared point */ sharedY = (rightViaY += (endY - viaY) >> 1); sharedX += (leftViaX - rightViaX) >> 1; /* Store the first part back */ sharedY += (leftViaY - rightViaY) >> 1; bzViaXput(index, leftViaX); bzViaYput(index, leftViaY); bzEndXput(index, sharedX); bzEndYput(index, sharedY); bzStartXput(newIndex, sharedX); bzStartYput(newIndex, sharedY); bzViaXput(newIndex, rightViaX); bzViaYput(newIndex, rightViaY); bzEndXput(newIndex, endX); bzEndYput(newIndex, endY); return newIndex; } /* Get both values from the two boundaries of the given bezier and compute the actual position/width of the line */ function computeFinalWideBezierValueswidth(bezier, lineWidth) { var leftX; var rightX; var temp; leftX = bezierUpdateDataOf(bezier)[GBUpdateX] >> 8; rightX = wideBezierUpdateDataOf(bezier)[GBUpdateX] >> 8; if (leftX > rightX) { temp = leftX; leftX = rightX; rightX = temp; } edgeXValueOfput(bezier, leftX); if ((rightX - leftX) > lineWidth) { wideBezierWidthOfput(bezier, rightX - leftX); } else { wideBezierWidthOfput(bezier, lineWidth); } } function computeSqrt(length2) { if (length2 < 32) { return smallSqrtTable()[length2]; } else { return ((Math.sqrt(length2) + 0.5)|0); } } function copyBitsFromtoat(x0, x1, yValue) { if (!copyBitsFn) { /* We need copyBits here so try to load it implicitly */ if (!initialiseModule()) { return false; } } return copyBitsFn(x0, x1, yValue); } /* Create the global edge table */ function createGlobalEdgeTable() { var end; var object; object = 0; end = objUsed; while (object < end) { /* Note: addEdgeToGET: may fail on insufficient space but that's not a problem here */ if (isEdge(object)) { /* Check if the edge starts below fillMaxY. */ if (!(edgeYValueOf(object) >= fillMaxYGet())) { checkedAddEdgeToGET(object); } } object += objectLengthOf(object); } } function currentYGet() { return workBuffer[GWCurrentY]; } function currentYPut(value) { return workBuffer[GWCurrentY] = value; } function currentZGet() { return workBuffer[GWCurrentZ]; } function currentZPut(value) { return workBuffer[GWCurrentZ] = value; } function destOffsetXGet() { return workBuffer[GWDestOffsetX]; } function destOffsetXPut(value) { return workBuffer[GWDestOffsetX] = value; } function destOffsetYGet() { return workBuffer[GWDestOffsetY]; } function destOffsetYPut(value) { return workBuffer[GWDestOffsetY] = value; } /* Display the span buffer at the current scan line. */ function displaySpanBufferAt(y) { var targetX0; var targetX1; var targetY; /* self aaLevelGet > 1 ifTrue:[self adjustAALevel]. */ targetX0 = SHR(spanStartGet(), aaShiftGet()); if (targetX0 < clipMinXGet()) { targetX0 = clipMinXGet(); } targetX1 = SHR(((spanEndGet() + aaLevelGet()) - 1), aaShiftGet()); if (targetX1 > clipMaxXGet()) { targetX1 = clipMaxXGet(); } targetY = SHR(y, aaShiftGet()); if ((targetY < clipMinYGet()) || ((targetY >= clipMaxYGet()) || ((targetX1 < clipMinXGet()) || (targetX0 >= clipMaxXGet())))) { return 0; } copyBitsFromtoat(targetX0, targetX1, targetY); } function edgeFillsInvalidate(edge) { return objectTypeOfput(edge, objectTypeOf(edge) | GEEdgeFillsInvalid); } function edgeFillsValidate(edge) { return objectTypeOfput(edge, objectTypeOf(edge) & -65537); } function edgeLeftFillOf(edge) { return objat(edge, GEFillIndexLeft); } function edgeLeftFillOfput(edge, value) { return objatput(edge, GEFillIndexLeft, value); } function edgeNumLinesOf(edge) { return objat(edge, GENumLines); } function edgeNumLinesOfput(edge, value) { return objatput(edge, GENumLines, value); } function edgeRightFillOf(edge) { return objat(edge, GEFillIndexRight); } function edgeRightFillOfput(edge, value) { return objatput(edge, GEFillIndexRight, value); } function edgeTransform() { return FPTR_ADD(workBuffer, GWEdgeTransform); } /* Return the edge type (e.g., witout the wide edge flag) */ function edgeTypeOf(edge) { return objectTypeOf(edge) >>> 1; } function edgeXValueOf(edge) { return objat(edge, GEXValue); } function edgeXValueOfput(edge, value) { return objatput(edge, GEXValue, value); } function edgeYValueOf(edge) { return objat(edge, GEYValue); } function edgeYValueOfput(edge, value) { return objatput(edge, GEYValue, value); } function edgeZValueOf(edge) { return objat(edge, GEZValue); } function edgeZValueOfput(edge, value) { return objatput(edge, GEZValue, value); } /* Fill the span buffer from leftX to rightX with the given fill. */ function fillAllFromto(leftX, rightX) { var fill; var startX; var stopX; fill = topFill(); startX = leftX; stopX = topRightX(); while (stopX < rightX) { fill = topFill(); if (fill !== 0) { if (fillSpanfromto(fill, startX, stopX)) { return true; } } quickRemoveInvalidFillsAt(stopX); startX = stopX; stopX = topRightX(); } fill = topFill(); if (fill !== 0) { return fillSpanfromto(fill, startX, rightX); } return false; } function fillBitmapSpan() { return fillBitmapSpanfromtoat(lastExportedFillGet(), lastExportedLeftXGet(), lastExportedRightXGet(), currentYGet()); } /* Fill the span buffer between leftEdge and rightEdge using the given bits. Note: We always start from zero - this avoids using huge bitmap buffers if the bitmap is to be displayed at the very far right hand side and also gives us a chance of using certain bitmaps (e.g., those with depth 32) directly. */ function fillBitmapSpanfromto(bits, leftX, rightX) { var baseShift; var bitX; var colorMask; var colorShift; var fillValue; var x; var x0; var x1; x0 = leftX; x1 = rightX; /* Hack for pre-increment */ bitX = -1; if (aaLevelGet() === 1) { /* Speedy version for no anti-aliasing */ while (x0 < x1) { fillValue = (bits[(++bitX)]|0); spanBuffer[x0] = fillValue; ++x0; } } else { /* Generic version with anti-aliasing */ colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); baseShift = aaShiftGet(); while (x0 < x1) { x = SHR(x0, baseShift); fillValue = (bits[(++bitX)]|0); fillValue = SHR((fillValue & colorMask), colorShift); spanBuffer[x] = (spanBuffer[x] + fillValue); ++x0; } } if (x1 > spanEndGet()) { spanEndPut(x1); } if (x1 > spanEndAAGet()) { spanEndAAPut(x1); } } function fillBitmapSpanfromtoat(bmFill, leftX, rightX, yValue) { var bits; var bmHeight; var bmWidth; var deltaX; var deltaY; var ds; var dsX; var dt; var dtX; var fillValue; var tileFlag; var x; var x1; var xp; var yp; if (aaLevelGet() !== 1) { return fillBitmapSpanAAfromtoat(bmFill, leftX, rightX, yValue); } bits = loadBitsFrom(bmFill); if (!bits) { return null; } bmWidth = bitmapWidthOf(bmFill); bmHeight = bitmapHeightOf(bmFill); tileFlag = bitmapTileFlagOf(bmFill) === 1; deltaX = leftX - fillOriginXOf(bmFill); deltaY = yValue - fillOriginYOf(bmFill); dsX = fillDirectionXOf(bmFill); dtX = fillNormalXOf(bmFill); ds = (deltaX * dsX) + (deltaY * fillDirectionYOf(bmFill)); dt = (deltaX * dtX) + (deltaY * fillNormalYOf(bmFill)); x = leftX; x1 = rightX; while (x < x1) { if (tileFlag) { ds = repeatValuemax(ds, bmWidth << 16); dt = repeatValuemax(dt, bmHeight << 16); } xp = ds >> 16; yp = dt >> 16; if (!tileFlag) { xp = clampValuemax(xp, bmWidth); yp = clampValuemax(yp, bmHeight); } if ((xp >= 0) && ((yp >= 0) && ((xp < bmWidth) && (yp < bmHeight)))) { fillValue = bitmapValuebitsatXy(bmFill, bits, xp, yp); spanBuffer[x] = fillValue; } ds += dsX; dt += dtX; ++x; } } function fillBitmapSpanAAfromtoat(bmFill, leftX, rightX, yValue) { var aaLevel; var baseShift; var bits; var bmHeight; var bmWidth; var cMask; var cShift; var deltaX; var deltaY; var ds; var dsX; var dt; var dtX; var fillValue; var firstPixel; var idx; var lastPixel; var tileFlag; var x; var xp; var yp; bits = loadBitsFrom(bmFill); if (!bits) { return null; } bmWidth = bitmapWidthOf(bmFill); bmHeight = bitmapHeightOf(bmFill); tileFlag = bitmapTileFlagOf(bmFill) === 1; deltaX = leftX - fillOriginXOf(bmFill); deltaY = yValue - fillOriginYOf(bmFill); dsX = fillDirectionXOf(bmFill); dtX = fillNormalXOf(bmFill); ds = (deltaX * dsX) + (deltaY * fillDirectionYOf(bmFill)); dt = (deltaX * dtX) + (deltaY * fillNormalYOf(bmFill)); aaLevel = aaLevelGet(); firstPixel = aaFirstPixelFromto(leftX, rightX); lastPixel = aaLastPixelFromto(leftX, rightX); baseShift = aaShiftGet(); cMask = aaColorMaskGet(); cShift = aaColorShiftGet(); x = leftX; while (x < firstPixel) { if (tileFlag) { ds = repeatValuemax(ds, bmWidth << 16); dt = repeatValuemax(dt, bmHeight << 16); } xp = ds >> 16; yp = dt >> 16; if (!tileFlag) { xp = clampValuemax(xp, bmWidth); yp = clampValuemax(yp, bmHeight); } if ((xp >= 0) && ((yp >= 0) && ((xp < bmWidth) && (yp < bmHeight)))) { fillValue = bitmapValuebitsatXy(bmFill, bits, xp, yp); fillValue = SHR((fillValue & cMask), cShift); idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + fillValue); } ds += dsX; dt += dtX; ++x; } cMask = (SHR(aaColorMaskGet(), aaShiftGet())) | 4042322160; cShift = aaShiftGet(); while (x < lastPixel) { if (tileFlag) { ds = repeatValuemax(ds, bmWidth << 16); dt = repeatValuemax(dt, bmHeight << 16); } xp = ds >> 16; yp = dt >> 16; if (!tileFlag) { xp = clampValuemax(xp, bmWidth); yp = clampValuemax(yp, bmHeight); } if ((xp >= 0) && ((yp >= 0) && ((xp < bmWidth) && (yp < bmHeight)))) { fillValue = bitmapValuebitsatXy(bmFill, bits, xp, yp); fillValue = SHR((fillValue & cMask), cShift); idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + fillValue); } ds += SHL(dsX, cShift); dt += SHL(dtX, cShift); x += aaLevel; } cMask = aaColorMaskGet(); cShift = aaColorShiftGet(); while (x < rightX) { if (tileFlag) { ds = repeatValuemax(ds, bmWidth << 16); dt = repeatValuemax(dt, bmHeight << 16); } xp = ds >> 16; yp = dt >> 16; if (!tileFlag) { xp = clampValuemax(xp, bmWidth); yp = clampValuemax(yp, bmHeight); } if ((xp >= 0) && ((yp >= 0) && ((xp < bmWidth) && (yp < bmHeight)))) { fillValue = bitmapValuebitsatXy(bmFill, bits, xp, yp); fillValue = SHR((fillValue & cMask), cShift); idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + fillValue); } ds += dsX; dt += dtX; ++x; } } /* Fill the span buffer between leftEdge and rightEdge with the given pixel value. */ function fillColorSpanfromto(pixelValue32, leftX, rightX) { var x0; var x1; /* Use a unrolled version for anti-aliased fills... */ if (aaLevelGet() !== 1) { return fillColorSpanAAx0x1(pixelValue32, leftX, rightX); } x0 = leftX; /* Unroll the inner loop four times, since we're only storing data. */ x1 = rightX; while ((x0 + 4) < x1) { spanBuffer[x0] = pixelValue32; spanBuffer[x0 + 1] = pixelValue32; spanBuffer[x0 + 2] = pixelValue32; spanBuffer[x0 + 3] = pixelValue32; x0 += 4; } while (x0 < x1) { spanBuffer[x0] = pixelValue32; ++x0; } } /* This is the inner loop for solid color fills with anti-aliasing. This loop has been unrolled for speed and quality into three parts: a) copy all pixels that fall into the first full pixel. b) copy aaLevel pixels between the first and the last full pixel c) copy all pixels that fall in the last full pixel */ function fillColorSpanAAx0x1(pixelValue32, leftX, rightX) { var aaLevel; var baseShift; var colorMask; var firstPixel; var idx; var lastPixel; var pv32; var x; /* Not now -- maybe later */ /* Compute the pixel boundaries. */ firstPixel = aaFirstPixelFromto(leftX, rightX); lastPixel = aaLastPixelFromto(leftX, rightX); aaLevel = aaLevelGet(); baseShift = aaShiftGet(); /* Part a: Deal with the first n sub-pixels */ x = leftX; if (x < firstPixel) { pv32 = SHR((pixelValue32 & aaColorMaskGet()), aaColorShiftGet()); while (x < firstPixel) { idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + pv32); ++x; } } if (x < lastPixel) { colorMask = (SHR(aaColorMaskGet(), aaShiftGet())) | 4042322160; pv32 = SHR((pixelValue32 & colorMask), aaShiftGet()); while (x < lastPixel) { idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + pv32); x += aaLevel; } } if (x < rightX) { pv32 = SHR((pixelValue32 & aaColorMaskGet()), aaColorShiftGet()); while (x < rightX) { idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + pv32); ++x; } } } function fillDirectionXOf(fill) { return objat(fill, GFDirectionX); } function fillDirectionXOfput(fill, value) { return objatput(fill, GFDirectionX, value); } function fillDirectionYOf(fill) { return objat(fill, GFDirectionY); } function fillDirectionYOfput(fill, value) { return objatput(fill, GFDirectionY, value); } function fillLinearGradient() { return fillLinearGradientfromtoat(lastExportedFillGet(), lastExportedLeftXGet(), lastExportedRightXGet(), currentYGet()); } /* Draw a linear gradient fill. */ function fillLinearGradientfromtoat(fill, leftX, rightX, yValue) { var ds; var dsX; var ramp; var rampIndex; var rampSize; var x; var x0; var x1; ramp = gradientRampOf(fill); rampSize = gradientRampLengthOf(fill); dsX = fillDirectionXOf(fill); ds = ((leftX - fillOriginXOf(fill)) * dsX) + ((yValue - fillOriginYOf(fill)) * fillDirectionYOf(fill)); x = (x0 = leftX); /* Note: The inner loop has been divided into three parts for speed */ /* Part one: Fill everything outside the left boundary */ x1 = rightX; while (((((rampIndex = ds >> 16)) < 0) || (rampIndex >= rampSize)) && (x < x1)) { ++x; ds += dsX; } if (x > x0) { if (rampIndex < 0) { rampIndex = 0; } if (rampIndex >= rampSize) { rampIndex = rampSize - 1; } fillColorSpanfromto(ramp[rampIndex], x0, x); } if (aaLevelGet() === 1) { /* Fast version w/o anti-aliasing */ while (((((rampIndex = ds >> 16)) < rampSize) && (rampIndex >= 0)) && (x < x1)) { spanBuffer[x] = ramp[rampIndex]; ++x; ds += dsX; } } else { x = fillLinearGradientAArampdsdsXfromto(fill, ramp, ds, dsX, x, rightX); } if (x < x1) { if (rampIndex < 0) { rampIndex = 0; } if (rampIndex >= rampSize) { rampIndex = rampSize - 1; } fillColorSpanfromto(ramp[rampIndex], x, x1); } } /* This is the AA version of linear gradient filling. */ function fillLinearGradientAArampdsdsXfromto(fill, ramp, deltaS, dsX, leftX, rightX) { var aaLevel; var baseShift; var colorMask; var colorShift; var ds; var firstPixel; var idx; var lastPixel; var rampIndex; var rampSize; var rampValue; var x; aaLevel = aaLevelGet(); baseShift = aaShiftGet(); rampSize = gradientRampLengthOf(fill); ds = deltaS; x = leftX; rampIndex = ds >> 16; firstPixel = aaFirstPixelFromto(leftX, rightX); /* Deal with the first n sub-pixels */ lastPixel = aaLastPixelFromto(leftX, rightX); colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); while ((x < firstPixel) && ((rampIndex < rampSize) && (rampIndex >= 0))) { rampValue = ramp[rampIndex]; /* Copy as many pixels as possible */ rampValue = SHR((rampValue & colorMask), colorShift); while ((x < firstPixel) && ((ds >> 16) === rampIndex)) { idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + rampValue); ++x; ds += dsX; } rampIndex = ds >> 16; } colorMask = (SHR(aaColorMaskGet(), aaShiftGet())) | 4042322160; colorShift = aaShiftGet(); while ((x < lastPixel) && ((rampIndex < rampSize) && (rampIndex >= 0))) { rampValue = ramp[rampIndex]; /* Copy as many pixels as possible */ rampValue = SHR((rampValue & colorMask), colorShift); while ((x < lastPixel) && ((ds >> 16) === rampIndex)) { idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + rampValue); x += aaLevel; ds += SHL(dsX, colorShift); } rampIndex = ds >> 16; } colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); while ((x < rightX) && ((rampIndex < rampSize) && (rampIndex >= 0))) { rampValue = ramp[rampIndex]; /* Copy as many pixels as possible */ rampValue = SHR((rampValue & colorMask), colorShift); while ((x < rightX) && ((ds >> 16) === rampIndex)) { idx = SHR(x, baseShift); spanBuffer[idx] = (spanBuffer[idx] + rampValue); ++x; ds += dsX; } rampIndex = ds >> 16; } return x; } function fillMaxXGet() { return workBuffer[GWFillMaxX]; } function fillMaxXPut(value) { return workBuffer[GWFillMaxX] = value; } function fillMaxYGet() { return workBuffer[GWFillMaxY]; } function fillMaxYPut(value) { return workBuffer[GWFillMaxY] = value; } function fillMinXGet() { return workBuffer[GWFillMinX]; } function fillMinXPut(value) { return workBuffer[GWFillMinX] = value; } function fillMinYGet() { return workBuffer[GWFillMinY]; } function fillMinYPut(value) { return workBuffer[GWFillMinY] = value; } function fillNormalXOf(fill) { return objat(fill, GFNormalX); } function fillNormalXOfput(fill, value) { return objatput(fill, GFNormalX, value); } function fillNormalYOf(fill) { return objat(fill, GFNormalY); } function fillNormalYOfput(fill, value) { return objatput(fill, GFNormalY, value); } function fillOriginXOf(fill) { return objat(fill, GFOriginX); } function fillOriginXOfput(fill, value) { return objatput(fill, GFOriginX, value); } function fillOriginYOf(fill) { return objat(fill, GFOriginY); } function fillOriginYOfput(fill, value) { return objatput(fill, GFOriginY, value); } /* Part 2a) Compute the decreasing part of the ramp */ function fillRadialDecreasingrampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, leftX, rightX) { var ds; var dt; var length2; var nextLength; var rampIndex; var rampValue; var x; var x1; ds = (deltaST[0]|0); dt = (deltaST[1]|0); rampIndex = accurateLengthOfwith(ds >> 16, dt >> 16); rampValue = ramp[rampIndex]; length2 = (rampIndex - 1) * (rampIndex - 1); x = leftX; x1 = rightX; if (x1 > fillOriginXOf(fill)) { x1 = fillOriginXOf(fill); } while (x < x1) { /* Try to copy the current value more than just once */ while ((x < x1) && (squaredLengthOfwith(ds >> 16, dt >> 16) >= length2)) { spanBuffer[x] = rampValue; ++x; ds += dsX; dt += dtX; } nextLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (nextLength < length2) { --rampIndex; rampValue = ramp[rampIndex]; length2 = (rampIndex - 1) * (rampIndex - 1); } } deltaST[0] = ds; deltaST[1] = dt; return x; } /* Part 2a) Compute the decreasing part of the ramp */ function fillRadialDecreasingAArampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, leftX, rightX) { var aaLevel; var baseShift; var colorMask; var colorShift; var ds; var dt; var firstPixel; var index; var lastPixel; var length2; var nextLength; var rampIndex; var rampValue; var x; var x1; ds = (deltaST[0]|0); dt = (deltaST[1]|0); aaLevel = aaLevelGet(); baseShift = aaShiftGet(); rampIndex = accurateLengthOfwith(ds >> 16, dt >> 16); length2 = (rampIndex - 1) * (rampIndex - 1); x = leftX; x1 = fillOriginXOf(fill); if (x1 > rightX) { x1 = rightX; } firstPixel = aaFirstPixelFromto(leftX, x1); /* Deal with the first n sub-pixels */ lastPixel = aaLastPixelFromto(leftX, x1); if (x < firstPixel) { colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); while (x < firstPixel) { /* Try to copy the current value more than just once */ while ((x < firstPixel) && (squaredLengthOfwith(ds >> 16, dt >> 16) >= length2)) { index = SHR(x, baseShift); spanBuffer[index] = (spanBuffer[index] + rampValue); ++x; ds += dsX; dt += dtX; } nextLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (nextLength < length2) { --rampIndex; rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); length2 = (rampIndex - 1) * (rampIndex - 1); } } } if (x < lastPixel) { colorMask = (SHR(aaColorMaskGet(), aaShiftGet())) | 4042322160; colorShift = aaShiftGet(); rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); while (x < lastPixel) { /* Try to copy the current value more than just once */ while ((x < lastPixel) && (squaredLengthOfwith(ds >> 16, dt >> 16) >= length2)) { index = SHR(x, baseShift); spanBuffer[index] = (spanBuffer[index] + rampValue); x += aaLevel; ds += SHL(dsX, colorShift); dt += SHL(dtX, colorShift); } nextLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (nextLength < length2) { --rampIndex; rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); length2 = (rampIndex - 1) * (rampIndex - 1); } } } if (x < x1) { colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); while (x < x1) { /* Try to copy the current value more than just once */ while ((x < x1) && (squaredLengthOfwith(ds >> 16, dt >> 16) >= length2)) { index = SHR(x, baseShift); spanBuffer[index] = (spanBuffer[index] + rampValue); ++x; ds += dsX; dt += dtX; } nextLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (nextLength < length2) { --rampIndex; rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); length2 = (rampIndex - 1) * (rampIndex - 1); } } } deltaST[0] = ds; deltaST[1] = dt; return x; } function fillRadialGradient() { return fillRadialGradientfromtoat(lastExportedFillGet(), lastExportedLeftXGet(), lastExportedRightXGet(), currentYGet()); } /* Draw a radial gradient fill. */ function fillRadialGradientfromtoat(fill, leftX, rightX, yValue) { var deltaST; var deltaX; var deltaY; var ds; var dsX; var dt; var dtX; var length2; var ramp; var rampSize; var x; var x1; ramp = gradientRampOf(fill); rampSize = gradientRampLengthOf(fill); deltaX = leftX - fillOriginXOf(fill); deltaY = yValue - fillOriginYOf(fill); dsX = fillDirectionXOf(fill); dtX = fillNormalXOf(fill); ds = (deltaX * dsX) + (deltaY * fillDirectionYOf(fill)); dt = (deltaX * dtX) + (deltaY * fillNormalYOf(fill)); x = leftX; /* Note: The inner loop has been divided into three parts for speed */ /* Part one: Fill everything outside the left boundary */ x1 = rightX; /* This is the upper bound */ length2 = (rampSize - 1) * (rampSize - 1); while ((squaredLengthOfwith(ds >> 16, dt >> 16) >= length2) && (x < x1)) { ++x; ds += dsX; dt += dtX; } if (x > leftX) { fillColorSpanfromto(ramp[rampSize - 1], leftX, x); } deltaST = point1Get(); deltaST[0] = ds; deltaST[1] = dt; if (x < fillOriginXOf(fill)) { /* Draw the decreasing part */ if (aaLevelGet() === 1) { x = fillRadialDecreasingrampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, x, x1); } else { x = fillRadialDecreasingAArampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, x, x1); } } if (x < x1) { /* Draw the increasing part */ if (aaLevelGet() === 1) { x = fillRadialIncreasingrampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, x, x1); } else { x = fillRadialIncreasingAArampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, x, x1); } } if (x < rightX) { fillColorSpanfromto(ramp[rampSize - 1], x, rightX); } } /* Part 2b) Compute the increasing part of the ramp */ function fillRadialIncreasingrampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, leftX, rightX) { var ds; var dt; var lastLength; var length2; var nextLength; var rampIndex; var rampSize; var rampValue; var x; var x1; ds = (deltaST[0]|0); dt = (deltaST[1]|0); rampIndex = accurateLengthOfwith(ds >> 16, dt >> 16); rampValue = ramp[rampIndex]; rampSize = gradientRampLengthOf(fill); /* This is the upper bound */ length2 = (rampSize - 1) * (rampSize - 1); nextLength = (rampIndex + 1) * (rampIndex + 1); lastLength = squaredLengthOfwith(ds >> 16, dt >> 16); x = leftX; x1 = rightX; while ((x < x1) && (lastLength < length2)) { /* Try to copy the current value more than once */ while ((x < x1) && (squaredLengthOfwith(ds >> 16, dt >> 16) <= nextLength)) { spanBuffer[x] = rampValue; ++x; ds += dsX; dt += dtX; } lastLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (lastLength > nextLength) { ++rampIndex; rampValue = ramp[rampIndex]; nextLength = (rampIndex + 1) * (rampIndex + 1); } } deltaST[0] = ds; deltaST[1] = dt; return x; } /* Part 2b) Compute the increasing part of the ramp */ function fillRadialIncreasingAArampdeltaSTdsXdtXfromto(fill, ramp, deltaST, dsX, dtX, leftX, rightX) { var aaLevel; var baseShift; var colorMask; var colorShift; var ds; var dt; var firstPixel; var index; var lastLength; var lastPixel; var length2; var nextLength; var rampIndex; var rampSize; var rampValue; var x; ds = (deltaST[0]|0); dt = (deltaST[1]|0); aaLevel = aaLevelGet(); baseShift = aaShiftGet(); rampIndex = accurateLengthOfwith(ds >> 16, dt >> 16); rampSize = gradientRampLengthOf(fill); /* This is the upper bound */ length2 = (rampSize - 1) * (rampSize - 1); nextLength = (rampIndex + 1) * (rampIndex + 1); lastLength = squaredLengthOfwith(ds >> 16, dt >> 16); x = leftX; firstPixel = aaFirstPixelFromto(leftX, rightX); /* Deal with the first n subPixels */ lastPixel = aaLastPixelFromto(leftX, rightX); if ((x < firstPixel) && (lastLength < length2)) { colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); while ((x < firstPixel) && (lastLength < length2)) { /* Try to copy the current value more than once */ while ((x < firstPixel) && (squaredLengthOfwith(ds >> 16, dt >> 16) <= nextLength)) { index = SHR(x, baseShift); spanBuffer[index] = (spanBuffer[index] + rampValue); ++x; ds += dsX; dt += dtX; } lastLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (lastLength > nextLength) { ++rampIndex; rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); nextLength = (rampIndex + 1) * (rampIndex + 1); } } } if ((x < lastPixel) && (lastLength < length2)) { colorMask = (SHR(aaColorMaskGet(), aaShiftGet())) | 4042322160; colorShift = aaShiftGet(); rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); while ((x < lastPixel) && (lastLength < length2)) { /* Try to copy the current value more than once */ while ((x < lastPixel) && (squaredLengthOfwith(ds >> 16, dt >> 16) <= nextLength)) { index = SHR(x, baseShift); spanBuffer[index] = (spanBuffer[index] + rampValue); x += aaLevel; ds += SHL(dsX, colorShift); dt += SHL(dtX, colorShift); } lastLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (lastLength > nextLength) { ++rampIndex; rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); nextLength = (rampIndex + 1) * (rampIndex + 1); } } } if ((x < rightX) && (lastLength < length2)) { colorMask = aaColorMaskGet(); colorShift = aaColorShiftGet(); rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); while ((x < rightX) && (lastLength < length2)) { /* Try to copy the current value more than once */ while ((x < rightX) && (squaredLengthOfwith(ds >> 16, dt >> 16) <= nextLength)) { index = SHR(x, baseShift); spanBuffer[index] = (spanBuffer[index] + rampValue); ++x; ds += dsX; dt += dtX; } lastLength = squaredLengthOfwith(ds >> 16, dt >> 16); while (lastLength > nextLength) { ++rampIndex; rampValue = ramp[rampIndex]; rampValue = SHR((rampValue & colorMask), colorShift); nextLength = (rampIndex + 1) * (rampIndex + 1); } } } deltaST[0] = ds; deltaST[1] = dt; return x; } /* Return true if fillEntry1 should be drawn before fillEntry2 */ function fillSortsbefore(fillEntry1, fillEntry2) { var diff; /* First check the depth value */ diff = stackFillDepth(fillEntry1) - stackFillDepth(fillEntry2); if (diff !== 0) { return diff > 0; } return (stackFillValue(fillEntry1)>>>0) < (stackFillValue(fillEntry2)>>>0); } /* Fill the span buffer from leftX to rightX with the given fill. Clip before performing any operations. Return true if the fill must be handled by some Smalltalk code. */ function fillSpanfromto(fill, leftX, rightX) { var type; var x0; var x1; if (fill === 0) { return false; } if (leftX < spanEndAAGet()) { x0 = spanEndAAGet(); } else { x0 = leftX; } if (rightX > (SHL(spanSizeGet(), aaShiftGet()))) { x1 = SHL(spanSizeGet(), aaShiftGet()); } else { x1 = rightX; } if (x0 < fillMinXGet()) { x0 = fillMinXGet(); } if (x1 > fillMaxXGet()) { x1 = fillMaxXGet(); } if (x0 < spanStartGet()) { spanStartPut(x0); } if (x1 > spanEndGet()) { spanEndPut(x1); } if (x1 > spanEndAAGet()) { spanEndAAPut(x1); } if (x0 >= x1) { return false; } if (isFillColor(fill)) { fillColorSpanfromto(fill, x0, x1); } else { /* Store the values for the dispatch */ lastExportedFillPut(fill); lastExportedLeftXPut(x0); lastExportedRightXPut(x1); type = fillTypeOf(fill); if (type <= 1) { return true; } switch (type) { case 0: case 1: break; case 2: fillLinearGradient(); break; case 3: fillRadialGradient(); break; case 4: case 5: fillBitmapSpan(); break; } } return false; } function fillTypeOf(fill) { return (objectTypeOf(fill) & GEPrimitiveFillMask) >>> 8; } /* Check the global edge table for any entries that cannot be handled by the engine itself. If there are any, return true. Otherwise, initialize the the edge and add it to the AET */ function findNextExternalEntryFromGET() { var edge; var type; var yValue; /* As long as we have entries in the GET */ yValue = currentYGet(); while (getStartGet() < getUsedGet()) { edge = getBuffer[getStartGet()]; if (edgeYValueOf(edge) > yValue) { return false; } type = objectTypeOf(edge); if ((type & GEPrimitiveWideMask) === GEPrimitiveEdge) { return true; } if (!needAvailableSpace(1)) { return false; } switch (type) { case 0: case 1: case 2: case 3: break; case 4: stepToFirstLine(); break; case 5: stepToFirstWideLine(); break; case 6: stepToFirstBezier(); break; case 7: stepToFirstWideBezier(); break; } insertEdgeIntoAET(edge); getStartPut(getStartGet() + 1); } return false; } /* Scan the active edge table. If there is any fill that cannot be handled by the engine itself, return true. Otherwise handle the fills and return false. */ /* self currentYGet >= 680 ifTrue:[ self printAET. self halt. ]. */ function findNextExternalFillFromAET() { var leftEdge; var leftX; var rightEdge; var rightX; leftX = (rightX = fillMaxXGet()); while (aetStartGet() < aetUsedGet()) { /* TODO: We should check if leftX from last operation is greater than leftX from next edge. Currently, we rely here on spanEndAA from the span buffer fill. */ leftEdge = (rightEdge = aetBuffer[aetStartGet()]); leftX = (rightX = edgeXValueOf(leftEdge)); if (leftX >= fillMaxXGet()) { return false; } quickRemoveInvalidFillsAt(leftX); if (isWide(leftEdge)) { toggleWideFillOf(leftEdge); } if (areEdgeFillsValid(leftEdge)) { toggleFillsOf(leftEdge); if (engineStopped) { return false; } } aetStartPut(aetStartGet() + 1); if (aetStartGet() < aetUsedGet()) { rightEdge = aetBuffer[aetStartGet()]; rightX = edgeXValueOf(rightEdge); if (rightX >= fillMinXGet()) { /* This is the visible portion */ fillAllFromto(leftX, rightX); } } } if (rightX < fillMaxXGet()) { fillAllFromto(rightX, fillMaxXGet()); } return false; } /* Check the active edge table for any entries that cannot be handled by the engine itself. If there are any, return true. Otherwise, step the the edge to the next y value. */ function findNextExternalUpdateFromAET() { var count; var edge; var type; while (aetStartGet() < aetUsedGet()) { edge = aetBuffer[aetStartGet()]; count = edgeNumLinesOf(edge) - 1; if (count === 0) { /* Edge at end -- remove it */ removeFirstAETEntry(); } else { /* Store remaining lines back */ edgeNumLinesOfput(edge, count); type = objectTypeOf(edge); if ((type & GEPrimitiveWideMask) === GEPrimitiveEdge) { return true; } switch (type) { case 0: case 1: case 2: case 3: break; case 4: stepToNextLine(); break; case 5: stepToNextWideLine(); break; case 6: stepToNextBezier(); break; case 7: stepToNextWideBezier(); break; } resortFirstAETEntry(); aetStartPut(aetStartGet() + 1); } } return false; } function findStackFilldepth(fillIndex, depth) { var index; index = 0; while ((index < stackFillSize()) && ((stackFillValue(index) !== fillIndex) || (stackFillDepth(index) !== depth))) { index += stackFillEntryLength(); } if (index >= stackFillSize()) { return -1; } else { return index; } } /* Return true if processing is finished */ function finishedProcessing() { return stateGet() === GEStateCompleted; } function freeStackFillEntry() { wbStackPop(stackFillEntryLength()); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Return true if the edge at index i should sort before the edge at index j. */ function getSortsbefore(edge1, edge2) { var diff; if (edge1 === edge2) { return true; } diff = edgeYValueOf(edge1) - edgeYValueOf(edge2); if (diff !== 0) { return diff < 0; } diff = edgeXValueOf(edge1) - edgeXValueOf(edge2); return diff < 0; } function getStartGet() { return workBuffer[GWGETStart]; } function getStartPut(value) { return workBuffer[GWGETStart] = value; } function getUsedGet() { return workBuffer[GWGETUsed]; } function getUsedPut(value) { return workBuffer[GWGETUsed] = value; } function gradientRampLengthOf(fill) { return objat(fill, GFRampLength); } function gradientRampLengthOfput(fill, value) { return objatput(fill, GFRampLength, value); } function gradientRampOf(fill) { return PTR_ADD(objBuffer, fill + GFRampOffset); } function hasColorTransform() { return hasColorTransformGet() !== 0; } function hasColorTransformGet() { return workBuffer[GWHasColorTransform]; } function hasColorTransformPut(value) { return workBuffer[GWHasColorTransform] = value; } function hasEdgeTransform() { return hasEdgeTransformGet() !== 0; } function hasEdgeTransformGet() { return workBuffer[GWHasEdgeTransform]; } function hasEdgeTransformPut(value) { return workBuffer[GWHasEdgeTransform] = value; } /* Make the fill style with the given index invisible */ function hideFilldepth(fillIndex, depth) { var index; var newDepth; var newRightX; var newTop; var newTopIndex; index = findStackFilldepth(fillIndex, depth); if (index === -1) { return false; } if (index === 0) { freeStackFillEntry(); return true; } stackFillValueput(index, stackFillValue(0)); stackFillDepthput(index, stackFillDepth(0)); stackFillRightXput(index, stackFillRightX(0)); freeStackFillEntry(); if (stackFillSize() <= stackFillEntryLength()) { return true; } newTopIndex = 0; index = stackFillEntryLength(); while (index < stackFillSize()) { if (fillSortsbefore(index, newTopIndex)) { newTopIndex = index; } index += stackFillEntryLength(); } if ((newTopIndex + stackFillEntryLength()) === stackFillSize()) { return true; } newTop = stackFillValue(newTopIndex); stackFillValueput(newTopIndex, topFillValue()); topFillValuePut(newTop); newDepth = stackFillDepth(newTopIndex); stackFillDepthput(newTopIndex, topFillDepth()); topFillDepthPut(newDepth); newRightX = stackFillRightX(newTopIndex); stackFillRightXput(newTopIndex, topFillRightX()); topFillRightXPut(newRightX); return true; } function incrementStatby(statIndex, value) { return workBuffer[statIndex] = (workBuffer[statIndex] + value); } /* Find insertion point for the given edge in the AET */ function indexForInsertingIntoAET(edge) { var index; var initialX; initialX = edgeXValueOf(edge); index = 0; while ((index < aetUsedGet()) && (edgeXValueOf(aetBuffer[index]) < initialX)) { ++index; } while ((index < aetUsedGet()) && ((edgeXValueOf(aetBuffer[index]) === initialX) && (getSortsbefore(aetBuffer[index], edge)))) { ++index; } return index; } function initColorTransform() { var transform; transform = colorTransform(); transform[0] = 1.0; transform[1] = 0.0; transform[2] = 1.0; transform[3] = 0.0; transform[4] = 1.0; transform[5] = 0.0; transform[6] = 1.0; transform[7] = 0.0; hasColorTransformPut(0); } function initEdgeTransform() { var transform; transform = edgeTransform(); transform[0] = 1.0; transform[1] = 0.0; transform[2] = 0.0; transform[3] = 0.0; transform[4] = 1.0; transform[5] = 0.0; hasEdgeTransformPut(0); } function initialiseModule() { loadBBFn = interpreterProxy.ioLoadFunctionFrom("loadBitBltFrom", bbPluginName); copyBitsFn = interpreterProxy.ioLoadFunctionFrom("copyBitsFromtoat", bbPluginName); return (!!loadBBFn) && (!!copyBitsFn); } /* Initialization stuff that needs to be done before any processing can take place. */ /* Make sure aaLevel is initialized */ function initializeGETProcessing() { setAALevel(aaLevelGet()); if (clipMinXGet() < 0) { clipMinXPut(0); } if (clipMaxXGet() > spanSizeGet()) { clipMaxXPut(spanSizeGet()); } fillMinXPut(SHL(clipMinXGet(), aaShiftGet())); fillMinYPut(SHL(clipMinYGet(), aaShiftGet())); fillMaxXPut(SHL(clipMaxXGet(), aaShiftGet())); fillMaxYPut(SHL(clipMaxYGet(), aaShiftGet())); getUsedPut(0); aetUsedPut(0); getBuffer = PTR_ADD(objBuffer, objUsed); /* Create the global edge table */ aetBuffer = PTR_ADD(objBuffer, objUsed); createGlobalEdgeTable(); if (engineStopped) { return null; } if (getUsedGet() === 0) { /* Nothing to do */ currentYPut(fillMaxYGet()); return 0; } sortGlobalEdgeTable(); currentYPut(edgeYValueOf(getBuffer[0])); if (currentYGet() < fillMinYGet()) { currentYPut(fillMinYGet()); } spanStartPut(0); spanEndPut((SHL(spanSizeGet(), aaShiftGet())) - 1); clearSpanBuffer(); } /* Insert the edge with the given index from the global edge table into the active edge table. The edge has already been stepped to the initial yValue -- thus remainingLines and rasterX are both set. */ function insertEdgeIntoAET(edge) { var index; /* Check for the number of lines remaining */ if (edgeNumLinesOf(edge) <= 0) { return null; } /* And insert edge */ index = indexForInsertingIntoAET(edge); insertToAETbeforeIndex(edge, index); } /* Insert the given edge into the AET. */ function insertToAETbeforeIndex(edge, index) { var i; /* Make sure we have space in the AET */ if (!allocateAETEntry(1)) { return null; } i = aetUsedGet() - 1; while (!(i < index)) { aetBuffer[i + 1] = aetBuffer[i]; --i; } aetBuffer[index] = edge; aetUsedPut(aetUsedGet() + 1); } function isBezier(bezier) { return (objectTypeOf(bezier) & GEPrimitiveWideMask) === GEPrimitiveBezier; } function isEdge(edge) { var type; type = objectTypeOf(edge); if (type > GEPrimitiveEdgeMask) { return false; } return (objectTypeOf(edge) & GEPrimitiveEdgeMask) !== 0; } function isFill(fill) { return isFillColor(fill) || (isRealFill(fill)); } function isFillColor(fill) { return (fill & 4278190080) !== 0; } function isFillOkay(fill) { return (fill === 0) || (isFillColor(fill) || (isObject(fill) && (isFill(fill)))); } function isLine(line) { return (objectTypeOf(line) & GEPrimitiveWideMask) === GEPrimitiveLine; } function isObject(obj) { return (obj >= 0) && (obj < objUsed); } function isRealFill(fill) { return (objectTypeOf(fill) & GEPrimitiveFillMask) !== 0; } function isWide(object) { return (objectTypeOf(object) & GEPrimitiveWide) !== 0; } function lastExportedEdgeGet() { return workBuffer[GWLastExportedEdge]; } function lastExportedEdgePut(value) { return workBuffer[GWLastExportedEdge] = value; } function lastExportedFillGet() { return workBuffer[GWLastExportedFill]; } function lastExportedFillPut(value) { return workBuffer[GWLastExportedFill] = value; } function lastExportedLeftXGet() { return workBuffer[GWLastExportedLeftX]; } function lastExportedLeftXPut(value) { return workBuffer[GWLastExportedLeftX] = value; } function lastExportedRightXGet() { return workBuffer[GWLastExportedRightX]; } function lastExportedRightXPut(value) { return workBuffer[GWLastExportedRightX] = value; } function lineEndXOf(line) { return objat(line, GLEndX); } function lineEndXOfput(line, value) { return objatput(line, GLEndX, value); } function lineEndYOf(line) { return objat(line, GLEndY); } function lineEndYOfput(line, value) { return objatput(line, GLEndY, value); } function lineErrorAdjDownOf(line) { return objat(line, GLErrorAdjDown); } function lineErrorAdjDownOfput(line, value) { return objatput(line, GLErrorAdjDown, value); } function lineErrorAdjUpOf(line) { return objat(line, GLErrorAdjUp); } function lineErrorAdjUpOfput(line, value) { return objatput(line, GLErrorAdjUp, value); } function lineErrorOf(line) { return objat(line, GLError); } function lineErrorOfput(line, value) { return objatput(line, GLError, value); } function lineXDirectionOf(line) { return objat(line, GLXDirection); } function lineXDirectionOfput(line, value) { return objatput(line, GLXDirection, value); } function lineXIncrementOf(line) { return objat(line, GLXIncrement); } function lineXIncrementOfput(line, value) { return objatput(line, GLXIncrement, value); } function lineYDirectionOfput(line, value) { return objatput(line, GLYDirection, value); } /* Load and subdivide the bezier curve from point1/point2/point3. If wideFlag is set then make sure the curve is monoton in X. */ function loadAndSubdivideBezierFromviatoisWide(point1, point2, point3, wideFlag) { var bz1; var bz2; var index; var index1; var index2; bz1 = allocateBezierStackEntry(); if (engineStopped) { return 0; } bzStartXput(bz1, point1[0]); bzStartYput(bz1, point1[1]); bzViaXput(bz1, point2[0]); bzViaYput(bz1, point2[1]); bzEndXput(bz1, point3[0]); bzEndYput(bz1, point3[1]); index2 = (bz2 = subdivideToBeMonotoninX(bz1, wideFlag)); for (index = bz1; index <= bz2; index += 6) { index1 = subdivideBezierFrom(index); if (index1 > index2) { index2 = index1; } if (engineStopped) { return 0; } } return DIV(index2, 6); } function loadArrayPolygonnPointsfilllineWidthlineFill(points, nPoints, fillIndex, lineWidth, lineFill) { var i; var x0; var x1; var y0; var y1; loadPointfrom(point1Get(), interpreterProxy.fetchPointerofObject(0, points)); if (interpreterProxy.failed()) { return null; } x0 = point1Get()[0]; y0 = point1Get()[1]; for (i = 1; i <= (nPoints - 1); i++) { loadPointfrom(point1Get(), interpreterProxy.fetchPointerofObject(i, points)); if (interpreterProxy.failed()) { return null; } x1 = point1Get()[0]; y1 = point1Get()[1]; point1Get()[0] = x0; point1Get()[1] = y0; point2Get()[0] = x1; point2Get()[1] = y1; transformPoints(2); loadWideLinefromtolineFillleftFillrightFill(lineWidth, point1Get(), point2Get(), lineFill, fillIndex, 0); if (engineStopped) { return null; } x0 = x1; y0 = y1; } } function loadArrayShapenSegmentsfilllineWidthlineFill(points, nSegments, fillIndex, lineWidth, lineFill) { var i; var pointOop; var segs; var x0; var x1; var x2; var y0; var y1; var y2; for (i = 0; i <= (nSegments - 1); i++) { pointOop = interpreterProxy.fetchPointerofObject(i * 3, points); loadPointfrom(point1Get(), pointOop); pointOop = interpreterProxy.fetchPointerofObject((i * 3) + 1, points); loadPointfrom(point2Get(), pointOop); pointOop = interpreterProxy.fetchPointerofObject((i * 3) + 2, points); loadPointfrom(point3Get(), pointOop); if (interpreterProxy.failed()) { return null; } transformPoints(3); x0 = point1Get()[0]; y0 = point1Get()[1]; x1 = point2Get()[0]; y1 = point2Get()[1]; x2 = point3Get()[0]; /* Check if we can use a line */ y2 = point3Get()[1]; if (((x0 === y0) && (x1 === y1)) || ((x1 === x2) && (y1 === y2))) { loadWideLinefromtolineFillleftFillrightFill(lineWidth, point1Get(), point3Get(), lineFill, fillIndex, 0); } else { /* Need bezier */ segs = loadAndSubdivideBezierFromviatoisWide(point1Get(), point2Get(), point3Get(), (lineWidth !== 0) && (lineFill !== 0)); if (engineStopped) { return null; } loadWideBezierlineFillleftFillrightFilln(lineWidth, lineFill, fillIndex, 0, segs); } if (engineStopped) { return null; } } } /* Load a transformation from the given array. */ function loadArrayTransformFromintolength(transformOop, destPtr, n) { var i; var value; for (i = 0; i <= (n - 1); i++) { value = interpreterProxy.fetchPointerofObject(i, transformOop); if (!(typeof value === "number" || (value.isFloat))) { return interpreterProxy.primitiveFail(); } if (typeof value === "number") { destPtr[i] = value; } else { destPtr[i] = interpreterProxy.floatValueOf(value); } } } /* Initialize the bezier segment stored on the stack */ function loadBeziersegmentleftFillrightFilloffset(bezier, index, leftFillIndex, rightFillIndex, yOffset) { if (bzEndY(index) >= bzStartY(index)) { /* Top to bottom */ edgeXValueOfput(bezier, bzStartX(index)); edgeYValueOfput(bezier, bzStartY(index) - yOffset); bezierViaXOfput(bezier, bzViaX(index)); bezierViaYOfput(bezier, bzViaY(index) - yOffset); bezierEndXOfput(bezier, bzEndX(index)); bezierEndYOfput(bezier, bzEndY(index) - yOffset); } else { edgeXValueOfput(bezier, bzEndX(index)); edgeYValueOfput(bezier, bzEndY(index) - yOffset); bezierViaXOfput(bezier, bzViaX(index)); bezierViaYOfput(bezier, bzViaY(index) - yOffset); bezierEndXOfput(bezier, bzStartX(index)); bezierEndYOfput(bezier, bzStartY(index) - yOffset); } edgeZValueOfput(bezier, currentZGet()); edgeLeftFillOfput(bezier, leftFillIndex); edgeRightFillOfput(bezier, rightFillIndex); } function loadBitBltFrom(bbObj) { if (!loadBBFn) { /* We need copyBits here so try to load it implicitly */ if (!initialiseModule()) { return false; } } return loadBBFn(bbObj); } /* Load the bitmap fill. */ function loadBitmapFillcolormaptilefromalongnormalxIndex(formOop, cmOop, tileFlag, point1, point2, point3, xIndex) { var bmBits; var bmBitsSize; var bmDepth; var bmFill; var bmHeight; var bmRaster; var bmWidth; var cmBits; var cmSize; var ppw; if (cmOop.isNil) { cmSize = 0; cmBits = null; } else { if (CLASSOF(cmOop) !== interpreterProxy.classBitmap()) { return interpreterProxy.primitiveFail(); } cmSize = SIZEOF(cmOop); cmBits = cmOop.wordsAsInt32Array(); } if (typeof formOop === "number") { return interpreterProxy.primitiveFail(); } if (!interpreterProxy.isPointers(formOop)) { return interpreterProxy.primitiveFail(); } if (SIZEOF(formOop) < 5) { return interpreterProxy.primitiveFail(); } bmBits = interpreterProxy.fetchPointerofObject(0, formOop); if (CLASSOF(bmBits) !== interpreterProxy.classBitmap()) { return interpreterProxy.primitiveFail(); } bmBitsSize = SIZEOF(bmBits); bmWidth = interpreterProxy.fetchIntegerofObject(1, formOop); bmHeight = interpreterProxy.fetchIntegerofObject(2, formOop); bmDepth = interpreterProxy.fetchIntegerofObject(3, formOop); if (interpreterProxy.failed()) { return null; } if (!((bmWidth >= 0) && (bmHeight >= 0))) { return interpreterProxy.primitiveFail(); } if (!((((((bmDepth === 32) || (bmDepth === 8)) || (bmDepth === 16)) || (bmDepth === 1)) || (bmDepth === 2)) || (bmDepth === 4))) { return interpreterProxy.primitiveFail(); } if (!((cmSize === 0) || (cmSize === (SHL(1, bmDepth))))) { return interpreterProxy.primitiveFail(); } ppw = DIV(32, bmDepth); bmRaster = DIV((bmWidth + (ppw - 1)), ppw); if (bmBitsSize !== (bmRaster * bmHeight)) { return interpreterProxy.primitiveFail(); } bmFill = allocateBitmapFillcolormap(cmSize, cmBits); if (engineStopped) { return null; } bitmapWidthOfput(bmFill, bmWidth); bitmapHeightOfput(bmFill, bmHeight); bitmapDepthOfput(bmFill, bmDepth); bitmapRasterOfput(bmFill, bmRaster); bitmapSizeOfput(bmFill, bmBitsSize); bitmapTileFlagOfput(bmFill, tileFlag); objectIndexOfput(bmFill, xIndex); loadFillOrientationfromalongnormalwidthheight(bmFill, point1, point2, point3, bmWidth, bmHeight); return bmFill; } /* Note: Assumes that the contents of formArray has been checked before */ function loadBitsFrom(bmFill) { var bitsLen; var bitsOop; var formOop; var xIndex; xIndex = objectIndexOf(bmFill); if (xIndex > SIZEOF(formArray)) { return null; } formOop = interpreterProxy.fetchPointerofObject(xIndex, formArray); bitsOop = interpreterProxy.fetchPointerofObject(0, formOop); bitsLen = SIZEOF(bitsOop); if (bitsLen !== bitmapSizeOf(bmFill)) { return null; } return bitsOop.wordsAsInt32Array(); } /* Load a 2x3 transformation matrix from the given oop. Return true if the matrix is not nil, false otherwise */ function loadColorTransformFrom(transformOop) { var okay; var transform; transform = colorTransform(); hasColorTransformPut(0); okay = loadTransformFromintolength(transformOop, transform, 8); if (!okay) { return false; } hasColorTransformPut(1); transform[1] = (transform[1] * 256.0); transform[3] = (transform[3] * 256.0); transform[5] = (transform[5] * 256.0); transform[7] = (transform[7] * 256.0); return okay; } /* Load the compressed segment identified by segment index */ function loadCompressedSegmentfromshortleftFillrightFilllineWidthlineColor(segmentIndex, points, pointsShort, leftFill, rightFill, lineWidth, lineFill) { var index; var segs; var x0; var x1; var x2; var y0; var y1; var y2; /* Check if have anything to do at all */ if ((leftFill === rightFill) && ((lineWidth === 0) || (lineFill === 0))) { return null; } /* 3 points with x/y each */ index = segmentIndex * 6; if (pointsShort) { /* Load short points */ x0 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[index + 0]; y0 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[index + 1]; x1 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[index + 2]; y1 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[index + 3]; x2 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[index + 4]; y2 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[index + 5]; } else { x0 = (points[(index + 0)]|0); y0 = (points[(index + 1)]|0); x1 = (points[(index + 2)]|0); y1 = (points[(index + 3)]|0); x2 = (points[(index + 4)]|0); y2 = (points[(index + 5)]|0); } if (((x0 === x1) && (y0 === y1)) || ((x1 === x2) && (y1 === y2))) { /* We can use a line from x0/y0 to x2/y2 */ if ((x0 === x2) && (y0 === y2)) { return null; } point1Get()[0] = x0; point1Get()[1] = y0; point2Get()[0] = x2; point2Get()[1] = y2; transformPoints(2); return loadWideLinefromtolineFillleftFillrightFill(lineWidth, point1Get(), point2Get(), lineFill, leftFill, rightFill); } point1Get()[0] = x0; point1Get()[1] = y0; point2Get()[0] = x1; point2Get()[1] = y1; point3Get()[0] = x2; point3Get()[1] = y2; transformPoints(3); segs = loadAndSubdivideBezierFromviatoisWide(point1Get(), point2Get(), point3Get(), (lineWidth !== 0) && (lineFill !== 0)); if (engineStopped) { return null; } loadWideBezierlineFillleftFillrightFilln(lineWidth, lineFill, leftFill, rightFill, segs); } /* Load a compressed shape into the engine. WARNING: THIS METHOD NEEDS THE FULL FRAME SIZE!!!! */ function loadCompressedShapesegmentsleftFillsrightFillslineWidthslineFillsfillIndexListpointShort(points, nSegments, leftFills, rightFills, lineWidths, lineFills, fillIndexList, pointsShort) { var i; var leftLength; var leftRun; var leftValue; var lineFillLength; var lineFillRun; var lineFillValue; var rightLength; var rightRun; var rightValue; var widthLength; var widthRun; var widthValue; if (nSegments === 0) { return 0; } leftRun = (rightRun = (widthRun = (lineFillRun = -1))); leftLength = (rightLength = (widthLength = (lineFillLength = 1))); leftValue = (rightValue = (widthValue = (lineFillValue = 0))); for (i = 1; i <= nSegments; i++) { /* Decrement current run length and load new stuff */ if (((--leftLength)) <= 0) { ++leftRun; leftLength = shortRunLengthAtfrom(leftRun, leftFills); leftValue = shortRunValueAtfrom(leftRun, leftFills); if (leftValue !== 0) { leftValue = fillIndexList[leftValue - 1]; leftValue = transformColor(leftValue); if (engineStopped) { return null; } } } if (((--rightLength)) <= 0) { ++rightRun; rightLength = shortRunLengthAtfrom(rightRun, rightFills); rightValue = shortRunValueAtfrom(rightRun, rightFills); if (rightValue !== 0) { rightValue = fillIndexList[rightValue - 1]; rightValue = transformColor(rightValue); } } if (((--widthLength)) <= 0) { ++widthRun; widthLength = shortRunLengthAtfrom(widthRun, lineWidths); widthValue = shortRunValueAtfrom(widthRun, lineWidths); if (widthValue !== 0) { widthValue = transformWidth(widthValue); } } if (((--lineFillLength)) <= 0) { ++lineFillRun; lineFillLength = shortRunLengthAtfrom(lineFillRun, lineFills); lineFillValue = shortRunValueAtfrom(lineFillRun, lineFills); if (lineFillValue !== 0) { lineFillValue = fillIndexList[lineFillValue - 1]; } } loadCompressedSegmentfromshortleftFillrightFilllineWidthlineColor(i - 1, points, pointsShort, leftValue, rightValue, widthValue, lineFillValue); if (engineStopped) { return null; } } } function loadEdgeStateFrom(edgeOop) { var edge; edge = lastExportedEdgeGet(); if (SIZEOF(edgeOop) < ETBalloonEdgeDataSize) { return null; } edgeXValueOfput(edge, interpreterProxy.fetchIntegerofObject(ETXValueIndex, edgeOop)); edgeYValueOfput(edge, interpreterProxy.fetchIntegerofObject(ETYValueIndex, edgeOop)); edgeZValueOfput(edge, interpreterProxy.fetchIntegerofObject(ETZValueIndex, edgeOop)); edgeNumLinesOfput(edge, interpreterProxy.fetchIntegerofObject(ETLinesIndex, edgeOop)); return edge; } /* Load a 2x3 transformation matrix from the given oop. Return true if the matrix is not nil, false otherwise */ function loadEdgeTransformFrom(transformOop) { var okay; var transform; hasEdgeTransformPut(0); transform = edgeTransform(); okay = loadTransformFromintolength(transformOop, transform, 6); if (interpreterProxy.failed()) { return null; } if (!okay) { return false; } hasEdgeTransformPut(1); transform[2] = (transform[2] + destOffsetXGet()); transform[5] = (transform[5] + destOffsetYGet()); return true; } /* Transform the points */ function loadFillOrientationfromalongnormalwidthheight(fill, point1, point2, point3, fillWidth, fillHeight) { var dirX; var dirY; var dsLength2; var dsX; var dsY; var dtLength2; var dtX; var dtY; var nrmX; var nrmY; point2[0] = (point2[0] + point1[0]); point2[1] = (point2[1] + point1[1]); point3[0] = (point3[0] + point1[0]); point3[1] = (point3[1] + point1[1]); transformPoint(point1); transformPoint(point2); transformPoint(point3); dirX = point2[0] - point1[0]; dirY = point2[1] - point1[1]; nrmX = point3[0] - point1[0]; /* Compute the scale from direction/normal into ramp size */ nrmY = point3[1] - point1[1]; dsLength2 = (dirX * dirX) + (dirY * dirY); if (dsLength2 > 0) { dsX = ((((dirX * fillWidth) * 65536.0) / dsLength2)|0); dsY = ((((dirY * fillWidth) * 65536.0) / dsLength2)|0); } else { dsX = 0; dsY = 0; } dtLength2 = (nrmX * nrmX) + (nrmY * nrmY); if (dtLength2 > 0) { dtX = ((((nrmX * fillHeight) * 65536.0) / dtLength2)|0); dtY = ((((nrmY * fillHeight) * 65536.0) / dtLength2)|0); } else { dtX = 0; dtY = 0; } fillOriginXOfput(fill, point1[0]); fillOriginYOfput(fill, point1[1]); fillDirectionXOfput(fill, dsX); fillDirectionYOfput(fill, dsY); fillNormalXOfput(fill, dtX); fillNormalYOfput(fill, dtY); } /* Check all the forms from arrayOop. */ function loadFormsFrom(arrayOop) { var bmBits; var bmBitsSize; var bmDepth; var bmHeight; var bmRaster; var bmWidth; var formOop; var i; var ppw; if (!interpreterProxy.isArray(arrayOop)) { return false; } formArray = arrayOop; for (i = 0; i <= (SIZEOF(formArray) - 1); i++) { formOop = interpreterProxy.fetchPointerofObject(i, formArray); if (typeof formOop === "number") { return false; } if (!interpreterProxy.isPointers(formOop)) { return false; } if (SIZEOF(formOop) < 5) { return false; } bmBits = interpreterProxy.fetchPointerofObject(0, formOop); if (CLASSOF(bmBits) !== interpreterProxy.classBitmap()) { return false; } bmBitsSize = SIZEOF(bmBits); bmWidth = interpreterProxy.fetchIntegerofObject(1, formOop); bmHeight = interpreterProxy.fetchIntegerofObject(2, formOop); bmDepth = interpreterProxy.fetchIntegerofObject(3, formOop); if (interpreterProxy.failed()) { return false; } if (!((bmWidth >= 0) && (bmHeight >= 0))) { return false; } ppw = DIV(32, bmDepth); bmRaster = DIV((bmWidth + (ppw - 1)), ppw); if (bmBitsSize !== (bmRaster * bmHeight)) { return false; } } return true; } /* Load the gradient fill as defined by the color ramp. */ function loadGradientFillfromalongnormalisRadial(rampOop, point1, point2, point3, isRadial) { var fill; var rampWidth; if (CLASSOF(rampOop) !== interpreterProxy.classBitmap()) { return interpreterProxy.primitiveFail(); } rampWidth = SIZEOF(rampOop); fill = allocateGradientFillrampWidthisRadial(rampOop.wordsAsInt32Array(), rampWidth, isRadial); if (engineStopped) { return null; } loadFillOrientationfromalongnormalwidthheight(fill, point1, point2, point3, rampWidth, rampWidth); return fill; } /* Load the line defined by point1 and point2. */ function loadLinefromtooffsetleftFillrightFill(line, point1, point2, yOffset, leftFill, rightFill) { var p1; var p2; var yDir; if (point1[1] <= point2[1]) { p1 = point1; p2 = point2; yDir = 1; } else { p1 = point2; p2 = point1; yDir = -1; } edgeXValueOfput(line, p1[0]); edgeYValueOfput(line, p1[1] - yOffset); edgeZValueOfput(line, currentZGet()); edgeLeftFillOfput(line, leftFill); edgeRightFillOfput(line, rightFill); lineEndXOfput(line, p2[0]); lineEndYOfput(line, p2[1] - yOffset); lineYDirectionOfput(line, yDir); } /* Load a rectangular oval currently defined by point1/point2 */ function loadOvallineFillleftFillrightFill(lineWidth, lineFill, leftFill, rightFill) { var cx; var cy; var h; var i; var nSegments; var w; w = (point2Get()[0] - point1Get()[0]) >> 1; h = (point2Get()[1] - point1Get()[1]) >> 1; cx = (point2Get()[0] + point1Get()[0]) >> 1; cy = (point2Get()[1] + point1Get()[1]) >> 1; for (i = 0; i <= 15; i++) { loadOvalSegmentwhcxcy(i, w, h, cx, cy); transformPoints(3); nSegments = loadAndSubdivideBezierFromviatoisWide(point1Get(), point2Get(), point3Get(), (lineWidth !== 0) && (lineFill !== 0)); if (engineStopped) { return null; } loadWideBezierlineFillleftFillrightFilln(lineWidth, lineFill, leftFill, rightFill, nSegments); if (engineStopped) { return null; } } } function loadOvalSegmentwhcxcy(seg, w, h, cx, cy) { var x0; var x1; var x2; var y0; var y1; var y2; /* Load start point of segment */ x0 = (((circleCosTable()[(seg * 2) + 0] * w) + cx)|0); y0 = (((circleSinTable()[(seg * 2) + 0] * h) + cy)|0); point1Get()[0] = x0; point1Get()[1] = y0; x2 = (((circleCosTable()[(seg * 2) + 2] * w) + cx)|0); y2 = (((circleSinTable()[(seg * 2) + 2] * h) + cy)|0); point3Get()[0] = x2; point3Get()[1] = y2; x1 = (((circleCosTable()[(seg * 2) + 1] * w) + cx)|0); /* NOTE: The intermediate point is the point ON the curve and not yet the control point (which is OFF the curve) */ y1 = (((circleSinTable()[(seg * 2) + 1] * h) + cy)|0); x1 = (x1 * 2) - ((x0 + x2) >> 1); y1 = (y1 * 2) - ((y0 + y2) >> 1); point2Get()[0] = x1; point2Get()[1] = y1; } /* Load the contents of pointOop into pointArray */ function loadPointfrom(pointArray, pointOop) { var value; if (CLASSOF(pointOop) !== interpreterProxy.classPoint()) { return interpreterProxy.primitiveFail(); } value = interpreterProxy.fetchPointerofObject(0, pointOop); if (!(typeof value === "number" || (value.isFloat))) { return interpreterProxy.primitiveFail(); } if (typeof value === "number") { pointArray[0] = value; } else { pointArray[0] = (interpreterProxy.floatValueOf(value)|0); } value = interpreterProxy.fetchPointerofObject(1, pointOop); if (!(typeof value === "number" || (value.isFloat))) { return interpreterProxy.primitiveFail(); } if (typeof value === "number") { pointArray[1] = value; } else { pointArray[1] = (interpreterProxy.floatValueOf(value)|0); } } function loadPolygonnPointsfilllineWidthlineFillpointsShort(points, nPoints, fillIndex, lineWidth, lineFill, isShort) { var i; var x0; var x1; var y0; var y1; if (isShort) { x0 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[0]; y0 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[1]; } else { x0 = (points[0]|0); y0 = (points[1]|0); } for (i = 1; i <= (nPoints - 1); i++) { if (isShort) { x1 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[i * 2]; y1 = (points.int16Array || (points.int16Array = new Int16Array(points.buffer, points.byteOffset)))[(i * 2) + 1]; } else { x1 = (points[(i * 2)]|0); y1 = (points[((i * 2) + 1)]|0); } point1Get()[0] = x0; point1Get()[1] = y0; point2Get()[0] = x1; point2Get()[1] = y1; transformPoints(2); loadWideLinefromtolineFillleftFillrightFill(lineWidth, point1Get(), point2Get(), lineFill, fillIndex, 0); if (engineStopped) { return null; } x0 = x1; y0 = y1; } } /* Load a rectangle currently defined by point1-point4 */ function loadRectanglelineFillleftFillrightFill(lineWidth, lineFill, leftFill, rightFill) { loadWideLinefromtolineFillleftFillrightFill(lineWidth, point1Get(), point2Get(), lineFill, leftFill, rightFill); loadWideLinefromtolineFillleftFillrightFill(lineWidth, point2Get(), point3Get(), lineFill, leftFill, rightFill); loadWideLinefromtolineFillleftFillrightFill(lineWidth, point3Get(), point4Get(), lineFill, leftFill, rightFill); loadWideLinefromtolineFillleftFillrightFill(lineWidth, point4Get(), point1Get(), lineFill, leftFill, rightFill); } /* Load the entire state from the interpreter for the rendering primitives. Answer 0 on success or a non-zero failure code on failure. */ function loadRenderingState() { var edgeOop; var failCode; var fillOop; var state; if (interpreterProxy.methodArgumentCount() !== 2) { return PrimErrBadNumArgs; } if (((failCode = quickLoadEngineFrom(interpreterProxy.stackValue(2)))) !== 0) { return failCode; } fillOop = interpreterProxy.stackObjectValue(0); edgeOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return PrimErrBadArgument; } if (((failCode = loadSpanBufferFrom(interpreterProxy.fetchPointerofObject(BESpanIndex, engine)))) !== 0) { return failCode; } if (!loadBitBltFrom(interpreterProxy.fetchPointerofObject(BEBitBltIndex, engine))) { return GEFBitBltLoadFailed; } if (!loadFormsFrom(interpreterProxy.fetchPointerofObject(BEFormsIndex, engine))) { return GEFFormLoadFailed; } if (SIZEOF(edgeOop) < ETBalloonEdgeDataSize) { return GEFEdgeDataTooSmall; } if (SIZEOF(fillOop) < FTBalloonFillDataSize) { return GEFFillDataTooSmall; } state = stateGet(); if ((state === GEStateWaitingForEdge) || ((state === GEStateWaitingForFill) || (state === GEStateWaitingChange))) { return GEFWrongState; } return 0; } function loadShapenSegmentsfilllineWidthlineFillpointsShort(points, nSegments, fillIndex, lineWidth, lineFill, pointsShort) { var i; for (i = 1; i <= nSegments; i++) { loadCompressedSegmentfromshortleftFillrightFilllineWidthlineColor(i - 1, points, pointsShort, fillIndex, 0, lineWidth, lineFill); if (engineStopped) { return null; } } } /* Load the span buffer from the given oop. Answer 0 on success or a non-zero failure code on failure. */ function loadSpanBufferFrom(spanOop) { if (CLASSOF(spanOop) !== interpreterProxy.classBitmap()) { return GEFClassMismatch; } /* Leave last entry unused to avoid complications */ spanBuffer = spanOop.words; spanSizePut(SIZEOF(spanOop) - 1); return 0; } /* Load a transformation from transformOop into the float array defined by destPtr. The transformation is assumed to be either an array or a FloatArray of length n. */ function loadTransformFromintolength(transformOop, destPtr, n) { if (transformOop.isNil) { return false; } if (typeof transformOop === "number") { return interpreterProxy.primitiveFail(); } if (SIZEOF(transformOop) !== n) { return interpreterProxy.primitiveFail(); } if (interpreterProxy.isWords(transformOop)) { loadWordTransformFromintolength(transformOop, destPtr, n); } else { loadArrayTransformFromintolength(transformOop, destPtr, n); } return true; } /* Load the (possibly wide) bezier from the segments currently on the bezier stack. */ function loadWideBezierlineFillleftFillrightFilln(lineWidth, lineFill, leftFill, rightFill, nSegments) { var bezier; var index; var offset; var wide; if ((lineWidth === 0) || (lineFill === 0)) { wide = false; offset = 0; } else { wide = true; offset = offsetFromWidth(lineWidth); } index = nSegments * 6; while (index > 0) { if (wide) { bezier = allocateWideBezier(); } else { bezier = allocateBezier(); } if (engineStopped) { return 0; } loadBeziersegmentleftFillrightFilloffset(bezier, index, leftFill, rightFill, offset); if (wide) { wideBezierFillOfput(bezier, lineFill); wideBezierWidthOfput(bezier, lineWidth); wideBezierExtentOfput(bezier, lineWidth); } index -= 6; } wbStackClear(); } /* Load a (possibly wide) line defined by the points p1 and p2 */ function loadWideLinefromtolineFillleftFillrightFill(lineWidth, p1, p2, lineFill, leftFill, rightFill) { var line; var offset; if ((lineWidth === 0) || (lineFill === 0)) { line = allocateLine(); offset = 0; } else { line = allocateWideLine(); offset = offsetFromWidth(lineWidth); } if (engineStopped) { return 0; } loadLinefromtooffsetleftFillrightFill(line, p1, p2, offset, leftFill, rightFill); if (isWide(line)) { wideLineFillOfput(line, lineFill); wideLineWidthOfput(line, lineWidth); wideLineExtentOfput(line, lineWidth); } } /* Load a float array transformation from the given oop */ function loadWordTransformFromintolength(transformOop, destPtr, n) { var i; var srcPtr; srcPtr = transformOop.wordsAsFloat32Array(); for (i = 0; i <= (n - 1); i++) { destPtr[i] = srcPtr[i]; } } /* Load the working buffer from the given oop */ function loadWorkBufferFrom(wbOop) { if (typeof wbOop === "number") { return GEFWorkBufferIsInteger; } if (!interpreterProxy.isWords(wbOop)) { return GEFWorkBufferIsPointers; } if (SIZEOF(wbOop) < GWMinimalSize) { return GEFWorkBufferTooSmall; } workBufferPut(wbOop); if (magicNumberGet() !== GWMagicNumber) { return GEFWorkBufferBadMagic; } if (wbSizeGet() !== SIZEOF(wbOop)) { return GEFWorkBufferWrongSize; } if (objStartGet() !== GWHeaderSize) { return GEFWorkBufferStartWrong; } objBuffer = PTR_ADD(workBuffer, objStartGet()); getBuffer = PTR_ADD(objBuffer, objUsedGet()); /* Make sure we don't exceed the work buffer */ aetBuffer = PTR_ADD(getBuffer, getUsedGet()); if ((((GWHeaderSize + objUsedGet()) + getUsedGet()) + aetUsedGet()) > wbSizeGet()) { return GEFWorkTooBig; } return 0; } function magicNumberGet() { return workBuffer[GWMagicIndex]; } function magicNumberPut(value) { return workBuffer[GWMagicIndex] = value; } /* The module with the given name was just unloaded. Make sure we have no dangling references. */ function moduleUnloaded(aModuleName) { if (strcmp(aModuleName, bbPluginName) === 0) { /* BitBlt just shut down. How nasty. */ loadBBFn = 0; copyBitsFn = 0; } } /* The entry at index is not in the right position of the AET. Move it to the left until the position is okay. */ function moveAETEntryFromedgex(index, edge, xValue) { var newIndex; newIndex = index; while ((newIndex > 0) && (edgeXValueOf(aetBuffer[newIndex - 1]) > xValue)) { aetBuffer[newIndex] = aetBuffer[newIndex - 1]; --newIndex; } aetBuffer[newIndex] = edge; } /* Check if we have n slots available */ function needAvailableSpace(nSlots) { if (((((GWHeaderSize + objUsed) + getUsedGet()) + aetUsedGet()) + nSlots) > wbTopGet()) { stopBecauseOf(GErrorNoMoreSpace); return false; } return true; } function needsFlush() { return needsFlushGet() !== 0; } function needsFlushGet() { return workBuffer[GWNeedsFlush]; } function needsFlushPut(value) { return workBuffer[GWNeedsFlush] = value; } function objat(object, index) { return objBuffer[object + index]; } function objatput(object, index, value) { return objBuffer[object + index] = value; } function objStartGet() { return workBuffer[GWObjStart]; } function objStartPut(value) { return workBuffer[GWObjStart] = value; } function objUsedGet() { return workBuffer[GWObjUsed]; } function objUsedPut(value) { return workBuffer[GWObjUsed] = value; } function objectHeaderOf(obj) { return objat(obj, GEObjectType); } function objectIndexOf(obj) { return objat(obj, GEObjectIndex); } function objectIndexOfput(obj, value) { return objatput(obj, GEObjectIndex, value); } function objectLengthOf(obj) { return objat(obj, GEObjectLength); } function objectLengthOfput(obj, value) { return objatput(obj, GEObjectLength, value); } function objectTypeOf(obj) { return objat(obj, GEObjectType) & GEPrimitiveTypeMask; } function objectTypeOfput(obj, value) { return objatput(obj, GEObjectType, value); } /* Common function so that we don't compute that wrong in any place and can easily find all the places where we deal with one-pixel offsets. */ function offsetFromWidth(lineWidth) { return lineWidth >> 1; } function point1Get() { return PTR_ADD(workBuffer, GWPoint1); } function point2Get() { return PTR_ADD(workBuffer, GWPoint2); } function point3Get() { return PTR_ADD(workBuffer, GWPoint3); } function point4Get() { return PTR_ADD(workBuffer, GWPoint4); } /* We have just blitted a scan line to the screen. Do whatever seems to be a good idea here. */ /* Note: In the future we may check the time needed for this scan line and interrupt processing to give the Smalltalk code a chance to run at a certain time. */ /* Check if there is any more work to do. */ function postDisplayAction() { if ((getStartGet() >= getUsedGet()) && (aetUsedGet() === 0)) { /* No more entries to process */ statePut(GEStateCompleted); } if (currentYGet() >= fillMaxYGet()) { /* Out of clipping range */ statePut(GEStateCompleted); } } function primitiveAbortProcessing() { var failureCode; if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(0)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } statePut(GEStateCompleted); storeEngineStateInto(); } /* Note: No need to load either bitBlt or spanBuffer */ function primitiveAddActiveEdgeEntry() { var edge; var edgeOop; var failureCode; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateWaitingForEdge))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } edgeOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } edge = loadEdgeStateFrom(edgeOop); if (!edge) { return interpreterProxy.primitiveFailFor(GEFEdgeDataTooSmall); } if (!needAvailableSpace(1)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } if (edgeNumLinesOf(edge) > 0) { insertEdgeIntoAET(edge); } if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } statePut(GEStateAddingFromGET); storeEngineStateInto(); interpreterProxy.pop(1); if (doProfileStats) { incrementStatby(GWCountAddAETEntry, 1); incrementStatby(GWTimeAddAETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } } function primitiveAddBezier() { var endOop; var failureCode; var leftFill; var nSegments; var rightFill; var startOop; var viaOop; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } rightFill = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); leftFill = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(1)); viaOop = interpreterProxy.stackObjectValue(2); endOop = interpreterProxy.stackObjectValue(3); startOop = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(5), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!(isFillOkay(leftFill) && (isFillOkay(rightFill)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } loadPointfrom(point1Get(), startOop); loadPointfrom(point2Get(), viaOop); loadPointfrom(point3Get(), endOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } transformPoints(3); nSegments = loadAndSubdivideBezierFromviatoisWide(point1Get(), point2Get(), point3Get(), false); needAvailableSpace(nSegments * GBBaseSize); if (!engineStopped) { leftFill = transformColor(leftFill); rightFill = transformColor(rightFill); } if (!engineStopped) { loadWideBezierlineFillleftFillrightFilln(0, 0, leftFill, rightFill, nSegments); } if (engineStopped) { /* Make sure the stack is okay */ wbStackClear(); return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } storeEngineStateInto(); interpreterProxy.pop(5); } function primitiveAddBezierShape() { var failureCode; var fillIndex; var length; var lineFill; var lineWidth; var nSegments; var points; var pointsIsArray; var segSize; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } lineFill = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); lineWidth = interpreterProxy.stackIntegerValue(1); fillIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(2)); nSegments = interpreterProxy.stackIntegerValue(3); points = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(5), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } length = SIZEOF(points); if (interpreterProxy.isWords(points)) { /* Either PointArray or ShortPointArray */ pointsIsArray = false; if (!((length === (nSegments * 3)) || (length === (nSegments * 6)))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } } else { /* Must be Array of points */ if (!interpreterProxy.isArray(points)) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (length !== (nSegments * 3)) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } pointsIsArray = true; } if ((lineWidth === 0) || (lineFill === 0)) { segSize = GLBaseSize; } else { segSize = GLWideSize; } if (!needAvailableSpace(segSize * nSegments)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } if (!(isFillOkay(lineFill) && (isFillOkay(fillIndex)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } lineFill = transformColor(lineFill); fillIndex = transformColor(fillIndex); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (((lineFill === 0) || (lineWidth === 0)) && (fillIndex === 0)) { return interpreterProxy.pop(5); } if (lineWidth !== 0) { lineWidth = transformWidth(lineWidth); if (lineWidth < 1) { lineWidth = 1; } } if (pointsIsArray) { loadArrayShapenSegmentsfilllineWidthlineFill(points, nSegments, fillIndex, lineWidth, lineFill); } else { loadShapenSegmentsfilllineWidthlineFillpointsShort(points.wordsAsInt32Array(), nSegments, fillIndex, lineWidth, lineFill, (nSegments * 3) === length); } if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } needsFlushPut(1); storeEngineStateInto(); interpreterProxy.pop(5); } function primitiveAddBitmapFill() { var cmOop; var dirOop; var failureCode; var fill; var formOop; var nrmOop; var originOop; var tileFlag; var xIndex; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 7) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } xIndex = interpreterProxy.stackIntegerValue(0); if (xIndex <= 0) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } nrmOop = interpreterProxy.stackObjectValue(1); dirOop = interpreterProxy.stackObjectValue(2); originOop = interpreterProxy.stackObjectValue(3); tileFlag = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(4)); cmOop = interpreterProxy.stackObjectValue(5); formOop = interpreterProxy.stackObjectValue(6); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(7), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } loadPointfrom(point1Get(), originOop); loadPointfrom(point2Get(), dirOop); loadPointfrom(point3Get(), nrmOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFBadPoint); } fill = loadBitmapFillcolormaptilefromalongnormalxIndex(formOop, cmOop, (tileFlag ? 1 : 0), point1Get(), point2Get(), point3Get(), xIndex - 1); if (engineStopped) { /* Make sure the stack is okay */ return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } storeEngineStateInto(); interpreterProxy.popthenPush(8, interpreterProxy.positive32BitIntegerFor(fill)); } function primitiveAddCompressedShape() { var failureCode; var fillIndexList; var leftFills; var lineFills; var lineWidths; var nSegments; var points; var pointsShort; var rightFills; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 7) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } fillIndexList = interpreterProxy.stackObjectValue(0); lineFills = interpreterProxy.stackObjectValue(1); lineWidths = interpreterProxy.stackObjectValue(2); rightFills = interpreterProxy.stackObjectValue(3); leftFills = interpreterProxy.stackObjectValue(4); nSegments = interpreterProxy.stackIntegerValue(5); points = interpreterProxy.stackObjectValue(6); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(7), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!checkCompressedShapesegmentsleftFillsrightFillslineWidthslineFillsfillIndexList(points, nSegments, leftFills, rightFills, lineWidths, lineFills, fillIndexList)) { return interpreterProxy.primitiveFailFor(GEFEntityCheckFailed); } if (!needAvailableSpace(Math.max(GBBaseSize, GLBaseSize) * nSegments)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } /* Then actually load the compressed shape */ pointsShort = SIZEOF(points) === (nSegments * 3); loadCompressedShapesegmentsleftFillsrightFillslineWidthslineFillsfillIndexListpointShort(points.wordsAsInt32Array(), nSegments, leftFills.wordsAsInt32Array(), rightFills.wordsAsInt32Array(), lineWidths.wordsAsInt32Array(), lineFills.wordsAsInt32Array(), fillIndexList.wordsAsInt32Array(), pointsShort); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } needsFlushPut(1); storeEngineStateInto(); interpreterProxy.pop(7); } function primitiveAddGradientFill() { var dirOop; var failureCode; var fill; var isRadial; var nrmOop; var originOop; var rampOop; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } isRadial = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); nrmOop = interpreterProxy.stackValue(1); dirOop = interpreterProxy.stackValue(2); originOop = interpreterProxy.stackValue(3); rampOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(5), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } loadPointfrom(point1Get(), originOop); loadPointfrom(point2Get(), dirOop); loadPointfrom(point3Get(), nrmOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFBadPoint); } fill = loadGradientFillfromalongnormalisRadial(rampOop, point1Get(), point2Get(), point3Get(), isRadial); if (engineStopped) { /* Make sure the stack is okay */ return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } storeEngineStateInto(); interpreterProxy.popthenPush(6, interpreterProxy.positive32BitIntegerFor(fill)); } function primitiveAddLine() { var endOop; var failureCode; var leftFill; var rightFill; var startOop; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 4) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } rightFill = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); leftFill = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(1)); endOop = interpreterProxy.stackObjectValue(2); startOop = interpreterProxy.stackObjectValue(3); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(4), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!(isFillOkay(leftFill) && (isFillOkay(rightFill)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } loadPointfrom(point1Get(), startOop); loadPointfrom(point2Get(), endOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFBadPoint); } transformPoints(2); leftFill = transformColor(leftFill); rightFill = transformColor(rightFill); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } loadWideLinefromtolineFillleftFillrightFill(0, point1Get(), point2Get(), 0, leftFill, rightFill); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } storeEngineStateInto(); interpreterProxy.pop(4); } function primitiveAddOval() { var borderIndex; var borderWidth; var endOop; var failureCode; var fillIndex; var startOop; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } borderIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); borderWidth = interpreterProxy.stackIntegerValue(1); fillIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(2)); endOop = interpreterProxy.stackObjectValue(3); startOop = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(5), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!(isFillOkay(borderIndex) && (isFillOkay(fillIndex)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } fillIndex = transformColor(fillIndex); borderIndex = transformColor(borderIndex); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if ((fillIndex === 0) && ((borderIndex === 0) || (borderWidth <= 0))) { return interpreterProxy.pop(5); } if (!needAvailableSpace(16 * GBBaseSize)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } if ((borderWidth > 0) && (borderIndex !== 0)) { borderWidth = transformWidth(borderWidth); } else { borderWidth = 0; } loadPointfrom(point1Get(), startOop); loadPointfrom(point2Get(), endOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFBadPoint); } loadOvallineFillleftFillrightFill(borderWidth, borderIndex, 0, fillIndex); if (engineStopped) { wbStackClear(); return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } needsFlushPut(1); storeEngineStateInto(); interpreterProxy.pop(5); } function primitiveAddPolygon() { var failureCode; var fillIndex; var length; var lineFill; var lineWidth; var nPoints; var points; var pointsIsArray; var segSize; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } lineFill = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); lineWidth = interpreterProxy.stackIntegerValue(1); fillIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(2)); nPoints = interpreterProxy.stackIntegerValue(3); points = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(5), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } length = SIZEOF(points); if (interpreterProxy.isWords(points)) { /* Either PointArray or ShortPointArray */ pointsIsArray = false; if (!((length === nPoints) || ((nPoints * 2) === length))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } } else { /* Must be Array of points */ if (!interpreterProxy.isArray(points)) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (length !== nPoints) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } pointsIsArray = true; } if ((lineWidth === 0) || (lineFill === 0)) { segSize = GLBaseSize; } else { segSize = GLWideSize; } if (!needAvailableSpace(segSize * nPoints)) { return interpreterProxy.primitiveFail(); } if (!(isFillOkay(lineFill) && (isFillOkay(fillIndex)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } lineFill = transformColor(lineFill); fillIndex = transformColor(fillIndex); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (((lineFill === 0) || (lineWidth === 0)) && (fillIndex === 0)) { return interpreterProxy.pop(5); } if (lineWidth !== 0) { lineWidth = transformWidth(lineWidth); } if (pointsIsArray) { loadArrayPolygonnPointsfilllineWidthlineFill(points, nPoints, fillIndex, lineWidth, lineFill); } else { loadPolygonnPointsfilllineWidthlineFillpointsShort(points.wordsAsInt32Array(), nPoints, fillIndex, lineWidth, lineFill, nPoints === length); } if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } needsFlushPut(1); storeEngineStateInto(); interpreterProxy.pop(5); } function primitiveAddRect() { var borderIndex; var borderWidth; var endOop; var failureCode; var fillIndex; var startOop; /* Fail if we have the wrong number of arguments */ if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } borderIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); borderWidth = interpreterProxy.stackIntegerValue(1); fillIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(2)); endOop = interpreterProxy.stackObjectValue(3); startOop = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(5), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!(isFillOkay(borderIndex) && (isFillOkay(fillIndex)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } borderIndex = transformColor(borderIndex); fillIndex = transformColor(fillIndex); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if ((fillIndex === 0) && ((borderIndex === 0) || (borderWidth === 0))) { return interpreterProxy.pop(5); } if (!needAvailableSpace(4 * GLBaseSize)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } if ((borderWidth > 0) && (borderIndex !== 0)) { borderWidth = transformWidth(borderWidth); } else { borderWidth = 0; } loadPointfrom(point1Get(), startOop); loadPointfrom(point3Get(), endOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFBadPoint); } point2Get()[0] = point3Get()[0]; point2Get()[1] = point1Get()[1]; point4Get()[0] = point1Get()[0]; point4Get()[1] = point3Get()[1]; transformPoints(4); loadRectanglelineFillleftFillrightFill(borderWidth, borderIndex, 0, fillIndex); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } needsFlushPut(1); storeEngineStateInto(); interpreterProxy.pop(5); } /* Note: No need to load either bitBlt or spanBuffer */ function primitiveChangedActiveEdgeEntry() { var edge; var edgeOop; var failureCode; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateWaitingChange))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } edgeOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } edge = loadEdgeStateFrom(edgeOop); if (!edge) { return interpreterProxy.primitiveFailFor(GEFEdgeDataTooSmall); } if (edgeNumLinesOf(edge) === 0) { removeFirstAETEntry(); } else { resortFirstAETEntry(); aetStartPut(aetStartGet() + 1); } statePut(GEStateUpdateEdges); storeEngineStateInto(); interpreterProxy.pop(1); if (doProfileStats) { incrementStatby(GWCountChangeAETEntry, 1); incrementStatby(GWTimeChangeAETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } } function primitiveCopyBuffer() { var buf1; var buf2; var diff; var dst; var failCode; var i; var src; var iLimiT; if (interpreterProxy.methodArgumentCount() !== 2) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } buf2 = interpreterProxy.stackValue(0); /* Make sure the old buffer is properly initialized */ buf1 = interpreterProxy.stackValue(1); if (((failCode = loadWorkBufferFrom(buf1))) !== 0) { return interpreterProxy.primitiveFailFor(failCode); } if (CLASSOF(buf1) !== CLASSOF(buf2)) { return interpreterProxy.primitiveFailFor(GEFClassMismatch); } diff = SIZEOF(buf2) - SIZEOF(buf1); if (diff < 0) { return interpreterProxy.primitiveFailFor(GEFSizeMismatch); } src = workBuffer; dst = buf2.wordsAsInt32Array(); for (i = 0, iLimiT = (wbTopGet() - 1); i <= iLimiT; i++) { dst[i] = src[i]; } dst[GWBufferTop] = (wbTopGet() + diff); dst[GWSize] = (wbSizeGet() + diff); src = PTR_ADD(src, wbTopGet()); dst = PTR_ADD(dst, wbTopGet() + diff); for (i = 0, iLimiT = ((wbSizeGet() - wbTopGet()) - 1); i <= iLimiT; i++) { dst[i] = src[i]; } if (((failCode = loadWorkBufferFrom(buf2))) !== 0) { return interpreterProxy.primitiveFailFor(failCode); } interpreterProxy.pop(2); } /* Note: Must load bitBlt and spanBuffer */ function primitiveDisplaySpanBuffer() { var failureCode; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(0), GEStateBlitBuffer))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (((failureCode = loadSpanBufferFrom(interpreterProxy.fetchPointerofObject(BESpanIndex, engine)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!loadBitBltFrom(interpreterProxy.fetchPointerofObject(BEBitBltIndex, engine))) { return interpreterProxy.primitiveFailFor(GEFBitBltLoadFailed); } if ((currentYGet() & aaScanMaskGet()) === aaScanMaskGet()) { displaySpanBufferAt(currentYGet()); postDisplayAction(); } if (!finishedProcessing()) { aetStartPut(0); currentYPut(currentYGet() + 1); statePut(GEStateUpdateEdges); } storeEngineStateInto(); if (doProfileStats) { incrementStatby(GWCountDisplaySpan, 1); incrementStatby(GWTimeDisplaySpan, interpreterProxy.ioMicroMSecs() - geProfileTime); } } /* Turn on/off profiling. Return the old value of the flag. */ function primitiveDoProfileStats() { var newValue; var oldValue; oldValue = doProfileStats; newValue = interpreterProxy.stackObjectValue(0); newValue = interpreterProxy.booleanValueOf(newValue); if (!interpreterProxy.failed()) { doProfileStats = newValue; interpreterProxy.pop(2); interpreterProxy.pushBool(oldValue); } } function primitiveFinishedProcessing() { var failureCode; var finished; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(0)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } finished = finishedProcessing(); storeEngineStateInto(); interpreterProxy.pop(1); interpreterProxy.pushBool(finished); if (doProfileStats) { incrementStatby(GWCountFinishTest, 1); incrementStatby(GWTimeFinishTest, interpreterProxy.ioMicroMSecs() - geProfileTime); } } function primitiveGetAALevel() { var failureCode; if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(0)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } interpreterProxy.pop(1); interpreterProxy.pushInteger(aaLevelGet()); } function primitiveGetBezierStats() { var failureCode; var statOop; var stats; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(1)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } statOop = interpreterProxy.stackObjectValue(0); if (!(!interpreterProxy.failed() && (interpreterProxy.isWords(statOop) && (SIZEOF(statOop) >= 4)))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } stats = statOop.wordsAsInt32Array(); stats[0] = (stats[0] + workBuffer[GWBezierMonotonSubdivisions]); stats[1] = (stats[1] + workBuffer[GWBezierHeightSubdivisions]); stats[2] = (stats[2] + workBuffer[GWBezierOverflowSubdivisions]); stats[3] = (stats[3] + workBuffer[GWBezierLineConversions]); interpreterProxy.pop(1); } function primitiveGetClipRect() { var failureCode; var pointOop; var rectOop; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(1)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } rectOop = interpreterProxy.stackObjectValue(0); if (!(!interpreterProxy.failed() && (interpreterProxy.isPointers(rectOop) && (SIZEOF(rectOop) >= 2)))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } interpreterProxy.pushRemappableOop(rectOop); pointOop = interpreterProxy.makePointwithxValueyValue(clipMinXGet(), clipMinYGet()); interpreterProxy.storePointerofObjectwithValue(0, interpreterProxy.topRemappableOop(), pointOop); pointOop = interpreterProxy.makePointwithxValueyValue(clipMaxXGet(), clipMaxYGet()); rectOop = interpreterProxy.popRemappableOop(); interpreterProxy.storePointerofObjectwithValue(1, rectOop, pointOop); interpreterProxy.popthenPush(2, rectOop); } function primitiveGetCounts() { var failureCode; var statOop; var stats; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(1)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } statOop = interpreterProxy.stackObjectValue(0); if (!(!interpreterProxy.failed() && (interpreterProxy.isWords(statOop) && (SIZEOF(statOop) >= 9)))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } stats = statOop.wordsAsInt32Array(); stats[0] = (stats[0] + workBuffer[GWCountInitializing]); stats[1] = (stats[1] + workBuffer[GWCountFinishTest]); stats[2] = (stats[2] + workBuffer[GWCountNextGETEntry]); stats[3] = (stats[3] + workBuffer[GWCountAddAETEntry]); stats[4] = (stats[4] + workBuffer[GWCountNextFillEntry]); stats[5] = (stats[5] + workBuffer[GWCountMergeFill]); stats[6] = (stats[6] + workBuffer[GWCountDisplaySpan]); stats[7] = (stats[7] + workBuffer[GWCountNextAETEntry]); stats[8] = (stats[8] + workBuffer[GWCountChangeAETEntry]); interpreterProxy.pop(1); } function primitiveGetDepth() { var failureCode; if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(0)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } interpreterProxy.pop(1); interpreterProxy.pushInteger(currentZGet()); } /* Return the reason why the last operation failed. */ function primitiveGetFailureReason() { var failCode; if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } /* Note -- don't call loadEngineFrom here because this will override the stopReason with Zero */ engine = interpreterProxy.stackValue(0); if (typeof engine === "number") { return interpreterProxy.primitiveFailFor(GEFEngineIsInteger); } if (!interpreterProxy.isPointers(engine)) { return interpreterProxy.primitiveFailFor(GEFEngineIsWords); } if (SIZEOF(engine) < BEBalloonEngineSize) { return interpreterProxy.primitiveFailFor(GEFEngineTooSmall); } if (((failCode = loadWorkBufferFrom(interpreterProxy.fetchPointerofObject(BEWorkBufferIndex, engine)))) !== 0) { return interpreterProxy.primitiveFailFor(failCode); } interpreterProxy.pop(1); interpreterProxy.pushInteger(stopReasonGet()); } function primitiveGetOffset() { var failureCode; var pointOop; if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(0)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } pointOop = interpreterProxy.makePointwithxValueyValue(destOffsetXGet(), destOffsetYGet()); interpreterProxy.popthenPush(1, pointOop); } function primitiveGetTimes() { var failureCode; var statOop; var stats; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(1)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } statOop = interpreterProxy.stackObjectValue(0); if (!(!interpreterProxy.failed() && (interpreterProxy.isWords(statOop) && (SIZEOF(statOop) >= 9)))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } stats = statOop.wordsAsInt32Array(); stats[0] = (stats[0] + workBuffer[GWTimeInitializing]); stats[1] = (stats[1] + workBuffer[GWTimeFinishTest]); stats[2] = (stats[2] + workBuffer[GWTimeNextGETEntry]); stats[3] = (stats[3] + workBuffer[GWTimeAddAETEntry]); stats[4] = (stats[4] + workBuffer[GWTimeNextFillEntry]); stats[5] = (stats[5] + workBuffer[GWTimeMergeFill]); stats[6] = (stats[6] + workBuffer[GWTimeDisplaySpan]); stats[7] = (stats[7] + workBuffer[GWTimeNextAETEntry]); stats[8] = (stats[8] + workBuffer[GWTimeChangeAETEntry]); interpreterProxy.pop(1); } function primitiveInitializeBuffer() { var size; var wbOop; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFail(); } wbOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(wbOop)) { return interpreterProxy.primitiveFail(); } if (((size = SIZEOF(wbOop))) < GWMinimalSize) { return interpreterProxy.primitiveFail(); } workBufferPut(wbOop); objBuffer = PTR_ADD(workBuffer, GWHeaderSize); magicNumberPut(GWMagicNumber); wbSizePut(size); wbTopPut(size); statePut(GEStateUnlocked); objStartPut(GWHeaderSize); objUsedPut(4); objectTypeOfput(0, GEPrimitiveFill); objectLengthOfput(0, 4); objectIndexOfput(0, 0); getStartPut(0); getUsedPut(0); aetStartPut(0); aetUsedPut(0); stopReasonPut(0); needsFlushPut(0); clipMinXPut(0); clipMaxXPut(0); clipMinYPut(0); clipMaxYPut(0); currentZPut(0); resetGraphicsEngineStats(); initEdgeTransform(); initColorTransform(); interpreterProxy.pop(2); interpreterProxy.push(wbOop); } /* Note: No need to load bitBlt but must load spanBuffer */ function primitiveInitializeProcessing() { var failureCode; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(0), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (((failureCode = loadSpanBufferFrom(interpreterProxy.fetchPointerofObject(BESpanIndex, engine)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } initializeGETProcessing(); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } statePut(GEStateAddingFromGET); if (!interpreterProxy.failed()) { storeEngineStateInto(); } if (doProfileStats) { incrementStatby(GWCountInitializing, 1); incrementStatby(GWTimeInitializing, interpreterProxy.ioMicroMSecs() - geProfileTime); } } /* Note: No need to load bitBlt but must load spanBuffer */ function primitiveMergeFillFrom() { var bitsOop; var failureCode; var fillOop; var value; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 2) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(2), GEStateWaitingForFill))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (((failureCode = loadSpanBufferFrom(interpreterProxy.fetchPointerofObject(BESpanIndex, engine)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } fillOop = interpreterProxy.stackObjectValue(0); /* Check bitmap */ bitsOop = interpreterProxy.stackObjectValue(1); if (!(!interpreterProxy.failed() && (CLASSOF(bitsOop) === interpreterProxy.classBitmap()))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (SIZEOF(fillOop) < FTBalloonFillDataSize) { return interpreterProxy.primitiveFailFor(GEFFillDataTooSmall); } value = interpreterProxy.fetchIntegerofObject(FTIndexIndex, fillOop); if (objectIndexOf(lastExportedFillGet()) !== value) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } value = interpreterProxy.fetchIntegerofObject(FTMinXIndex, fillOop); if (lastExportedLeftXGet() !== value) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } value = interpreterProxy.fetchIntegerofObject(FTMaxXIndex, fillOop); if (lastExportedRightXGet() !== value) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } if (SIZEOF(bitsOop) < (lastExportedRightXGet() - lastExportedLeftXGet())) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (interpreterProxy.failed()) { return null; } fillBitmapSpanfromto(bitsOop.wordsAsInt32Array(), lastExportedLeftXGet(), lastExportedRightXGet()); statePut(GEStateScanningAET); storeEngineStateInto(); interpreterProxy.pop(2); if (doProfileStats) { incrementStatby(GWCountMergeFill, 1); incrementStatby(GWTimeMergeFill, interpreterProxy.ioMicroMSecs() - geProfileTime); } } function primitiveNeedsFlush() { var failureCode; var needFlush; if (interpreterProxy.methodArgumentCount() !== 0) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(0)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } needFlush = needsFlush(); storeEngineStateInto(); interpreterProxy.pop(1); interpreterProxy.pushBool(needFlush); } function primitiveNeedsFlushPut() { var failureCode; var needFlush; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFrom(interpreterProxy.stackValue(1)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } needFlush = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (needFlush === true) { needsFlushPut(1); } else { needsFlushPut(0); } storeEngineStateInto(); interpreterProxy.pop(1); } /* Note: No need to load either bitBlt or spanBuffer */ function primitiveNextActiveEdgeEntry() { var edge; var edgeOop; var failureCode; var hasEdge; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredStateor(interpreterProxy.stackValue(1), GEStateUpdateEdges, GEStateCompleted))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } edgeOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } hasEdge = false; if (stateGet() !== GEStateCompleted) { hasEdge = findNextExternalUpdateFromAET(); if (hasEdge) { edge = aetBuffer[aetStartGet()]; storeEdgeStateFrominto(edge, edgeOop); statePut(GEStateWaitingChange); } else { statePut(GEStateAddingFromGET); } } if (interpreterProxy.failed()) { return null; } storeEngineStateInto(); interpreterProxy.pop(2); interpreterProxy.pushBool(!hasEdge); if (doProfileStats) { incrementStatby(GWCountNextAETEntry, 1); incrementStatby(GWTimeNextAETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } } /* Note: No need to load bitBlt but must load spanBuffer */ function primitiveNextFillEntry() { var failureCode; var fillOop; var hasFill; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateScanningAET))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (((failureCode = loadSpanBufferFrom(interpreterProxy.fetchPointerofObject(BESpanIndex, engine)))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } if (!loadFormsFrom(interpreterProxy.fetchPointerofObject(BEFormsIndex, engine))) { return interpreterProxy.primitiveFailFor(GEFFormLoadFailed); } if (clearSpanBufferGet() !== 0) { if ((currentYGet() & aaScanMaskGet()) === 0) { clearSpanBuffer(); } clearSpanBufferPut(0); } fillOop = interpreterProxy.stackObjectValue(0); hasFill = findNextExternalFillFromAET(); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (hasFill) { storeFillStateInto(fillOop); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } if (hasFill) { statePut(GEStateWaitingForFill); } else { wbStackClear(); spanEndAAPut(0); statePut(GEStateBlitBuffer); } storeEngineStateInto(); interpreterProxy.pop(2); interpreterProxy.pushBool(!hasFill); if (doProfileStats) { incrementStatby(GWCountNextFillEntry, 1); incrementStatby(GWTimeNextFillEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } } /* Note: No need to load either bitBlt or spanBuffer */ function primitiveNextGlobalEdgeEntry() { var edge; var edgeOop; var failureCode; var hasEdge; if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateAddingFromGET))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } edgeOop = interpreterProxy.stackObjectValue(0); hasEdge = findNextExternalEntryFromGET(); if (hasEdge) { edge = getBuffer[getStartGet()]; storeEdgeStateFrominto(edge, edgeOop); getStartPut(getStartGet() + 1); } if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFWrongEdge); } if (hasEdge) { statePut(GEStateWaitingForEdge); } else { /* Start scanning the AET */ statePut(GEStateScanningAET); clearSpanBufferPut(1); aetStartPut(0); wbStackClear(); } storeEngineStateInto(); interpreterProxy.pop(2); interpreterProxy.pushBool(!hasEdge); if (doProfileStats) { incrementStatby(GWCountNextGETEntry, 1); incrementStatby(GWTimeNextGETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } } function primitiveRegisterExternalEdge() { var edge; var failureCode; var index; var initialX; var initialY; var initialZ; var leftFillIndex; var rightFillIndex; if (interpreterProxy.methodArgumentCount() !== 6) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(6), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } rightFillIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(0)); leftFillIndex = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(1)); initialZ = interpreterProxy.stackIntegerValue(2); initialY = interpreterProxy.stackIntegerValue(3); initialX = interpreterProxy.stackIntegerValue(4); index = interpreterProxy.stackIntegerValue(5); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } if (!allocateObjEntry(GEBaseEdgeSize)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } if (!(isFillOkay(leftFillIndex) && (isFillOkay(rightFillIndex)))) { return interpreterProxy.primitiveFailFor(GEFWrongFill); } edge = objUsed; /* Install type and length */ objUsed = edge + GEBaseEdgeSize; objectTypeOfput(edge, GEPrimitiveEdge); objectLengthOfput(edge, GEBaseEdgeSize); objectIndexOfput(edge, index); edgeXValueOfput(edge, initialX); edgeYValueOfput(edge, initialY); edgeZValueOfput(edge, initialZ); edgeLeftFillOfput(edge, transformColor(leftFillIndex)); edgeRightFillOfput(edge, transformColor(rightFillIndex)); if (engineStopped) { return interpreterProxy.primitiveFailFor(GEFEngineStopped); } if (!interpreterProxy.failed()) { storeEngineStateInto(); interpreterProxy.pop(6); } } function primitiveRegisterExternalFill() { var failureCode; var fill; var index; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } index = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } fill = 0; while (fill === 0) { if (!allocateObjEntry(GEBaseEdgeSize)) { return interpreterProxy.primitiveFailFor(GEFWorkTooBig); } fill = objUsed; /* Install type and length */ objUsed = fill + GEBaseFillSize; objectTypeOfput(fill, GEPrimitiveFill); objectLengthOfput(fill, GEBaseFillSize); objectIndexOfput(fill, index); } if (!interpreterProxy.failed()) { storeEngineStateInto(); interpreterProxy.pop(2); interpreterProxy.pushInteger(fill); } } /* Start/Proceed rendering the entire image */ function primitiveRenderImage() { var failCode; if (((failCode = loadRenderingState())) !== 0) { return interpreterProxy.primitiveFailFor(failCode); } proceedRenderingScanline(); if (engineStopped) { return storeRenderingState(); } proceedRenderingImage(); storeRenderingState(); } /* Start rendering the entire image */ function primitiveRenderScanline() { var failCode; if (((failCode = loadRenderingState())) !== 0) { return interpreterProxy.primitiveFailFor(failCode); } proceedRenderingScanline(); storeRenderingState(); } function primitiveSetAALevel() { var failureCode; var level; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } level = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } setAALevel(level); storeEngineStateInto(); interpreterProxy.pop(1); } /* Primitive. Set the BitBlt plugin to use. */ function primitiveSetBitBltPlugin() { var length; var needReload; var pluginName; /* Must be string to work */ pluginName = interpreterProxy.stackValue(0); if (!interpreterProxy.isBytes(pluginName)) { return interpreterProxy.primitiveFail(); } length = BYTESIZEOF(pluginName); if (length >= 256) { return interpreterProxy.primitiveFail(); } pluginName.bytes; needReload = false; // JS hack: can't copy bytes as in the C version var newPluginName = pluginName.bytesAsString(); if (newPluginName !== bbPluginName) { bbPluginName = newPluginName; needReload = true; } if (needReload) { if (!initialiseModule()) { return interpreterProxy.primitiveFail(); } } interpreterProxy.pop(1); } function primitiveSetClipRect() { var failureCode; var rectOop; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } rectOop = interpreterProxy.stackObjectValue(0); if (!(!interpreterProxy.failed() && (interpreterProxy.isPointers(rectOop) && (SIZEOF(rectOop) >= 2)))) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } loadPointfrom(point1Get(), interpreterProxy.fetchPointerofObject(0, rectOop)); loadPointfrom(point2Get(), interpreterProxy.fetchPointerofObject(1, rectOop)); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } clipMinXPut(point1Get()[0]); clipMinYPut(point1Get()[1]); clipMaxXPut(point2Get()[0]); clipMaxYPut(point2Get()[1]); storeEngineStateInto(); interpreterProxy.pop(1); } function primitiveSetColorTransform() { var failureCode; var transformOop; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } transformOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } loadColorTransformFrom(transformOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(GEFEntityLoadFailed); } storeEngineStateInto(); interpreterProxy.pop(1); } function primitiveSetDepth() { var depth; var failureCode; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } depth = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } currentZPut(depth); storeEngineStateInto(); interpreterProxy.pop(1); } function primitiveSetEdgeTransform() { var failureCode; var transformOop; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } transformOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } loadEdgeTransformFrom(transformOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } storeEngineStateInto(); interpreterProxy.pop(1); } function primitiveSetOffset() { var failureCode; var pointOop; if (interpreterProxy.methodArgumentCount() !== 1) { return interpreterProxy.primitiveFailFor(PrimErrBadNumArgs); } if (((failureCode = quickLoadEngineFromrequiredState(interpreterProxy.stackValue(1), GEStateUnlocked))) !== 0) { return interpreterProxy.primitiveFailFor(failureCode); } pointOop = interpreterProxy.stackValue(0); if (CLASSOF(pointOop) !== interpreterProxy.classPoint()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } loadPointfrom(point1Get(), pointOop); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFailFor(PrimErrBadArgument); } destOffsetXPut(point1Get()[0]); destOffsetYPut(point1Get()[1]); storeEngineStateInto(); interpreterProxy.pop(1); } /* This is the main rendering entry */ function proceedRenderingImage() { var external; while (!(finishedProcessing())) { if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } external = findNextExternalEntryFromGET(); if (doProfileStats) { incrementStatby(GWCountNextGETEntry, 1); incrementStatby(GWTimeNextGETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateAddingFromGET); } if (external) { statePut(GEStateWaitingForEdge); return stopBecauseOf(GErrorGETEntry); } aetStartPut(0); wbStackClear(); clearSpanBufferPut(1); if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if ((clearSpanBufferGet() !== 0) && ((currentYGet() & aaScanMaskGet()) === 0)) { clearSpanBuffer(); } clearSpanBufferPut(0); external = findNextExternalFillFromAET(); if (doProfileStats) { incrementStatby(GWCountNextFillEntry, 1); incrementStatby(GWTimeNextFillEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateScanningAET); } if (external) { statePut(GEStateWaitingForFill); return stopBecauseOf(GErrorFillEntry); } wbStackClear(); spanEndAAPut(0); if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if ((currentYGet() & aaScanMaskGet()) === aaScanMaskGet()) { displaySpanBufferAt(currentYGet()); postDisplayAction(); } if (doProfileStats) { incrementStatby(GWCountDisplaySpan, 1); incrementStatby(GWTimeDisplaySpan, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateBlitBuffer); } if (finishedProcessing()) { return 0; } aetStartPut(0); currentYPut(currentYGet() + 1); if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } external = findNextExternalUpdateFromAET(); if (doProfileStats) { incrementStatby(GWCountNextAETEntry, 1); incrementStatby(GWTimeNextAETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateUpdateEdges); } if (external) { statePut(GEStateWaitingChange); return stopBecauseOf(GErrorAETEntry); } } } /* Proceed rendering the current scan line. This method may be called after some Smalltalk code has been executed inbetween. */ /* This is the main rendering entry */ function proceedRenderingScanline() { var external; var state; state = stateGet(); if (state === GEStateUnlocked) { initializeGETProcessing(); if (engineStopped) { return 0; } state = GEStateAddingFromGET; } if (state === GEStateAddingFromGET) { if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } external = findNextExternalEntryFromGET(); if (doProfileStats) { incrementStatby(GWCountNextGETEntry, 1); incrementStatby(GWTimeNextGETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateAddingFromGET); } if (external) { statePut(GEStateWaitingForEdge); return stopBecauseOf(GErrorGETEntry); } aetStartPut(0); wbStackClear(); clearSpanBufferPut(1); state = GEStateScanningAET; } if (state === GEStateScanningAET) { if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if ((clearSpanBufferGet() !== 0) && ((currentYGet() & aaScanMaskGet()) === 0)) { clearSpanBuffer(); } clearSpanBufferPut(0); external = findNextExternalFillFromAET(); if (doProfileStats) { incrementStatby(GWCountNextFillEntry, 1); incrementStatby(GWTimeNextFillEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateScanningAET); } if (external) { statePut(GEStateWaitingForFill); return stopBecauseOf(GErrorFillEntry); } state = GEStateBlitBuffer; wbStackClear(); spanEndAAPut(0); } if (state === GEStateBlitBuffer) { if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } if ((currentYGet() & aaScanMaskGet()) === aaScanMaskGet()) { displaySpanBufferAt(currentYGet()); postDisplayAction(); } if (doProfileStats) { incrementStatby(GWCountDisplaySpan, 1); incrementStatby(GWTimeDisplaySpan, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateBlitBuffer); } if (finishedProcessing()) { return 0; } state = GEStateUpdateEdges; aetStartPut(0); currentYPut(currentYGet() + 1); } if (state === GEStateUpdateEdges) { if (doProfileStats) { geProfileTime = interpreterProxy.ioMicroMSecs(); } external = findNextExternalUpdateFromAET(); if (doProfileStats) { incrementStatby(GWCountNextAETEntry, 1); incrementStatby(GWTimeNextAETEntry, interpreterProxy.ioMicroMSecs() - geProfileTime); } if (engineStopped) { return statePut(GEStateUpdateEdges); } if (external) { statePut(GEStateWaitingChange); return stopBecauseOf(GErrorAETEntry); } statePut(GEStateAddingFromGET); } } /* Load the minimal required state from the engineOop, e.g., just the work buffer. Answer 0 on success or non-zero a failure code on failure */ function quickLoadEngineFrom(engineOop) { var failCode; if (interpreterProxy.failed()) { return GEFAlreadyFailed; } if (typeof engineOop === "number") { return GEFEngineIsInteger; } if (!interpreterProxy.isPointers(engineOop)) { return GEFEngineIsWords; } if (SIZEOF(engineOop) < BEBalloonEngineSize) { return GEFEngineTooSmall; } engine = engineOop; if (((failCode = loadWorkBufferFrom(interpreterProxy.fetchPointerofObject(BEWorkBufferIndex, engineOop)))) !== 0) { return failCode; } stopReasonPut(0); objUsed = objUsedGet(); engineStopped = false; return 0; } function quickLoadEngineFromrequiredState(oop, requiredState) { var failureCode; if (((failureCode = quickLoadEngineFrom(oop))) !== 0) { return failureCode; } if (stateGet() === requiredState) { return 0; } stopReasonPut(GErrorBadState); return GEFWrongState; } function quickLoadEngineFromrequiredStateor(oop, requiredState, alternativeState) { var failureCode; if (((failureCode = quickLoadEngineFrom(oop))) !== 0) { return failureCode; } if (stateGet() === requiredState) { return 0; } if (stateGet() === alternativeState) { return 0; } stopReasonPut(GErrorBadState); return GEFWrongState; } /* Remove any top fills if they have become invalid. */ function quickRemoveInvalidFillsAt(leftX) { if (stackFillSize() === 0) { return null; } while (topRightX() <= leftX) { hideFilldepth(topFill(), topDepth()); if (stackFillSize() === 0) { return null; } } } /* Sort elements i through j of self to be nondescending according to sortBlock. */ /* Note: The original loop has been heavily re-written for C translation */ function quickSortGlobalEdgeTablefromto(array, i, j) { var again; var before; var di; var dij; var dj; var ij; var k; var l; var n; var tmp; var tt; /* The prefix d means the data at that index. */ if (((n = (j + 1) - i)) <= 1) { return 0; } di = array[i]; dj = array[j]; /* i.e., should di precede dj? */ before = getSortsbefore(di, dj); if (!before) { tmp = array[i]; array[i] = array[j]; array[j] = tmp; tt = di; di = dj; dj = tt; } if (n <= 2) { return 0; } /* ij is the midpoint of i and j. */ ij = (i + j) >> 1; /* Sort di,dij,dj. Make dij be their median. */ dij = array[ij]; /* i.e. should di precede dij? */ before = getSortsbefore(di, dij); if (before) { /* i.e., should dij precede dj? */ before = getSortsbefore(dij, dj); if (!before) { /* i.e., should dij precede dj? */ tmp = array[j]; array[j] = array[ij]; array[ij] = tmp; dij = dj; } } else { /* i.e. di should come after dij */ tmp = array[i]; array[i] = array[ij]; array[ij] = tmp; dij = di; } if (n <= 3) { return 0; } k = i; l = j; again = true; while (again) { before = true; while (before) { if (k <= ((--l))) { tmp = array[l]; before = getSortsbefore(dij, tmp); } else { before = false; } } before = true; while (before) { if (((++k)) <= l) { tmp = array[k]; before = getSortsbefore(tmp, dij); } else { before = false; } } again = k <= l; if (again) { tmp = array[k]; array[k] = array[l]; array[l] = tmp; } } quickSortGlobalEdgeTablefromto(array, i, l); quickSortGlobalEdgeTablefromto(array, k, j); } function rShiftTable() { var theTable = [0, 5, 4, 0, 3, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1]; return theTable; } function removeFirstAETEntry() { var index; index = aetStartGet(); aetUsedPut(aetUsedGet() - 1); while (index < aetUsedGet()) { aetBuffer[index] = aetBuffer[index + 1]; ++index; } } function repeatValuemax(delta, maxValue) { var newDelta; newDelta = delta; while (newDelta < 0) { newDelta += maxValue; } while (newDelta >= maxValue) { newDelta -= maxValue; } return newDelta; } function resetGraphicsEngineStats() { workBuffer[GWTimeInitializing] = 0; workBuffer[GWTimeFinishTest] = 0; workBuffer[GWTimeNextGETEntry] = 0; workBuffer[GWTimeAddAETEntry] = 0; workBuffer[GWTimeNextFillEntry] = 0; workBuffer[GWTimeMergeFill] = 0; workBuffer[GWTimeDisplaySpan] = 0; workBuffer[GWTimeNextAETEntry] = 0; workBuffer[GWTimeChangeAETEntry] = 0; workBuffer[GWCountInitializing] = 0; workBuffer[GWCountFinishTest] = 0; workBuffer[GWCountNextGETEntry] = 0; workBuffer[GWCountAddAETEntry] = 0; workBuffer[GWCountNextFillEntry] = 0; workBuffer[GWCountMergeFill] = 0; workBuffer[GWCountDisplaySpan] = 0; workBuffer[GWCountNextAETEntry] = 0; workBuffer[GWCountChangeAETEntry] = 0; workBuffer[GWBezierMonotonSubdivisions] = 0; workBuffer[GWBezierHeightSubdivisions] = 0; workBuffer[GWBezierOverflowSubdivisions] = 0; workBuffer[GWBezierLineConversions] = 0; } function resortFirstAETEntry() { var edge; var leftEdge; var xValue; if (aetStartGet() === 0) { return null; } edge = aetBuffer[aetStartGet()]; xValue = edgeXValueOf(edge); leftEdge = aetBuffer[aetStartGet() - 1]; if (edgeXValueOf(leftEdge) <= xValue) { return null; } moveAETEntryFromedgex(aetStartGet(), edge, xValue); } function returnWideBezierFill() { return (dispatchReturnValue = wideBezierFillOf(dispatchedValue)); } function returnWideBezierWidth() { return (dispatchReturnValue = wideBezierWidthOf(dispatchedValue)); } /* Return the fill of the (wide) line - this method is called from a case. */ function returnWideLineFill() { return (dispatchReturnValue = wideLineFillOf(dispatchedValue)); } /* Return the width of the (wide) line - this method is called from a case. */ function returnWideLineWidth() { return (dispatchReturnValue = wideLineWidthOf(dispatchedValue)); } /* Set the anti-aliasing level. Three levels are supported: 1 - No antialiasing 2 - 2x2 unweighted anti-aliasing 4 - 4x4 unweighted anti-aliasing. */ function setAALevel(level) { var aaLevel; if (level >= 4) { aaLevel = 4; } if ((level >= 2) && (level < 4)) { aaLevel = 2; } if (level < 2) { aaLevel = 1; } aaLevelPut(aaLevel); if (aaLevel === 1) { aaShiftPut(0); aaColorMaskPut(4294967295); aaScanMaskPut(0); } if (aaLevel === 2) { aaShiftPut(1); aaColorMaskPut(4244438268); aaScanMaskPut(1); } if (aaLevel === 4) { aaShiftPut(2); aaColorMaskPut(4042322160); aaScanMaskPut(3); } aaColorShiftPut(aaShiftGet() * 2); aaHalfPixelPut(aaShiftGet()); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } /* Return the run-length value from the given ShortRunArray. */ function shortRunLengthAtfrom(i, runArray) { return (runArray[i]|0) >>> 16; } /* Return the run-length value from the given ShortRunArray. Note: We don't need any coercion to short/int here, since we deal basically only with unsigned values. */ function shortRunValueAtfrom(i, runArray) { return (runArray[i]|0) & 65535; } function showFilldepthrightX(fillIndex, depth, rightX) { if (!allocateStackFillEntry()) { return null; } stackFillValueput(0, fillIndex); stackFillDepthput(0, depth); stackFillRightXput(0, rightX); if (stackFillSize() === stackFillEntryLength()) { return null; } if (fillSortsbefore(0, stackFillSize() - stackFillEntryLength())) { /* New top fill */ stackFillValueput(0, topFillValue()); stackFillDepthput(0, topFillDepth()); stackFillRightXput(0, topFillRightX()); topFillValuePut(fillIndex); topFillDepthPut(depth); topFillRightXPut(rightX); } } function smallSqrtTable() { var theTable = [0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6]; return theTable; } /* Sort the entire global edge table */ function sortGlobalEdgeTable() { quickSortGlobalEdgeTablefromto(getBuffer, 0, getUsedGet() - 1); } function spanEndAAGet() { return workBuffer[GWSpanEndAA]; } function spanEndAAPut(value) { return workBuffer[GWSpanEndAA] = value; } function spanEndGet() { return workBuffer[GWSpanEnd]; } function spanEndPut(value) { return workBuffer[GWSpanEnd] = value; } function spanSizeGet() { return workBuffer[GWSpanSize]; } function spanSizePut(value) { return workBuffer[GWSpanSize] = value; } function spanStartGet() { return workBuffer[GWSpanStart]; } function spanStartPut(value) { return workBuffer[GWSpanStart] = value; } function squaredLengthOfwith(deltaX, deltaY) { return (deltaX * deltaX) + (deltaY * deltaY); } function stackFillDepth(index) { return wbStackValue(index + 1); } function stackFillDepthput(index, value) { return wbStackValueput(index + 1, value); } function stackFillEntryLength() { return 3; } function stackFillRightX(index) { return wbStackValue(index + 2); } function stackFillRightXput(index, value) { return wbStackValueput(index + 2, value); } function stackFillSize() { return wbStackSize(); } function stackFillValue(index) { return wbStackValue(index); } function stackFillValueput(index, value) { return wbStackValueput(index, value); } function stateGet() { return workBuffer[GWState]; } function statePut(value) { return workBuffer[GWState] = value; } /* Initialize the current entry in the GET by stepping to the current scan line */ function stepToFirstBezier() { return stepToFirstBezierInat(getBuffer[getStartGet()], currentYGet()); } /* Initialize the bezier at yValue. TODO: Check if reducing maxSteps from 2*deltaY to deltaY brings a *significant* performance improvement. In theory this should make for double step performance but will cost in quality. Might be that the AA stuff will compensate for this - but I'm not really sure. */ function stepToFirstBezierInat(bezier, yValue) { var deltaY; var endX; var endY; var fwDDx; var fwDDy; var fwDx; var fwDy; var fwX1; var fwX2; var fwY1; var fwY2; var maxSteps; var scaledStepSize; var squaredStepSize; var startX; var startY; var updateData; var viaX; var viaY; /* Do a quick check if there is anything at all to do */ if (!isWide(bezier) && (yValue >= bezierEndYOf(bezier))) { return edgeNumLinesOfput(bezier, 0); } startX = edgeXValueOf(bezier); startY = edgeYValueOf(bezier); viaX = bezierViaXOf(bezier); viaY = bezierViaYOf(bezier); endX = bezierEndXOf(bezier); endY = bezierEndYOf(bezier); /* Initialize integer forward differencing */ deltaY = endY - startY; fwX1 = (viaX - startX) * 2; fwX2 = (startX + endX) - (viaX * 2); fwY1 = (viaY - startY) * 2; fwY2 = (startY + endY) - (viaY * 2); maxSteps = deltaY * 2; if (maxSteps < 2) { maxSteps = 2; } scaledStepSize = DIV(16777216, maxSteps); squaredStepSize = absoluteSquared8Dot24(scaledStepSize); fwDx = fwX1 * scaledStepSize; fwDDx = (fwX2 * squaredStepSize) * 2; fwDx += fwDDx >> 1; fwDy = fwY1 * scaledStepSize; fwDDy = (fwY2 * squaredStepSize) * 2; /* Store the values */ fwDy += fwDDy >> 1; edgeNumLinesOfput(bezier, deltaY); updateData = bezierUpdateDataOf(bezier); updateData[GBUpdateX] = (startX * 256); updateData[GBUpdateY] = (startY * 256); updateData[GBUpdateDX] = fwDx; updateData[GBUpdateDY] = fwDy; updateData[GBUpdateDDX] = fwDDx; updateData[GBUpdateDDY] = fwDDy; if (((startY = edgeYValueOf(bezier))) !== yValue) { stepToNextBezierInat(bezier, yValue); edgeNumLinesOfput(bezier, deltaY - (yValue - startY)); } } /* Initialize the current entry in the GET by stepping to the current scan line */ function stepToFirstLine() { return stepToFirstLineInat(getBuffer[getStartGet()], currentYGet()); } /* Initialize the line at yValue */ function stepToFirstLineInat(line, yValue) { var deltaX; var deltaY; var error; var errorAdjUp; var i; var startY; var widthX; var xDir; var xInc; /* Do a quick check if there is anything at all to do */ if (!isWide(line) && (yValue >= lineEndYOf(line))) { return edgeNumLinesOfput(line, 0); } deltaX = lineEndXOf(line) - edgeXValueOf(line); /* Check if edge goes left to right */ deltaY = lineEndYOf(line) - edgeYValueOf(line); if (deltaX >= 0) { xDir = 1; widthX = deltaX; error = 0; } else { xDir = -1; widthX = 0 - deltaX; error = 1 - deltaY; } if (deltaY === 0) { /* No error for horizontal edges */ error = 0; /* Encodes width and direction */ xInc = deltaX; errorAdjUp = 0; } else { /* Check if edge is y-major */ if (deltaY > widthX) { /* Note: The '>' instead of '>=' could be important here... */ xInc = 0; errorAdjUp = widthX; } else { xInc = (DIV(widthX, deltaY)) * xDir; errorAdjUp = MOD(widthX, deltaY); } } edgeNumLinesOfput(line, deltaY); lineXDirectionOfput(line, xDir); lineXIncrementOfput(line, xInc); lineErrorOfput(line, error); lineErrorAdjUpOfput(line, errorAdjUp); lineErrorAdjDownOfput(line, deltaY); if (((startY = edgeYValueOf(line))) !== yValue) { for (i = startY; i <= (yValue - 1); i++) { stepToNextLineInat(line); } edgeNumLinesOfput(line, deltaY - (yValue - startY)); } } /* Initialize the current entry in the GET by stepping to the current scan line */ function stepToFirstWideBezier() { return stepToFirstWideBezierInat(getBuffer[getStartGet()], currentYGet()); } /* Initialize the bezier at yValue */ function stepToFirstWideBezierInat(bezier, yValue) { var endX; var i; var lineOffset; var lineWidth; var nLines; var startY; var xDir; var yEntry; var yExit; /* Get some values */ lineWidth = wideBezierExtentOf(bezier); /* Compute the incremental values of the bezier */ lineOffset = offsetFromWidth(lineWidth); endX = bezierEndXOf(bezier); startY = edgeYValueOf(bezier); stepToFirstBezierInat(bezier, startY); /* Copy the incremental update data */ nLines = edgeNumLinesOf(bezier); for (i = 0; i <= 5; i++) { wideBezierUpdateDataOf(bezier)[i] = bezierUpdateDataOf(bezier)[i]; } xDir = bezierUpdateDataOf(bezier)[GBUpdateDX]; if (xDir === 0) { bezierUpdateDataOf(bezier)[GBUpdateDDX]; } if (xDir >= 0) { xDir = 1; } else { xDir = -1; } if (xDir < 0) { adjustWideBezierLeftwidthoffsetendX(bezier, lineWidth, lineOffset, endX); } else { adjustWideBezierRightwidthoffsetendX(bezier, lineWidth, lineOffset, endX); } if (nLines === 0) { bezierUpdateDataOf(bezier)[GBUpdateX] = (bezierFinalXOf(bezier) * 256); } edgeNumLinesOfput(bezier, nLines + lineWidth); /* turned on at lineOffset */ yEntry = 0; /* turned off at zero */ yExit = (0 - nLines) - lineOffset; wideBezierEntryOfput(bezier, yEntry); wideBezierExitOfput(bezier, yExit); if ((yEntry >= lineOffset) && (yExit < 0)) { edgeFillsValidate(bezier); } else { edgeFillsInvalidate(bezier); } computeFinalWideBezierValueswidth(bezier, lineWidth); if (startY !== yValue) { /* Note: Must single step here so that entry/exit works */ for (i = startY; i <= (yValue - 1); i++) { stepToNextWideBezierInat(bezier, i); } edgeNumLinesOfput(bezier, edgeNumLinesOf(bezier) - (yValue - startY)); } } /* Initialize the current entry in the GET by stepping to the current scan line */ function stepToFirstWideLine() { return stepToFirstWideLineInat(getBuffer[getStartGet()], currentYGet()); } /* Initialize the wide line at yValue. */ function stepToFirstWideLineInat(line, yValue) { var i; var lineOffset; var lineWidth; var nLines; var startX; var startY; var xDir; var yEntry; var yExit; /* Get some values */ lineWidth = wideLineExtentOf(line); /* Compute the incremental values of the line */ lineOffset = offsetFromWidth(lineWidth); startX = edgeXValueOf(line); startY = edgeYValueOf(line); stepToFirstLineInat(line, startY); nLines = edgeNumLinesOf(line); /* Adjust the line to start at the correct X position */ xDir = lineXDirectionOf(line); edgeXValueOfput(line, startX - lineOffset); edgeNumLinesOfput(line, nLines + lineWidth); if (xDir > 0) { wideLineWidthOfput(line, lineXIncrementOf(line) + lineWidth); } else { wideLineWidthOfput(line, lineWidth - lineXIncrementOf(line)); edgeXValueOfput(line, edgeXValueOf(line) + lineXIncrementOf(line)); } /* turned on at lineOffset */ yEntry = 0; /* turned off at zero */ yExit = (0 - nLines) - lineOffset; wideLineEntryOfput(line, yEntry); wideLineExitOfput(line, yExit); if ((yEntry >= lineOffset) && (yExit < 0)) { edgeFillsValidate(line); } else { edgeFillsInvalidate(line); } if (startY !== yValue) { for (i = startY; i <= (yValue - 1); i++) { stepToNextWideLineInat(line); } edgeNumLinesOfput(line, edgeNumLinesOf(line) - (yValue - startY)); } } /* Process the current entry in the AET by stepping to the next scan line */ function stepToNextBezier() { return stepToNextBezierInat(aetBuffer[aetStartGet()], currentYGet()); } /* Incrementally step to the next scan line in the given bezier update data. */ function stepToNextBezierForwardat(updateData, yValue) { var fwDx; var fwDy; var lastX; var lastY; var minY; lastX = updateData[GBUpdateX]; lastY = updateData[GBUpdateY]; fwDx = updateData[GBUpdateDX]; fwDy = updateData[GBUpdateDY]; /* Step as long as we haven't yet reached minY and also as long as fwDy is greater than zero thus stepping down. Note: The test for fwDy should not be necessary in theory but is a good insurance in practice. */ minY = yValue * 256; while ((minY > lastY) && (fwDy >= 0)) { lastX += (fwDx + 32768) >> 16; lastY += (fwDy + 32768) >> 16; fwDx += updateData[GBUpdateDDX]; fwDy += updateData[GBUpdateDDY]; } updateData[GBUpdateX] = lastX; updateData[GBUpdateY] = lastY; updateData[GBUpdateDX] = fwDx; updateData[GBUpdateDY] = fwDy; return lastX >> 8; } /* Incrementally step to the next scan line in the given bezier */ function stepToNextBezierInat(bezier, yValue) { var xValue; xValue = stepToNextBezierForwardat(bezierUpdateDataOf(bezier), yValue); edgeXValueOfput(bezier, xValue); } /* Process the current entry in the AET by stepping to the next scan line */ function stepToNextLine() { return stepToNextLineInat(aetBuffer[aetStartGet()], currentYGet()); } /* Incrementally step to the next scan line in the given line */ function stepToNextLineInat(line, yValue) { var err; var x; x = edgeXValueOf(line) + lineXIncrementOf(line); err = lineErrorOf(line) + lineErrorAdjUpOf(line); if (err > 0) { x += lineXDirectionOf(line); err -= lineErrorAdjDownOf(line); } lineErrorOfput(line, err); edgeXValueOfput(line, x); } /* Initialize the current entry in the GET by stepping to the current scan line */ function stepToNextWideBezier() { stepToNextWideBezierInat(aetBuffer[aetStartGet()], currentYGet()); } /* Incrementally step to the next scan line in the given wide bezier */ function stepToNextWideBezierInat(bezier, yValue) { var lineOffset; var lineWidth; var yEntry; var yExit; /* Don't inline this */ lineWidth = wideBezierExtentOf(bezier); lineOffset = offsetFromWidth(lineWidth); yEntry = wideBezierEntryOf(bezier) + 1; yExit = wideBezierExitOf(bezier) + 1; wideBezierEntryOfput(bezier, yEntry); wideBezierExitOfput(bezier, yExit); if (yEntry >= lineOffset) { edgeFillsValidate(bezier); } if (yExit >= 0) { edgeFillsInvalidate(bezier); } if ((yExit + lineOffset) < 0) { stepToNextBezierForwardat(bezierUpdateDataOf(bezier), yValue); } else { /* Adjust the last x value to the final x recorded previously */ bezierUpdateDataOf(bezier)[GBUpdateX] = (bezierFinalXOf(bezier) * 256); } stepToNextBezierForwardat(wideBezierUpdateDataOf(bezier), yValue); computeFinalWideBezierValueswidth(bezier, lineWidth); } /* Process the current entry in the AET by stepping to the next scan line */ function stepToNextWideLine() { return stepToNextWideLineInat(aetBuffer[aetStartGet()], currentYGet()); } /* Incrementally step to the next scan line in the given wide line */ function stepToNextWideLineInat(line, yValue) { var lastX; var lineOffset; var lineWidth; var nextX; var yEntry; var yExit; /* Adjust entry/exit values */ yEntry = wideLineEntryOf(line) + 1; yExit = wideLineExitOf(line) + 1; wideLineEntryOfput(line, yEntry); wideLineExitOfput(line, yExit); lineWidth = wideLineExtentOf(line); lineOffset = offsetFromWidth(lineWidth); if (yEntry >= lineOffset) { edgeFillsValidate(line); } if (yExit >= 0) { edgeFillsInvalidate(line); } lastX = edgeXValueOf(line); stepToNextLineInat(line); /* Check for special start/end adjustments */ nextX = edgeXValueOf(line); if ((yEntry <= lineWidth) || ((yExit + lineOffset) >= 0)) { /* Yes, need an update */ adjustWideLineafterSteppingFromto(line, lastX, nextX); } } function stopBecauseOf(stopReason) { stopReasonPut(stopReason); engineStopped = true; } function stopReasonGet() { return workBuffer[GWStopReason]; } function stopReasonPut(value) { return workBuffer[GWStopReason] = value; } function storeEdgeStateFrominto(edge, edgeOop) { if (SIZEOF(edgeOop) < ETBalloonEdgeDataSize) { return interpreterProxy.primitiveFail(); } interpreterProxy.storeIntegerofObjectwithValue(ETIndexIndex, edgeOop, objectIndexOf(edge)); interpreterProxy.storeIntegerofObjectwithValue(ETXValueIndex, edgeOop, edgeXValueOf(edge)); interpreterProxy.storeIntegerofObjectwithValue(ETYValueIndex, edgeOop, currentYGet()); interpreterProxy.storeIntegerofObjectwithValue(ETZValueIndex, edgeOop, edgeZValueOf(edge)); interpreterProxy.storeIntegerofObjectwithValue(ETLinesIndex, edgeOop, edgeNumLinesOf(edge)); lastExportedEdgePut(edge); } function storeEngineStateInto(oop) { objUsedPut(objUsed); } function storeFillStateInto(fillOop) { var fillIndex; var leftX; var rightX; fillIndex = lastExportedFillGet(); leftX = lastExportedLeftXGet(); rightX = lastExportedRightXGet(); if (SIZEOF(fillOop) < FTBalloonFillDataSize) { return interpreterProxy.primitiveFail(); } interpreterProxy.storeIntegerofObjectwithValue(FTIndexIndex, fillOop, objectIndexOf(fillIndex)); interpreterProxy.storeIntegerofObjectwithValue(FTMinXIndex, fillOop, leftX); interpreterProxy.storeIntegerofObjectwithValue(FTMaxXIndex, fillOop, rightX); interpreterProxy.storeIntegerofObjectwithValue(FTYValueIndex, fillOop, currentYGet()); } function storeRenderingState() { if (interpreterProxy.failed()) { return null; } if (engineStopped) { /* Check the stop reason and store the required information */ storeStopStateIntoEdgefill(interpreterProxy.stackObjectValue(1), interpreterProxy.stackObjectValue(0)); } storeEngineStateInto(); interpreterProxy.pop(3); interpreterProxy.pushInteger(stopReasonGet()); } function storeStopStateIntoEdgefill(edgeOop, fillOop) { var edge; var reason; reason = stopReasonGet(); if (reason === GErrorGETEntry) { edge = getBuffer[getStartGet()]; storeEdgeStateFrominto(edge, edgeOop); getStartPut(getStartGet() + 1); } if (reason === GErrorFillEntry) { storeFillStateInto(fillOop); } if (reason === GErrorAETEntry) { edge = aetBuffer[aetStartGet()]; storeEdgeStateFrominto(edge, edgeOop); } } /* Subdivide the given bezier curve if necessary */ function subdivideBezier(index) { var deltaX; var deltaY; var endX; var endY; var startX; var startY; startY = bzStartY(index); /* If the receiver is horizontal, don't do anything */ endY = bzEndY(index); if (endY === startY) { return index; } deltaY = endY - startY; if (deltaY < 0) { deltaY = 0 - deltaY; } if (deltaY > 255) { incrementStatby(GWBezierHeightSubdivisions, 1); return computeBezierSplitAtHalf(index); } startX = bzStartX(index); endX = bzEndX(index); deltaX = endX - startX; if (deltaX < 0) { deltaX = 0 - deltaX; } if ((deltaY * 32) < deltaX) { incrementStatby(GWBezierOverflowSubdivisions, 1); return computeBezierSplitAtHalf(index); } return index; } /* Recursively subdivide the curve on the bezier stack. */ function subdivideBezierFrom(index) { var index1; var index2; var otherIndex; otherIndex = subdivideBezier(index); if (otherIndex !== index) { index1 = subdivideBezierFrom(index); if (engineStopped) { return 0; } index2 = subdivideBezierFrom(otherIndex); if (engineStopped) { return 0; } if (index1 >= index2) { return index1; } else { return index2; } } return index; } /* Check if the given bezier curve is monoton in Y, and, if desired in X. If not, subdivide it */ function subdivideToBeMonotoninX(base, doTestX) { var base2; var index1; var index2; base2 = (index1 = (index2 = subdivideToBeMonotonInY(base))); if (doTestX) { index1 = subdivideToBeMonotonInX(base); } if (index1 > index2) { index2 = index1; } if ((base !== base2) && (doTestX)) { index1 = subdivideToBeMonotonInX(base2); } if (index1 > index2) { index2 = index1; } return index2; } /* Check if the given bezier curve is monoton in X. If not, subdivide it */ function subdivideToBeMonotonInX(index) { var denom; var dx1; var dx2; var endX; var num; var startX; var viaX; startX = bzStartX(index); viaX = bzViaX(index); endX = bzEndX(index); dx1 = viaX - startX; dx2 = endX - viaX; if ((dx1 * dx2) >= 0) { return index; } incrementStatby(GWBezierMonotonSubdivisions, 1); denom = dx2 - dx1; num = dx1; if (num < 0) { num = 0 - num; } if (denom < 0) { denom = 0 - denom; } return computeBeziersplitAt(index, num / denom); } /* Check if the given bezier curve is monoton in Y. If not, subdivide it */ function subdivideToBeMonotonInY(index) { var denom; var dy1; var dy2; var endY; var num; var startY; var viaY; startY = bzStartY(index); viaY = bzViaY(index); endY = bzEndY(index); dy1 = viaY - startY; dy2 = endY - viaY; if ((dy1 * dy2) >= 0) { return index; } incrementStatby(GWBezierMonotonSubdivisions, 1); denom = dy2 - dy1; num = dy1; if (num < 0) { num = 0 - num; } if (denom < 0) { denom = 0 - denom; } return computeBeziersplitAt(index, num / denom); } /* Make the fill style with the given index either visible or invisible */ function toggleFilldepthrightX(fillIndex, depth, rightX) { var hidden; if (stackFillSize() === 0) { if (allocateStackFillEntry()) { topFillValuePut(fillIndex); topFillDepthPut(depth); topFillRightXPut(rightX); } } else { hidden = hideFilldepth(fillIndex, depth); if (!hidden) { showFilldepthrightX(fillIndex, depth, rightX); } } } function toggleFillsOf(edge) { var depth; var fillIndex; if (!needAvailableSpace(stackFillEntryLength() * 2)) { return null; } depth = edgeZValueOf(edge) << 1; fillIndex = edgeLeftFillOf(edge); if (fillIndex !== 0) { toggleFilldepthrightX(fillIndex, depth, 999999999); } fillIndex = edgeRightFillOf(edge); if (fillIndex !== 0) { toggleFilldepthrightX(fillIndex, depth, 999999999); } quickRemoveInvalidFillsAt(edgeXValueOf(edge)); } function toggleWideFillOf(edge) { var depth; var fill; var index; var lineWidth; var rightX; var type; type = edgeTypeOf(edge); dispatchedValue = edge; switch (type) { case 0: case 1: break; case 2: returnWideLineWidth(); break; case 3: returnWideBezierWidth(); break; } lineWidth = dispatchReturnValue; switch (type) { case 0: case 1: break; case 2: returnWideLineFill(); break; case 3: returnWideBezierFill(); break; } fill = dispatchReturnValue; if (fill === 0) { return null; } if (!needAvailableSpace(stackFillEntryLength())) { return null; } /* So lines sort before interior fills */ depth = (edgeZValueOf(edge) << 1) + 1; rightX = edgeXValueOf(edge) + lineWidth; index = findStackFilldepth(fill, depth); if (index === -1) { showFilldepthrightX(fill, depth, rightX); } else { if (stackFillRightX(index) < rightX) { stackFillRightXput(index, rightX); } } quickRemoveInvalidFillsAt(edgeXValueOf(edge)); } function topDepth() { if (stackFillSize() === 0) { return -1; } else { return topFillDepth(); } } function topFill() { if (stackFillSize() === 0) { return 0; } else { return topFillValue(); } } function topFillDepth() { return stackFillDepth(stackFillSize() - stackFillEntryLength()); } function topFillDepthPut(value) { return stackFillDepthput(stackFillSize() - stackFillEntryLength(), value); } function topFillRightX() { return stackFillRightX(stackFillSize() - stackFillEntryLength()); } function topFillRightXPut(value) { return stackFillRightXput(stackFillSize() - stackFillEntryLength(), value); } function topFillValue() { return stackFillValue(stackFillSize() - stackFillEntryLength()); } function topFillValuePut(value) { return stackFillValueput(stackFillSize() - stackFillEntryLength(), value); } function topRightX() { if (stackFillSize() === 0) { return 999999999; } else { return topFillRightX(); } } function transformColor(fillIndex) { var a; var alphaScale; var b; var g; var r; var transform; if (!((fillIndex === 0) || (isFillColor(fillIndex)))) { return fillIndex; } b = fillIndex & 255; g = (fillIndex >>> 8) & 255; r = (fillIndex >>> 16) & 255; a = (fillIndex >>> 24) & 255; if (hasColorTransform()) { transform = colorTransform(); alphaScale = ((a * transform[6]) + transform[7]) / a; r = ((((r * transform[0]) + transform[1]) * alphaScale)|0); g = ((((g * transform[2]) + transform[3]) * alphaScale)|0); b = ((((b * transform[4]) + transform[5]) * alphaScale)|0); a = a * alphaScale|0; r = Math.max(r, 0); r = Math.min(r, 255); g = Math.max(g, 0); g = Math.min(g, 255); b = Math.max(b, 0); b = Math.min(b, 255); a = Math.max(a, 0); a = Math.min(a, 255); } if (a < 1) { return 0; } if ((a < 255) && (needsFlush())) { stopBecauseOf(GErrorNeedFlush); } return ((b + (g << 8)) + (r << 16)) + (a << 24); } /* Transform the given point. If haveMatrix is true then use the current transformation. */ function transformPoint(point) { if (hasEdgeTransform()) { /* Note: AA adjustment is done in #transformPoint: for higher accuracy */ transformPointinto(point, point); } else { /* Multiply each component by aaLevel and add a half pixel */ point[0] = ((point[0] + destOffsetXGet()) * aaLevelGet()); point[1] = ((point[1] + destOffsetYGet()) * aaLevelGet()); } } /* Transform srcPoint into dstPoint by using the currently loaded matrix */ /* Note: This method has been rewritten so that inlining works (e.g., removing the declarations and adding argument coercions at the appropriate points) */ function transformPointinto(srcPoint, dstPoint) { transformPointXyinto((srcPoint[0]|0), (srcPoint[1]|0), dstPoint); } /* Transform srcPoint into dstPoint by using the currently loaded matrix */ /* Note: This should be rewritten so that inlining works (e.g., removing the declarations and adding argument coercions at the appropriate points) */ function transformPointXyinto(xValue, yValue, dstPoint) { var transform; var x; var y; transform = edgeTransform(); x = (((((transform[0] * xValue) + (transform[1] * yValue)) + transform[2]) * aaLevelGet())|0); y = (((((transform[3] * xValue) + (transform[4] * yValue)) + transform[5]) * aaLevelGet())|0); dstPoint[0] = x; dstPoint[1] = y; } /* Transform n (n=1,2,3) points. If haveMatrix is true then the matrix contains the actual transformation. */ function transformPoints(n) { if (n > 0) { transformPoint(point1Get()); } if (n > 1) { transformPoint(point2Get()); } if (n > 2) { transformPoint(point3Get()); } if (n > 3) { transformPoint(point4Get()); } } /* Transform the given width */ function transformWidth(w) { var deltaX; var deltaY; var dstWidth; var dstWidth2; if (w === 0) { return 0; } point1Get()[0] = 0; point1Get()[1] = 0; point2Get()[0] = (w * 256); point2Get()[1] = 0; point3Get()[0] = 0; point3Get()[1] = (w * 256); transformPoints(3); deltaX = (point2Get()[0] - point1Get()[0]); deltaY = (point2Get()[1] - point1Get()[1]); dstWidth = ((Math.sqrt((deltaX * deltaX) + (deltaY * deltaY))|0) + 128) >> 8; deltaX = (point3Get()[0] - point1Get()[0]); deltaY = (point3Get()[1] - point1Get()[1]); dstWidth2 = ((Math.sqrt((deltaX * deltaX) + (deltaY * deltaY))|0) + 128) >> 8; if (dstWidth2 < dstWidth) { dstWidth = dstWidth2; } if (dstWidth === 0) { return 1; } else { return dstWidth; } } function uncheckedTransformColor(fillIndex) { var a; var b; var g; var r; var transform; if (!hasColorTransform()) { return fillIndex; } b = fillIndex & 255; g = (fillIndex >>> 8) & 255; r = (fillIndex >>> 16) & 255; a = (fillIndex >>> 24) & 255; transform = colorTransform(); r = (((r * transform[0]) + transform[1])|0); g = (((g * transform[2]) + transform[3])|0); b = (((b * transform[4]) + transform[5])|0); a = (((a * transform[6]) + transform[7])|0); r = Math.max(r, 0); r = Math.min(r, 255); g = Math.max(g, 0); g = Math.min(g, 255); b = Math.max(b, 0); b = Math.min(b, 255); a = Math.max(a, 0); a = Math.min(a, 255); if (a < 16) { return 0; } return ((b + (g << 8)) + (r << 16)) + (a << 24); } function wbSizeGet() { return workBuffer[GWSize]; } function wbSizePut(value) { return workBuffer[GWSize] = value; } function wbStackClear() { wbTopPut(wbSizeGet()); } function wbStackPop(nItems) { wbTopPut(wbTopGet() + nItems); } function wbStackPush(nItems) { if (!allocateStackEntry(nItems)) { return false; } wbTopPut(wbTopGet() - nItems); return true; } function wbStackSize() { return wbSizeGet() - wbTopGet(); } function wbStackValue(index) { return workBuffer[wbTopGet() + index]; } function wbStackValueput(index, value) { return workBuffer[wbTopGet() + index] = value; } function wbTopGet() { return workBuffer[GWBufferTop]; } function wbTopPut(value) { return workBuffer[GWBufferTop] = value; } function wideBezierEntryOf(line) { return objat(line, GBWideEntry); } function wideBezierEntryOfput(line, value) { return objatput(line, GBWideEntry, value); } function wideBezierExitOf(line) { return objat(line, GBWideExit); } function wideBezierExitOfput(line, value) { return objatput(line, GBWideExit, value); } function wideBezierExtentOf(bezier) { return objat(bezier, GBWideExtent); } function wideBezierExtentOfput(bezier, value) { return objatput(bezier, GBWideExtent, value); } function wideBezierFillOf(bezier) { return objat(bezier, GBWideFill); } function wideBezierFillOfput(bezier, value) { return objatput(bezier, GBWideFill, value); } function wideBezierUpdateDataOf(bezier) { return PTR_ADD(objBuffer, bezier + GBWideUpdateData); } function wideBezierWidthOf(line) { return objat(line, GBWideWidth); } function wideBezierWidthOfput(line, value) { return objatput(line, GBWideWidth, value); } function wideLineEntryOf(line) { return objat(line, GLWideEntry); } function wideLineEntryOfput(line, value) { return objatput(line, GLWideEntry, value); } function wideLineExitOf(line) { return objat(line, GLWideExit); } function wideLineExitOfput(line, value) { return objatput(line, GLWideExit, value); } function wideLineExtentOf(line) { return objat(line, GLWideExtent); } function wideLineExtentOfput(line, value) { return objatput(line, GLWideExtent, value); } function wideLineFillOf(line) { return objat(line, GLWideFill); } function wideLineFillOfput(line, value) { return objatput(line, GLWideFill, value); } function wideLineWidthOf(line) { return objat(line, GLWideWidth); } function wideLineWidthOfput(line, value) { return objatput(line, GLWideWidth, value); } function workBufferPut(wbOop) { workBuffer = wbOop.wordsAsInt32Array(); } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("B2DPlugin", { primitiveMergeFillFrom: primitiveMergeFillFrom, primitiveCopyBuffer: primitiveCopyBuffer, primitiveAddRect: primitiveAddRect, primitiveAddGradientFill: primitiveAddGradientFill, primitiveSetClipRect: primitiveSetClipRect, initialiseModule: initialiseModule, primitiveSetBitBltPlugin: primitiveSetBitBltPlugin, primitiveRegisterExternalEdge: primitiveRegisterExternalEdge, primitiveGetClipRect: primitiveGetClipRect, primitiveAddBezier: primitiveAddBezier, primitiveInitializeProcessing: primitiveInitializeProcessing, primitiveRenderImage: primitiveRenderImage, primitiveGetOffset: primitiveGetOffset, primitiveSetDepth: primitiveSetDepth, primitiveAddBezierShape: primitiveAddBezierShape, primitiveSetEdgeTransform: primitiveSetEdgeTransform, getModuleName: getModuleName, primitiveGetTimes: primitiveGetTimes, primitiveNextActiveEdgeEntry: primitiveNextActiveEdgeEntry, primitiveAddBitmapFill: primitiveAddBitmapFill, primitiveGetDepth: primitiveGetDepth, primitiveAbortProcessing: primitiveAbortProcessing, primitiveNextGlobalEdgeEntry: primitiveNextGlobalEdgeEntry, primitiveGetFailureReason: primitiveGetFailureReason, primitiveDisplaySpanBuffer: primitiveDisplaySpanBuffer, moduleUnloaded: moduleUnloaded, primitiveGetCounts: primitiveGetCounts, primitiveChangedActiveEdgeEntry: primitiveChangedActiveEdgeEntry, primitiveRenderScanline: primitiveRenderScanline, primitiveGetBezierStats: primitiveGetBezierStats, primitiveFinishedProcessing: primitiveFinishedProcessing, setInterpreter: setInterpreter, primitiveNeedsFlush: primitiveNeedsFlush, primitiveAddLine: primitiveAddLine, primitiveSetOffset: primitiveSetOffset, primitiveNextFillEntry: primitiveNextFillEntry, primitiveInitializeBuffer: primitiveInitializeBuffer, primitiveDoProfileStats: primitiveDoProfileStats, primitiveAddActiveEdgeEntry: primitiveAddActiveEdgeEntry, primitiveSetAALevel: primitiveSetAALevel, primitiveNeedsFlushPut: primitiveNeedsFlushPut, primitiveAddCompressedShape: primitiveAddCompressedShape, primitiveSetColorTransform: primitiveSetColorTransform, primitiveAddOval: primitiveAddOval, primitiveRegisterExternalFill: primitiveRegisterExternalFill, primitiveAddPolygon: primitiveAddPolygon, primitiveGetAALevel: primitiveGetAALevel, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin function B3DAcceleratorPlugin() { var renderers = []; // list of all renderers var rendererId = 0; // unique id for each renderer var currentRenderer = null; // set by makeCurrent() var OpenGL = null; // set by setOpenGL() var GL = null; // set by setOpenGL() var B3D_STENCIL_BUFFER = 0x0004; return { getModuleName: function() { return 'B3DAcceleratorPlugin (SqueakJS)'; }, interpreterProxy: null, primHandler: null, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; this.vm = this.interpreterProxy.vm; this.primHandler = this.vm.primHandler; this.prevDCCallback = this.primHandler.display.changedCallback; this.primHandler.display.changedCallback = () => { if (this.prevDCCallback) this.prevDCCallback(); for (const renderer of renderers) { this.adjustCanvas(renderer); } }; return true; }, setOpenGL: function(OpenGLPlugin) { OpenGL = OpenGLPlugin; GL = OpenGLPlugin.GL; if (currentRenderer) OpenGL.makeCurrent(currentRenderer); }, makeCurrent(renderer) { if (currentRenderer !== renderer) { currentRenderer = renderer; if (OpenGL) OpenGL.makeCurrent(renderer); } if (renderer.webgl.isContextLost()) { if (!renderer.warning) { console.warn("B3DAccel: WebGL context lost"); var div = document.createElement("div"); div.style.position = "absolute"; div.style.left = renderer.canvas.offsetLeft + "px"; div.style.top = renderer.canvas.offsetTop + "px"; div.style.width = renderer.canvas.width + "px"; div.style.height = renderer.canvas.height + "px"; div.style.backgroundColor = "rgba(0,0,0,0.8)"; div.style.color = "white"; div.style.fontFamily = "sans-serif"; div.style.fontSize = "24px"; div.style.textAlign = "center"; div.style.lineHeight = renderer.canvas.height + "px"; div.style.pointerEvents = "none"; div.style.cursor = "normal"; div.innerHTML = "WebGL context lost"; document.body.appendChild(div); renderer.warning = div; } } }, currentFromStack: function(i) { var renderer = this.interpreterProxy.stackObjectValue(i); if (!renderer.webgl) return null; this.makeCurrent(renderer); return renderer; }, primitiveAllocateTexture: function(argCount) { if (argCount !== 4) return false; var h = this.interpreterProxy.stackIntegerValue(0); var w = this.interpreterProxy.stackIntegerValue(1); this.interpreterProxy.stackIntegerValue(2); if (!this.currentFromStack(3)) return false; if (w & (w-1)) return false; /* not power of two */ if (h & (h-1)) return false; /* not power of two */ var tex = [0]; OpenGL.glGenTextures(1, tex); var texture = tex[0]; OpenGL.glBindTexture(GL.TEXTURE_2D, texture); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.REPEAT); OpenGL.glTexParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.REPEAT); OpenGL.glTexEnvi(GL.TEXTURE_ENV, GL.TEXTURE_ENV_MODE, GL.MODULATE); OpenGL.glTexImage2D(GL.TEXTURE_2D, 0, GL.RGBA, w, h, 0, GL.BGRA, GL.UNSIGNED_BYTE, null); return this.primHandler.popNandPushIfOK(argCount + 1, texture); }, primitiveSetVerboseLevel: function(argCount) { if (argCount !== 1) return false; this.interpreterProxy.stackIntegerValue(0); this.interpreterProxy.pop(argCount); return true; }, primitiveCompositeTexture: function(argCount) { if (argCount !== 7) return false; if (!this.currentFromStack(6)) return false; var texHandle = this.interpreterProxy.stackIntegerValue(5); var x = this.interpreterProxy.stackIntegerValue(4); var y = this.interpreterProxy.stackIntegerValue(3); var w = this.interpreterProxy.stackIntegerValue(2); var h = this.interpreterProxy.stackIntegerValue(1); var translucent = this.interpreterProxy.booleanValueOf(this.interpreterProxy.stackValue(0)); if (this.interpreterProxy.failed()) return false; var result = this.b3dxCompositeTexture(texHandle, x, y, w, h, translucent); if (!result) return false; this.interpreterProxy.pop(argCount); return true; }, primitiveCreateRendererFlags: function(argCount) { if (argCount !== 5) return false; var flags = this.interpreterProxy.stackIntegerValue(4); var x = this.interpreterProxy.stackIntegerValue(3); var y = this.interpreterProxy.stackIntegerValue(2); var w = this.interpreterProxy.stackIntegerValue(1); var h = this.interpreterProxy.stackIntegerValue(0); if (flags & -8) return false; // create WebGL canvas var canvas = document.createElement("canvas"); canvas.classList.add("b3daccel"); canvas.width = w; canvas.height = h; canvas.style.position = "absolute"; canvas.style.backgroundColor = "transparent"; canvas.style.pointerEvents = "none"; canvas.style.cursor = "normal"; var options = { depth: true, alpha: false, antialias: true }; if (flags & B3D_STENCIL_BUFFER) options.stencil = true; var webgl = canvas.getContext("webgl", options); if (!webgl) return false; // create renderer rendererId++; var renderer = this.primHandler.makeStString("WebGL#" + rendererId); renderers.push(renderer); renderer.rendererId = rendererId; renderer.canvas = canvas; renderer.webgl = webgl; // set viewport this.b3dxSetViewport(renderer, x, y, w, h); document.body.appendChild(canvas); // make renderer accessible to other plugins this.makeCurrent(renderer); return this.primHandler.popNandPushIfOK(argCount + 1, renderer); }, primitiveDestroyRenderer: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; renderers = renderers.filter(r => r !== currentRenderer); if (OpenGL) OpenGL.destroyGL(currentRenderer); if (currentRenderer.warning) { currentRenderer.warning.remove(); currentRenderer.warning = null; } currentRenderer.canvas.remove(); currentRenderer.canvas = null; currentRenderer.webgl = null; currentRenderer = null; this.interpreterProxy.pop(argCount); return true; }, primitiveDestroyTexture: function(argCount) { if (argCount !== 2) return false; var texture = this.interpreterProxy.stackIntegerValue(0); if (!this.currentFromStack(1)) return false; OpenGL.glDeleteTextures(1, [texture]); this.interpreterProxy.pop(argCount); return true; }, primitiveFinishRenderer: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; OpenGL.glFinish(); this.interpreterProxy.pop(argCount); return true; }, primitiveFlushRenderer: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; OpenGL.glFlush(); this.interpreterProxy.pop(argCount); return true; }, primitiveGetRendererSurfaceHandle: function(argCount) { // this would allow BitBlt to draw directly into the renderer surface // but it was only ever implemented for Direct3D not OpenGL // so the image will use a texture overlay instead if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; return false; }, primitiveGetIntProperty: function(argCount) { if (argCount !== 2) return false; var property = this.interpreterProxy.stackIntegerValue(0); if (!this.currentFromStack(1)) return false; var value = this.b3dxGetIntProperty(currentRenderer, property); return this.primHandler.popNandPushIfOK(argCount + 1, value); }, primitiveGetRendererSurfaceWidth: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; var width = currentRenderer.canvas.width; return this.primHandler.popNandPushIfOK(argCount + 1, width); }, primitiveGetRendererSurfaceHeight: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; var height = currentRenderer.canvas.height; return this.primHandler.popNandPushIfOK(argCount + 1, height); }, primitiveGetRendererSurfaceDepth: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; var depth = 32; return this.primHandler.popNandPushIfOK(argCount + 1, depth); }, primitiveGetRendererColorMasks: function(argCount) { if (argCount !== 2) return false; var array = this.interpreterProxy.stackObjectValue(0); if (this.currentFromStack(1)) return false; if (array.pointersSize() !== 4) return false; var masks = [0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000]; for (var i = 0; i < 4; i++) { array.pointers[i] = this.interpreterProxy.positive32BitIntegerFor(masks[i]); } this.interpreterProxy.pop(argCount); return true; }, primitiveSetBufferRect: function(argCount) { if (argCount !== 5) return false; if (!this.currentFromStack(4)) return false; var x = this.interpreterProxy.stackIntegerValue(3); var y = this.interpreterProxy.stackIntegerValue(2); var w = this.interpreterProxy.stackIntegerValue(1); var h = this.interpreterProxy.stackIntegerValue(0); this.b3dxSetViewport(currentRenderer, x, y, w, h); this.interpreterProxy.pop(argCount); return true; }, primitiveSetViewport: function(argCount) { if (argCount !== 5) return false; if (!this.currentFromStack(4)) return false; var x = this.interpreterProxy.stackIntegerValue(3); var y = this.interpreterProxy.stackIntegerValue(2); var w = this.interpreterProxy.stackIntegerValue(1); var h = this.interpreterProxy.stackIntegerValue(0); this.b3dxSetViewport(currentRenderer, x, y, w, h); this.interpreterProxy.pop(argCount); return true; }, primitiveSetTransform: function(argCount) { if (argCount !== 3) return false; if (!this.currentFromStack(2)) return false; var modelViewMatrix = this.stackMatrix(1); var projectionMatrix = this.stackMatrix(0); if (!modelViewMatrix || !projectionMatrix) return false; OpenGL.glMatrixMode(GL.PROJECTION); OpenGL.glLoadMatrixf(projectionMatrix); OpenGL.glMatrixMode(GL.MODELVIEW); OpenGL.glLoadMatrixf(modelViewMatrix); this.interpreterProxy.pop(argCount); return true; }, primitiveSetLights: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; var lightArray = this.interpreterProxy.stackObjectValue(0); if (this.interpreterProxy.failed()) return false; if (!this.b3dxDisableLights(currentRenderer)) return false; if (!lightArray) return false; var lightCount = lightArray.pointersSize(); for (var i = 0; i < lightCount; i++) { var light = this.fetchLightSource(i, lightArray); if (!this.b3dxLoadLight(currentRenderer, i, light)) return false; } this.interpreterProxy.pop(argCount); return true; }, primitiveSetMaterial: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; var material = this.stackMaterialValue(0); if (!material) { OpenGL.glMaterialfv(GL.FRONT, GL.AMBIENT, [0.2, 0.2, 0.2, 1.0]); OpenGL.glMaterialfv(GL.FRONT, GL.DIFFUSE, [0.8, 0.8, 0.8, 1.0]); OpenGL.glMaterialfv(GL.FRONT, GL.SPECULAR, [0.0, 0.0, 0.0, 1.0]); OpenGL.glMaterialfv(GL.FRONT, GL.EMISSION, [0.0, 0.0, 0.0, 1.0]); OpenGL.glMaterialf(GL.FRONT, GL.SHININESS, 0.0); OpenGL.glDisable(GL.TEXTURE_2D); } else { // 0: ambient, 4: diffuse, 8: specular, 12: emission, 16: shininess this.vm.warnOnce("B3DAccel: primitiveSetMaterial not fully implemented"); } this.interpreterProxy.pop(argCount); return true; }, primitiveSwapRendererBuffers: function(argCount) { if (argCount !== 1) return false; if (!this.currentFromStack(0)) return false; // let browser display the rendered frame this.interpreterProxy.vm.breakNow(); // tell the spinner we have been rendering this.primHandler.display.lastTick = this.vm.lastTick; this.interpreterProxy.pop(argCount); return true; }, primitiveTextureDepth: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; var depth = 32; return this.primHandler.popNandPushIfOK(argCount + 1, depth); }, primitiveTextureGetColorMasks: function(argCount) { if (argCount !== 3) return false; if (!this.currentFromStack(2)) return false; this.interpreterProxy.stackIntegerValue(1); var array = this.interpreterProxy.stackObjectValue(0); if (array.pointersSize() !== 4) return false; var masks = [0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000]; for (var i = 0; i < 4; i++) { array.pointers[i] = this.interpreterProxy.positive32BitIntegerFor(masks[i]); } this.interpreterProxy.pop(argCount); return true; }, primitiveTextureByteSex: function(argCount) { if (argCount !== 2) return false; if (!this.currentFromStack(1)) return false; // return > 0 if MSB, = 0 if LSB, var byteSex = 0; return this.primHandler.popNandPushIfOK(argCount + 1, byteSex); }, primitiveTextureSurfaceHandle: function(argCount) { /* GL textures are not directly accessible */ return false; }, primitiveTextureUpload: function(argCount) { if (argCount !== 3) return false; if (!this.currentFromStack(2)) return false; var texture = this.interpreterProxy.stackIntegerValue(1); var form = this.interpreterProxy.stackObjectValue(0); if (form.pointersSize() < 4) return false; var bits = form.pointers[Squeak.Form_bits].words; var w = form.pointers[Squeak.Form_width]; var h = form.pointers[Squeak.Form_height]; var d = form.pointers[Squeak.Form_depth]; var ppw = 32 / d; if (!bits || bits.length !== (w + ppw - 1) / ppw * h) return false; var result = this.b3dxUploadTexture(texture, w, h, d, bits); if (!result) return false; this.interpreterProxy.pop(argCount); return true; }, b3dxCompositeTexture: function(texture, x, y, w, h, translucent) { if (!OpenGL.glIsTexture(texture)) return false; OpenGL.glMatrixMode(GL.MODELVIEW); OpenGL.glPushMatrix(); OpenGL.glLoadIdentity(); OpenGL.glMatrixMode(GL.PROJECTION); OpenGL.glPushMatrix(); OpenGL.glLoadIdentity(); var width = currentRenderer.viewport.w; var height = currentRenderer.viewport.h; OpenGL.glViewport(0, 0, width, height); OpenGL.glScaled(2.0/width, -2/height, 1.0); OpenGL.glTranslated(width*-0.5, height*-0.5, 0.0); //We haven't implemented glPushAttrib and glPopAttrib yet //OpenGL.glPushAttrib(GL.ALL_ATTRIB_BITS); // OpenGL.glShadeModel(GL.FLAT); // not implemented OpenGL.glEnable(GL.TEXTURE_2D); // OpenGL.glDisable(GL.COLOR_MATERIAL); // not implemented // OpenGL.glDisable(GL.DITHER); // OpenGL.glDisable(GL.LIGHTING); OpenGL.glDisable(GL.DEPTH_TEST); OpenGL.glDisable(GL.BLEND); OpenGL.glDisable(GL.CULL_FACE); OpenGL.glDepthMask(GL.FALSE); OpenGL.glColor4d(1.0, 1.0, 1.0, 1.0); if (translucent) { OpenGL.glEnable(GL.BLEND); OpenGL.glBlendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA); } // subtract top and left position of canvas x -= currentRenderer.viewport.x; y -= currentRenderer.viewport.y; OpenGL.glBindTexture(GL.TEXTURE_2D, texture); OpenGL.glBegin(GL.QUADS); OpenGL.glTexCoord2d(0.0, 0.0); OpenGL.glVertex2i(x, y); OpenGL.glTexCoord2d(1.0, 0.0); OpenGL.glVertex2i(x+w, y); OpenGL.glTexCoord2d(1.0, 1.0); OpenGL.glVertex2i(x+w, y+h); OpenGL.glTexCoord2d(0.0, 1.0); OpenGL.glVertex2i(x, y+h); OpenGL.glEnd(); // instead of this ... // OpenGL.glPopAttrib(); // we do this: OpenGL.glDepthMask(GL.TRUE); OpenGL.glEnable(GL.DEPTH_TEST); OpenGL.glEnable(GL.CULL_FACE); OpenGL.glDisable(GL.BLEND); // OpenGL.glEnable(GL.COLOR_MATERIAL); // not implemented // OpenGL.glEnable(GL.DITHER); // not implemented OpenGL.glDisable(GL.TEXTURE_2D); // OpenGL.glShadeModel(GL.SMOOTH); // not implemented OpenGL.glPopMatrix(); OpenGL.glMatrixMode(GL.MODELVIEW); OpenGL.glPopMatrix(); return true; }, adjustCanvas: function(renderer) { var canvas = renderer.canvas; var sq = this.primHandler.display.css; var x = renderer.viewport.x; var y = renderer.viewport.y; var w = renderer.viewport.w; var h = renderer.viewport.h; canvas.width = w; canvas.height = h; canvas.style.left = (sq.left + x * sq.scale) + "px"; canvas.style.top = (sq.top + y * sq.scale) + "px"; canvas.style.width = (w * sq.scale) + "px"; canvas.style.height = (h * sq.scale) + "px"; var warning = renderer.warning; if (warning) { warning.style.left = (sq.left + x * sq.scale) + "px"; warning.style.top = (sq.top + y * sq.scale) + "px"; warning.style.width = (w * sq.scale) + "px"; warning.style.height = (h * sq.scale) + "px"; } }, b3dxSetViewport: function(renderer, x, y, w, h) { renderer.viewport = {x: x, y: y, w: w, h: h}; this.adjustCanvas(renderer); }, b3dxDisableLights: function(renderer) { OpenGL.glDisable(GL.LIGHTING); for (var i = 0; i < 8; i++) { OpenGL.glDisable(GL.LIGHT0 + i); } return true; }, b3dxLoadLight: function(renderer, index, light) { return true; }, b3dxGetIntProperty: function(renderer, prop) { // switch (prop) { // case 1: /* backface culling */ // if (!glIsEnabled(GL_CULL_FACE)) return 0; // glGetIntegerv(GL_FRONT_FACE, & v); // if (v == GL_CW) return 1; // if (v == GL_CCW) return -1; // return 0; // case 2: /* polygon mode */ // glGetIntegerv(GL_POLYGON_MODE, & v); // ERROR_CHECK; // return v; // case 3: /* point size */ // glGetIntegerv(GL_POINT_SIZE, & v); // ERROR_CHECK; // return v; // case 4: /* line width */ // glGetIntegerv(GL_LINE_WIDTH, & v); // ERROR_CHECK; // return v; // case 5: /* blend enable */ // return glIsEnabled(GL_BLEND); // case 6: /* blend source factor */ // case 7: /* blend dest factor */ // if (prop == 6) // glGetIntegerv(GL_BLEND_SRC, & v); // else // glGetIntegerv(GL_BLEND_DST, & v); // ERROR_CHECK; // switch (v) { // case GL_ZERO: return 0; // case GL_ONE: return 1; // case GL_SRC_COLOR: return 2; // case GL_ONE_MINUS_SRC_COLOR: return 3; // case GL_DST_COLOR: return 4; // case GL_ONE_MINUS_DST_COLOR: return 5; // case GL_SRC_ALPHA: return 6; // case GL_ONE_MINUS_SRC_ALPHA: return 7; // case GL_DST_ALPHA: return 8; // case GL_ONE_MINUS_DST_ALPHA: return 9; // case GL_SRC_ALPHA_SATURATE: return 10; // default: return -1; // } // } return 0; }, b3dxUploadTexture: function(texture, w, h, d, bits) { if (!OpenGL.glIsTexture(texture)) return false; OpenGL.glBindTexture(GL.TEXTURE_2D, texture); OpenGL.glTexSubImage2D(GL.TEXTURE_2D, 0, 0, 0, w, h, GL.RGBA, GL.UNSIGNED_BYTE, bits.buffer); return true; }, fetchLightSource: function(index, lightArray) { var light = lightArray.pointers[index]; if (!light) return null; return light; }, stackMatrix: function(stackIndex) { var m = this.interpreterProxy.stackObjectValue(stackIndex); if (!m.words || m.words.length !== 16) return null; return m.wordsAsFloat32Array(); }, stackMaterialValue: function(stackIndex) { var material = this.interpreterProxy.stackObjectValue(stackIndex); if (!material.words) return null; return material.wordsAsFloat32Array(); }, } } function registerB3DAcceleratorPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('B3DAcceleratorPlugin', B3DAcceleratorPlugin()); } else self.setTimeout(registerB3DAcceleratorPlugin, 100); } registerB3DAcceleratorPlugin(); /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:20 pm */ /* Automatically generated by JSSmartSyntaxPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from BitBltSimulation VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function BitBltPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; /*** Functions ***/ function CLASSOF(obj) { return typeof obj === "number" ? interpreterProxy.classSmallInteger() : obj.sqClass } function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function BYTESIZEOF(obj) { return obj.bytes ? obj.bytes.length : obj.words ? obj.words.length * 4 : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift function SHIFT(a, b) { return b < 0 ? (b < -31 ? 0 : a >>> (0-b) ) : (b > 31 ? 0 : a << b); } /*** Constants ***/ var AllOnes = 4294967295; var AlphaIndex = 3; var BBClipHeightIndex = 13; var BBClipWidthIndex = 12; var BBClipXIndex = 10; var BBClipYIndex = 11; var BBColorMapIndex = 14; var BBDestFormIndex = 0; var BBDestXIndex = 4; var BBDestYIndex = 5; var BBHalftoneFormIndex = 2; var BBHeightIndex = 7; var BBRuleIndex = 3; var BBSourceFormIndex = 1; var BBSourceXIndex = 8; var BBSourceYIndex = 9; var BBWarpBase = 15; var BBWidthIndex = 6; var BlueIndex = 2; var ColorMapFixedPart = 2; var ColorMapIndexedPart = 4; var ColorMapNewStyle = 8; var ColorMapPresent = 1; var FixedPt1 = 16384; var FormBitsIndex = 0; var FormDepthIndex = 3; var FormHeightIndex = 2; var FormWidthIndex = 1; var GreenIndex = 1; var OpTableSize = 43; var RedIndex = 0; /*** Variables ***/ var affectedB = 0; var affectedL = 0; var affectedR = 0; var affectedT = 0; var bbH = 0; var bbW = 0; var bitBltOop = 0; var bitCount = 0; var clipHeight = 0; var clipWidth = 0; var clipX = 0; var clipY = 0; var cmBitsPerColor = 0; var cmFlags = 0; var cmLookupTable = null; var cmMask = 0; var cmMaskTable = null; var cmShiftTable = null; var combinationRule = 0; var componentAlphaModeAlpha = 0; var componentAlphaModeColor = 0; var destBits = 0; var destDelta = 0; var destDepth = 0; var destForm = 0; var destHeight = 0; var destIndex = 0; var destMSB = 0; var destMask = 0; var destPPW = 0; var destPitch = 0; var destWidth = 0; var destX = 0; var destY = 0; var dither8Lookup = new Array(4096); var ditherMatrix4x4 = [ 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 ]; var ditherThresholds16 = [ 0, 2, 4, 6, 8, 12, 14, 16 ]; var ditherValues16 = [ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 ]; var dstBitShift = 0; var dx = 0; var dy = 0; var gammaLookupTable = null; var hDir = 0; var halftoneBase = 0; var halftoneForm = 0; var halftoneHeight = 0; var hasSurfaceLock = 0; var height = 0; var interpreterProxy = null; var isWarping = 0; var lockSurfaceFn = null; var mask1 = 0; var mask2 = 0; var maskTable = [ 0, 1, 3, 0, 15, 31, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 65535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1 ]; var moduleName = "BitBltPlugin 3 November 2014 (e)"; var nWords = 0; var noHalftone = 0; var noSource = 0; var opTable = new Array(43); var preload = 0; var querySurfaceFn = null; var skew = 0; var sourceAlpha = 0; var sourceBits = 0; var sourceDelta = 0; var sourceDepth = 0; var sourceForm = 0; var sourceHeight = 0; var sourceIndex = 0; var sourceMSB = 0; var sourcePPW = 0; var sourcePitch = 0; var sourceWidth = 0; var sourceX = 0; var sourceY = 0; var srcBitShift = 0; var sx = 0; var sy = 0; var ungammaLookupTable = null; var unlockSurfaceFn = null; var vDir = 0; var warpAlignMask = 0; var warpAlignShift = 0; var warpBitShiftTable = new Array(32); var warpSrcMask = 0; var warpSrcShift = 0; var width = 0; /* Subract the pixels in the source and destination, color by color, and return the sum of the absolute value of all the differences. For non-rgb, XOR the two and return the number of differing pixels. Note that the region is not clipped to bit boundaries, but only to the nearest (enclosing) word. This is because copyLoop does not do pre-merge masking. For accurate results, you must subtract the values obtained from the left and right fringes. */ function OLDrgbDiffwith(sourceWord, destinationWord) { var diff; var pixMask; if (destDepth < 16) { /* Just xor and count differing bits if not RGB */ diff = sourceWord ^ destinationWord; pixMask = maskTable[destDepth]; while (!(diff === 0)) { if ((diff & pixMask) !== 0) { ++bitCount; } diff = SHR(diff, destDepth); } return destinationWord; } if (destDepth === 16) { diff = partitionedSubfromnBitsnPartitions(sourceWord, destinationWord, 5, 3); bitCount = ((bitCount + (diff & 31)) + ((diff >>> 5) & 31)) + ((diff >>> 10) & 31); diff = partitionedSubfromnBitsnPartitions(sourceWord >>> 16, destinationWord >>> 16, 5, 3); bitCount = ((bitCount + (diff & 31)) + ((diff >>> 5) & 31)) + ((diff >>> 10) & 31); } else { diff = partitionedSubfromnBitsnPartitions(sourceWord, destinationWord, 8, 3); bitCount = ((bitCount + (diff & 255)) + ((diff >>> 8) & 255)) + ((diff >>> 16) & 255); } return destinationWord; } /* Tally pixels into the color map. Note that the source should be specified = destination, in order for the proper color map checks to be performed at setup. Note that the region is not clipped to bit boundaries, but only to the nearest (enclosing) word. This is because copyLoop does not do pre-merge masking. For accurate results, you must subtract the values obtained from the left and right fringes. */ function OLDtallyIntoMapwith(sourceWord, destinationWord) { var pixMask; var mapIndex; var i; var shiftWord; if ((cmFlags & (ColorMapPresent | ColorMapIndexedPart)) !== (ColorMapPresent | ColorMapIndexedPart)) { return destinationWord; } if (destDepth < 16) { /* loop through all packed pixels. */ pixMask = maskTable[destDepth] & cmMask; shiftWord = destinationWord; for (i = 1; i <= destPPW; i++) { mapIndex = shiftWord & pixMask; tallyMapAtput(mapIndex, tallyMapAt(mapIndex) + 1); shiftWord = SHR(shiftWord, destDepth); } return destinationWord; } if (destDepth === 16) { /* Two pixels Tally the right half... */ mapIndex = rgbMapfromto(destinationWord & 65535, 5, cmBitsPerColor); tallyMapAtput(mapIndex, tallyMapAt(mapIndex) + 1); mapIndex = rgbMapfromto(destinationWord >>> 16, 5, cmBitsPerColor); tallyMapAtput(mapIndex, tallyMapAt(mapIndex) + 1); } else { /* Just one pixel. */ mapIndex = rgbMapfromto(destinationWord, 8, cmBitsPerColor); tallyMapAtput(mapIndex, tallyMapAt(mapIndex) + 1); } return destinationWord; } function addWordwith(sourceWord, destinationWord) { return sourceWord + destinationWord; } /* Blend sourceWord with destinationWord, assuming both are 32-bit pixels. The source is assumed to have 255*alpha in the high 8 bits of each pixel, while the high 8 bits of the destinationWord will be ignored. The blend produced is alpha*source + (1-alpha)*dest, with the computation being performed independently on each color component. The high byte of the result will be 0. */ function alphaBlendwith(sourceWord, destinationWord) { var unAlpha; var blendRB; var blendAG; var result; var alpha; /* High 8 bits of source pixel */ alpha = sourceWord >>> 24; if (alpha === 0) { return destinationWord; } if (alpha === 255) { return sourceWord; } unAlpha = 255 - alpha; /* blend red and blue */ blendRB = (((sourceWord & 16711935) * alpha) + ((destinationWord & 16711935) * unAlpha)) + 16711935; /* blend alpha and green */ blendAG = (((((sourceWord >>> 8) | 16711680) & 16711935) * alpha) + (((destinationWord >>> 8) & 16711935) * unAlpha)) + 16711935; /* divide by 255 */ blendRB = ((blendRB + (((blendRB - 65537) >>> 8) & 16711935)) >>> 8) & 16711935; blendAG = ((blendAG + (((blendAG - 65537) >>> 8) & 16711935)) >>> 8) & 16711935; result = blendRB | (blendAG << 8); return result; } function alphaBlendConstwith(sourceWord, destinationWord) { return alphaBlendConstwithpaintMode(sourceWord, destinationWord, false); } /* Blend sourceWord with destinationWord using a constant alpha. Alpha is encoded as 0 meaning 0.0, and 255 meaning 1.0. The blend produced is alpha*source + (1.0-alpha)*dest, with the computation being performed independently on each color component. This function could eventually blend into any depth destination, using the same color averaging and mapping as warpBlt. paintMode = true means do nothing if the source pixel value is zero. */ /* This first implementation works with dest depths of 16 and 32 bits only. Normal color mapping will allow sources of lower depths in this case, and results can be mapped directly by truncation, so no extra color maps are needed. To allow storing into any depth will require subsequent addition of two other colormaps, as is the case with WarpBlt. */ function alphaBlendConstwithpaintMode(sourceWord, destinationWord, paintMode) { var rgbMask; var pixMask; var pixBlend; var j; var sourceShifted; var result; var shift; var sourcePixVal; var i; var unAlpha; var destPixVal; var blendRB; var blendAG; var bitsPerColor; var blend; var destShifted; var maskShifted; if (destDepth < 16) { return destinationWord; } unAlpha = 255 - sourceAlpha; result = destinationWord; if (destPPW === 1) { /* 32bpp blends include alpha */ if (!(paintMode && (sourceWord === 0))) { /* painting a transparent pixel */ /* blendRB red and blue */ blendRB = (((sourceWord & 16711935) * sourceAlpha) + ((destinationWord & 16711935) * unAlpha)) + 16711935; /* blendRB alpha and green */ blendAG = ((((sourceWord >>> 8) & 16711935) * sourceAlpha) + (((destinationWord >>> 8) & 16711935) * unAlpha)) + 16711935; /* divide by 255 */ blendRB = ((blendRB + (((blendRB - 65537) >>> 8) & 16711935)) >>> 8) & 16711935; blendAG = ((blendAG + (((blendAG - 65537) >>> 8) & 16711935)) >>> 8) & 16711935; result = blendRB | (blendAG << 8); } } else { pixMask = maskTable[destDepth]; bitsPerColor = 5; rgbMask = 31; maskShifted = destMask; destShifted = destinationWord; sourceShifted = sourceWord; for (j = 1; j <= destPPW; j++) { sourcePixVal = sourceShifted & pixMask; if (!(((maskShifted & pixMask) === 0) || (paintMode && (sourcePixVal === 0)))) { destPixVal = destShifted & pixMask; pixBlend = 0; for (i = 1; i <= 3; i++) { shift = (i - 1) * bitsPerColor; blend = (DIV((((((SHR(sourcePixVal, shift)) & rgbMask) * sourceAlpha) + (((SHR(destPixVal, shift)) & rgbMask) * unAlpha)) + 254), 255)) & rgbMask; pixBlend = pixBlend | (SHL(blend, shift)); } result = (result & ~(SHL(pixMask, ((j - 1) * 16)))) | (SHL(pixBlend, ((j - 1) * 16))); } maskShifted = SHR(maskShifted, destDepth); sourceShifted = SHR(sourceShifted, destDepth); destShifted = SHR(destShifted, destDepth); } } return result; } /* Blend sourceWord with destinationWord using the alpha value from sourceWord. Alpha is encoded as 0 meaning 0.0, and 255 meaning 1.0. In contrast to alphaBlend:with: the color produced is srcColor + (1-srcAlpha) * dstColor e.g., it is assumed that the source color is already scaled. */ function alphaBlendScaledwith(sourceWord, destinationWord) { var unAlpha; var rb; var ag; /* Do NOT inline this into optimized loops */ /* High 8 bits of source pixel is source opacity (ARGB format) */ unAlpha = 255 - (sourceWord >>> 24); /* blend red and blue components */ rb = ((((destinationWord & 16711935) * unAlpha) >>> 8) & 16711935) + (sourceWord & 16711935); /* blend alpha and green components */ ag = (((((destinationWord >>> 8) & 16711935) * unAlpha) >>> 8) & 16711935) + ((sourceWord >>> 8) & 16711935); /* saturate red and blue components if there is a carry */ rb = (rb & 16711935) | (((rb & 16777472) * 255) >>> 8); /* saturate alpha and green components if there is a carry */ ag = ((ag & 16711935) << 8) | ((ag & 16777472) * 255); return ag | rb; } function alphaPaintConstwith(sourceWord, destinationWord) { if (sourceWord === 0) { return destinationWord; } return alphaBlendConstwithpaintMode(sourceWord, destinationWord, true); } /* This version assumes combinationRule = 34 sourcePixSize = 32 destPixSize = 16 sourceForm ~= destForm. */ function alphaSourceBlendBits16() { var ditherBase; var ditherThreshold; var srcShift; var sourceWord; var srcIndex; var deltaX; var dstIndex; var srcAlpha; var dstMask; var deltaY; var srcY; var destWord; var dstY; var ditherIndex; /* This particular method should be optimized in itself */ /* So we can pre-decrement */ deltaY = bbH + 1; srcY = sy; dstY = dy; srcShift = (dx & 1) * 16; if (destMSB) { srcShift = 16 - srcShift; } /* This is the outer loop */ mask1 = SHL(65535, (16 - srcShift)); while (((--deltaY)) !== 0) { srcIndex = ((srcY * sourcePitch)) + (sx * 4); dstIndex = ((dstY * destPitch)) + ((dx >> 1) * 4); ditherBase = (dstY & 3) * 4; /* For pre-increment */ ditherIndex = (sx & 3) - 1; /* So we can pre-decrement */ deltaX = bbW + 1; dstMask = mask1; if (dstMask === 65535) { srcShift = 16; } else { srcShift = 0; } while (((--deltaX)) !== 0) { ditherThreshold = ditherMatrix4x4[ditherBase + ((ditherIndex = (ditherIndex + 1) & 3))]; sourceWord = sourceBits[srcIndex >>> 2]; srcAlpha = sourceWord >>> 24; if (srcAlpha === 255) { /* Dither from 32 to 16 bit */ sourceWord = dither32To16threshold(sourceWord, ditherThreshold); if (sourceWord === 0) { sourceWord = SHL(1, srcShift); } else { sourceWord = SHL(sourceWord, srcShift); } dstLongAtputmask(dstIndex, sourceWord, dstMask); } else { /* srcAlpha ~= 255 */ if (srcAlpha !== 0) { /* 0 < srcAlpha < 255 */ /* If we have to mix colors then just copy a single word */ destWord = destBits[dstIndex >>> 2]; destWord = destWord & ~dstMask; /* Expand from 16 to 32 bit by adding zero bits */ destWord = SHR(destWord, srcShift); /* Mix colors */ destWord = (((destWord & 31744) << 9) | ((destWord & 992) << 6)) | (((destWord & 31) << 3) | 4278190080); /* And dither */ sourceWord = alphaBlendScaledwith(sourceWord, destWord); sourceWord = dither32To16threshold(sourceWord, ditherThreshold); if (sourceWord === 0) { sourceWord = SHL(1, srcShift); } else { sourceWord = SHL(sourceWord, srcShift); } dstLongAtputmask(dstIndex, sourceWord, dstMask); } } srcIndex += 4; if (destMSB) { if (srcShift === 0) { dstIndex += 4; } } else { if (srcShift !== 0) { dstIndex += 4; } } /* Toggle between 0 and 16 */ srcShift = srcShift ^ 16; dstMask = ~dstMask; } ++srcY; ++dstY; } } /* This version assumes combinationRule = 34 sourcePixSize = destPixSize = 32 sourceForm ~= destForm. Note: The inner loop has been optimized for dealing with the special cases of srcAlpha = 0.0 and srcAlpha = 1.0 */ function alphaSourceBlendBits32() { var sourceWord; var srcIndex; var deltaX; var dstIndex; var srcAlpha; var deltaY; var srcY; var destWord; var dstY; /* This particular method should be optimized in itself */ /* Give the compile a couple of hints */ /* The following should be declared as pointers so the compiler will notice that they're used for accessing memory locations (good to know on an Intel architecture) but then the increments would be different between ST code and C code so must hope the compiler notices what happens (MS Visual C does) */ /* So we can pre-decrement */ deltaY = bbH + 1; srcY = sy; /* This is the outer loop */ dstY = dy; while (((--deltaY)) !== 0) { srcIndex = ((srcY * sourcePitch)) + (sx * 4); dstIndex = ((dstY * destPitch)) + (dx * 4); /* So we can pre-decrement */ /* This is the inner loop */ deltaX = bbW + 1; while (((--deltaX)) !== 0) { sourceWord = sourceBits[srcIndex >>> 2]; srcAlpha = sourceWord >>> 24; if (srcAlpha === 255) { destBits[dstIndex >>> 2] = sourceWord; srcIndex += 4; /* Now copy as many words as possible with alpha = 255 */ dstIndex += 4; while ((((--deltaX)) !== 0) && ((((sourceWord = sourceBits[srcIndex >>> 2])) >>> 24) === 255)) { destBits[dstIndex >>> 2] = sourceWord; srcIndex += 4; dstIndex += 4; } ++deltaX; } else { /* srcAlpha ~= 255 */ if (srcAlpha === 0) { srcIndex += 4; /* Now skip as many words as possible, */ dstIndex += 4; while ((((--deltaX)) !== 0) && ((((sourceWord = sourceBits[srcIndex >>> 2])) >>> 24) === 0)) { srcIndex += 4; dstIndex += 4; } ++deltaX; } else { /* 0 < srcAlpha < 255 */ /* If we have to mix colors then just copy a single word */ destWord = destBits[dstIndex >>> 2]; destWord = alphaBlendScaledwith(sourceWord, destWord); destBits[dstIndex >>> 2] = destWord; srcIndex += 4; dstIndex += 4; } } } ++srcY; ++dstY; } } /* This version assumes combinationRule = 34 sourcePixSize = 32 destPixSize = 8 sourceForm ~= destForm. Note: This is not real blending since we don't have the source colors available. */ function alphaSourceBlendBits8() { var srcShift; var sourceWord; var srcIndex; var deltaX; var mappingTable; var dstIndex; var adjust; var mapperFlags; var srcAlpha; var dstMask; var deltaY; var srcY; var destWord; var dstY; mappingTable = default8To32Table(); mapperFlags = cmFlags & -9; /* So we can pre-decrement */ deltaY = bbH + 1; srcY = sy; dstY = dy; mask1 = (dx & 3) * 8; if (destMSB) { mask1 = 24 - mask1; } mask2 = AllOnes ^ (SHL(255, mask1)); if ((dx & 1) === 0) { adjust = 0; } else { adjust = 522133279; } if ((dy & 1) === 0) { adjust = adjust ^ 522133279; } while (((--deltaY)) !== 0) { adjust = adjust ^ 522133279; srcIndex = ((srcY * sourcePitch)) + (sx * 4); dstIndex = ((dstY * destPitch)) + ((dx >> 2) * 4); /* So we can pre-decrement */ deltaX = bbW + 1; srcShift = mask1; /* This is the inner loop */ dstMask = mask2; while (((--deltaX)) !== 0) { sourceWord = (sourceBits[srcIndex >>> 2] & ~adjust) + adjust; srcAlpha = sourceWord >>> 24; if (srcAlpha > 31) { /* Everything below 31 is transparent */ if (srcAlpha < 224) { /* Everything above 224 is opaque */ destWord = destBits[dstIndex >>> 2]; destWord = destWord & ~dstMask; destWord = SHR(destWord, srcShift); destWord = mappingTable[destWord]; sourceWord = alphaBlendScaledwith(sourceWord, destWord); } sourceWord = mapPixelflags(sourceWord, mapperFlags); /* Store back */ sourceWord = SHL(sourceWord, srcShift); dstLongAtputmask(dstIndex, sourceWord, dstMask); } srcIndex += 4; if (destMSB) { if (srcShift === 0) { dstIndex += 4; srcShift = 24; dstMask = 16777215; } else { srcShift -= 8; dstMask = (dstMask >>> 8) | 4278190080; } } else { if (srcShift === 24) { dstIndex += 4; srcShift = 0; dstMask = 4294967040; } else { srcShift += 8; dstMask = (dstMask << 8) | 255; } } adjust = adjust ^ 522133279; } ++srcY; ++dstY; } } function bitAndwith(sourceWord, destinationWord) { return sourceWord & destinationWord; } function bitAndInvertwith(sourceWord, destinationWord) { return sourceWord & ~destinationWord; } function bitInvertAndwith(sourceWord, destinationWord) { return ~sourceWord & destinationWord; } function bitInvertAndInvertwith(sourceWord, destinationWord) { return ~sourceWord & ~destinationWord; } function bitInvertDestinationwith(sourceWord, destinationWord) { return ~destinationWord; } function bitInvertOrwith(sourceWord, destinationWord) { return ~sourceWord | destinationWord; } function bitInvertOrInvertwith(sourceWord, destinationWord) { return ~sourceWord | ~destinationWord; } function bitInvertSourcewith(sourceWord, destinationWord) { return ~sourceWord; } function bitInvertXorwith(sourceWord, destinationWord) { return ~sourceWord ^ destinationWord; } function bitOrwith(sourceWord, destinationWord) { return sourceWord | destinationWord; } function bitOrInvertwith(sourceWord, destinationWord) { return sourceWord | ~destinationWord; } function bitXorwith(sourceWord, destinationWord) { return sourceWord ^ destinationWord; } /* check for possible overlap of source and destination */ /* ar 10/19/1999: This method requires surfaces to be locked. */ function checkSourceOverlap() { var t; if ((sourceForm === destForm) && (dy >= sy)) { if (dy > sy) { /* have to start at bottom */ vDir = -1; sy = (sy + bbH) - 1; dy = (dy + bbH) - 1; } else { if ((dy === sy) && (dx > sx)) { /* y's are equal, but x's are backward */ hDir = -1; /* start at right */ sx = (sx + bbW) - 1; /* and fix up masks */ dx = (dx + bbW) - 1; if (nWords > 1) { t = mask1; mask1 = mask2; mask2 = t; } } } destIndex = ((dy * destPitch)) + ((DIV(dx, destPPW)) * 4); destDelta = (destPitch * vDir) - (4 * (nWords * hDir)); } } function clearWordwith(source, destination) { return 0; } /* clip and adjust source origin and extent appropriately */ /* first in x */ function clipRange() { if (destX >= clipX) { sx = sourceX; dx = destX; bbW = width; } else { sx = sourceX + (clipX - destX); bbW = width - (clipX - destX); dx = clipX; } if ((dx + bbW) > (clipX + clipWidth)) { bbW -= (dx + bbW) - (clipX + clipWidth); } if (destY >= clipY) { sy = sourceY; dy = destY; bbH = height; } else { sy = (sourceY + clipY) - destY; bbH = height - (clipY - destY); dy = clipY; } if ((dy + bbH) > (clipY + clipHeight)) { bbH -= (dy + bbH) - (clipY + clipHeight); } if (noSource) { return null; } if (sx < 0) { dx -= sx; bbW += sx; sx = 0; } if ((sx + bbW) > sourceWidth) { bbW -= (sx + bbW) - sourceWidth; } if (sy < 0) { dy -= sy; bbH += sy; sy = 0; } if ((sy + bbH) > sourceHeight) { bbH -= (sy + bbH) - sourceHeight; } } /* This function is exported for the Balloon engine */ function copyBits() { clipRange(); if ((bbW <= 0) || (bbH <= 0)) { /* zero width or height; noop */ affectedL = (affectedR = (affectedT = (affectedB = 0))); return null; } if (!lockSurfaces()) { return interpreterProxy.primitiveFail(); } // skipping ifdef ENABLE_FAST_BLT copyBitsLockedAndClipped(); unlockSurfaces(); } /* Support for the balloon engine. */ function copyBitsFromtoat(startX, stopX, yValue) { destX = startX; destY = yValue; sourceX = startX; width = stopX - startX; copyBits(); showDisplayBits(); } /* Perform the actual copyBits operation. Assume: Surfaces have been locked and clipping was performed. */ function copyBitsLockedAndClipped() { var done; copyBitsRule41Test(); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFail(); } done = tryCopyingBitsQuickly(); if (done) { return null; } if ((combinationRule === 30) || (combinationRule === 31)) { /* Check and fetch source alpha parameter for alpha blend */ if (interpreterProxy.methodArgumentCount() === 1) { sourceAlpha = interpreterProxy.stackIntegerValue(0); if (!(!interpreterProxy.failed() && ((sourceAlpha >= 0) && (sourceAlpha <= 255)))) { return interpreterProxy.primitiveFail(); } } else { return interpreterProxy.primitiveFail(); } } /* Choose and perform the actual copy loop. */ bitCount = 0; performCopyLoop(); if ((combinationRule === 22) || (combinationRule === 32)) { /* zero width and height; return the count */ affectedL = (affectedR = (affectedT = (affectedB = 0))); } if (hDir > 0) { affectedL = dx; affectedR = dx + bbW; } else { affectedL = (dx - bbW) + 1; affectedR = dx + 1; } if (vDir > 0) { affectedT = dy; affectedB = dy + bbH; } else { affectedT = (dy - bbH) + 1; affectedB = dy + 1; } } /* Test possible use of rule 41, rgbComponentAlpha:with: Nothing to return, just set up some variables */ function copyBitsRule41Test() { var ungammaLookupTableOop; var gammaLookupTableOop; if (combinationRule === 41) { /* fetch the forecolor into componentAlphaModeColor. */ componentAlphaModeAlpha = 255; componentAlphaModeColor = 16777215; gammaLookupTable = null; ungammaLookupTable = null; if (interpreterProxy.methodArgumentCount() >= 2) { componentAlphaModeAlpha = interpreterProxy.stackIntegerValue(interpreterProxy.methodArgumentCount() - 2); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFail(); } componentAlphaModeColor = interpreterProxy.stackIntegerValue(interpreterProxy.methodArgumentCount() - 1); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFail(); } if (interpreterProxy.methodArgumentCount() === 4) { gammaLookupTableOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.isBytes(gammaLookupTableOop)) { gammaLookupTable = gammaLookupTableOop.bytes; } ungammaLookupTableOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.isBytes(ungammaLookupTableOop)) { ungammaLookupTable = ungammaLookupTableOop.bytes; } } } else { if (interpreterProxy.methodArgumentCount() === 1) { componentAlphaModeColor = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return interpreterProxy.primitiveFail(); } } else { return interpreterProxy.primitiveFail(); } } } } /* This version of the inner loop assumes noSource = false. */ function copyLoop() { var mergeWord; var skewWord; var skewMask; var halftoneWord; var unskew; var mergeFnwith; var hInc; var destWord; var word; var prevWord; var y; var i; var thisWord; var notSkewMask; mergeFnwith = opTable[combinationRule + 1]; /* Byte delta */ /* degenerate skew fixed for Sparc. 10/20/96 ikp */ hInc = hDir * 4; if (skew === -32) { skew = (unskew = (skewMask = 0)); } else { if (skew < 0) { unskew = skew + 32; skewMask = SHL(AllOnes, (0 - skew)); } else { if (skew === 0) { unskew = 0; skewMask = AllOnes; } else { unskew = skew - 32; skewMask = SHR(AllOnes, skew); } } } notSkewMask = ~skewMask; if (noHalftone) { halftoneWord = AllOnes; halftoneHeight = 0; } else { halftoneWord = halftoneAt(0); } y = dy; for (i = 1; i <= bbH; i++) { /* here is the vertical loop */ if (halftoneHeight > 1) { /* Otherwise, its always the same */ halftoneWord = halftoneAt(y); y += vDir; } if (preload) { /* load the 64-bit shifter */ prevWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; } else { prevWord = 0; } destMask = mask1; /* pick up next word */ thisWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; /* 32-bit rotate */ skewWord = (SHIFT((prevWord & notSkewMask), unskew)) | (SHIFT((thisWord & skewMask), skew)); prevWord = thisWord; destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(skewWord & halftoneWord, destWord); destWord = (destMask & mergeWord) | (destWord & ~destMask); destBits[destIndex >>> 2] = destWord; /* This central horizontal loop requires no store masking */ destIndex += hInc; destMask = AllOnes; if (combinationRule === 3) { if ((skew === 0) && (halftoneWord === AllOnes)) { /* Very special inner loop for STORE mode with no skew -- just move words */ if (hDir === -1) { /* Woeful patch: revert to older code for hDir = -1 */ for (word = 2; word <= (nWords - 1); word++) { thisWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; destBits[destIndex >>> 2] = thisWord; destIndex += hInc; } } else { for (word = 2; word <= (nWords - 1); word++) { /* Note loop starts with prevWord loaded (due to preload) */ destBits[destIndex >>> 2] = prevWord; destIndex += hInc; prevWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; } } } else { /* Special inner loop for STORE mode -- no need to call merge */ for (word = 2; word <= (nWords - 1); word++) { thisWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; /* 32-bit rotate */ skewWord = (SHIFT((prevWord & notSkewMask), unskew)) | (SHIFT((thisWord & skewMask), skew)); prevWord = thisWord; destBits[destIndex >>> 2] = skewWord & halftoneWord; destIndex += hInc; } } } else { for (word = 2; word <= (nWords - 1); word++) { /* Normal inner loop does merge: */ /* pick up next word */ thisWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; /* 32-bit rotate */ skewWord = (SHIFT((prevWord & notSkewMask), unskew)) | (SHIFT((thisWord & skewMask), skew)); prevWord = thisWord; mergeWord = mergeFnwith(skewWord & halftoneWord, destBits[destIndex >>> 2]); destBits[destIndex >>> 2] = mergeWord; destIndex += hInc; } } if (nWords > 1) { destMask = mask2; /* pick up next word */ thisWord = sourceBits[sourceIndex >>> 2]; sourceIndex += hInc; /* 32-bit rotate */ skewWord = (SHIFT((prevWord & notSkewMask), unskew)) | (SHIFT((thisWord & skewMask), skew)); destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(skewWord & halftoneWord, destWord); destWord = (destMask & mergeWord) | (destWord & ~destMask); destBits[destIndex >>> 2] = destWord; destIndex += hInc; } sourceIndex += sourceDelta; destIndex += destDelta; } } /* Faster copyLoop when source not used. hDir and vDir are both positive, and perload and skew are unused */ function copyLoopNoSource() { var mergeWord; var halftoneWord; var mergeFnwith; var destWord; var word; var i; mergeFnwith = opTable[combinationRule + 1]; for (i = 1; i <= bbH; i++) { /* here is the vertical loop */ if (noHalftone) { halftoneWord = AllOnes; } else { halftoneWord = halftoneAt((dy + i) - 1); } destMask = mask1; destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(halftoneWord, destWord); destWord = (destMask & mergeWord) | (destWord & ~destMask); destBits[destIndex >>> 2] = destWord; /* This central horizontal loop requires no store masking */ destIndex += 4; destMask = AllOnes; if (combinationRule === 3) { /* Special inner loop for STORE */ destWord = halftoneWord; for (word = 2; word <= (nWords - 1); word++) { destBits[destIndex >>> 2] = destWord; destIndex += 4; } } else { /* Normal inner loop does merge */ for (word = 2; word <= (nWords - 1); word++) { /* Normal inner loop does merge */ destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(halftoneWord, destWord); destBits[destIndex >>> 2] = mergeWord; destIndex += 4; } } if (nWords > 1) { destMask = mask2; destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(halftoneWord, destWord); destWord = (destMask & mergeWord) | (destWord & ~destMask); destBits[destIndex >>> 2] = destWord; destIndex += 4; } destIndex += destDelta; } } /* This version of the inner loop maps source pixels to a destination form with different depth. Because it is already unweildy, the loop is not unrolled as in the other versions. Preload, skew and skewMask are all overlooked, since pickSourcePixels delivers its destination word already properly aligned. Note that pickSourcePixels could be copied in-line at the top of the horizontal loop, and some of its inits moved out of the loop. */ /* ar 12/7/1999: The loop has been rewritten to use only one pickSourcePixels call. The idea is that the call itself could be inlined. If we decide not to inline pickSourcePixels we could optimize the loop instead. */ function copyLoopPixMap() { var mapperFlags; var srcShiftInc; var dstShiftLeft; var sourcePixMask; var nSourceIncs; var skewWord; var words; var destWord; var startBits; var mergeFnwith; var dstShift; var i; var halftoneWord; var mergeWord; var destPixMask; var dstShiftInc; var srcShift; var endBits; var nPix; var scrStartBits; mergeFnwith = opTable[combinationRule + 1]; sourcePPW = DIV(32, sourceDepth); sourcePixMask = maskTable[sourceDepth]; destPixMask = maskTable[destDepth]; mapperFlags = cmFlags & -9; sourceIndex = ((sy * sourcePitch)) + ((DIV(sx, sourcePPW)) * 4); scrStartBits = sourcePPW - (sx & (sourcePPW - 1)); if (bbW < scrStartBits) { nSourceIncs = 0; } else { nSourceIncs = (DIV((bbW - scrStartBits), sourcePPW)) + 1; } /* Note following two items were already calculated in destmask setup! */ sourceDelta = sourcePitch - (nSourceIncs * 4); startBits = destPPW - (dx & (destPPW - 1)); endBits = (((dx + bbW) - 1) & (destPPW - 1)) + 1; if (bbW < startBits) { startBits = bbW; } srcShift = (sx & (sourcePPW - 1)) * sourceDepth; dstShift = (dx & (destPPW - 1)) * destDepth; srcShiftInc = sourceDepth; dstShiftInc = destDepth; dstShiftLeft = 0; if (sourceMSB) { srcShift = (32 - sourceDepth) - srcShift; srcShiftInc = 0 - srcShiftInc; } if (destMSB) { dstShift = (32 - destDepth) - dstShift; dstShiftInc = 0 - dstShiftInc; dstShiftLeft = 32 - destDepth; } for (i = 1; i <= bbH; i++) { /* here is the vertical loop */ /* *** is it possible at all that noHalftone == false? *** */ if (noHalftone) { halftoneWord = AllOnes; } else { halftoneWord = halftoneAt((dy + i) - 1); } srcBitShift = srcShift; dstBitShift = dstShift; destMask = mask1; /* Here is the horizontal loop... */ nPix = startBits; words = nWords; do { /* pick up the word */ /* align next word to leftmost pixel */ skewWord = pickSourcePixelsflagssrcMaskdestMasksrcShiftIncdstShiftInc(nPix, mapperFlags, sourcePixMask, destPixMask, srcShiftInc, dstShiftInc); dstBitShift = dstShiftLeft; if (destMask === AllOnes) { /* avoid read-modify-write */ mergeWord = mergeFnwith(skewWord & halftoneWord, destBits[destIndex >>> 2]); destBits[destIndex >>> 2] = destMask & mergeWord; } else { /* General version using dest masking */ destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(skewWord & halftoneWord, destWord & destMask); destWord = (destMask & mergeWord) | (destWord & ~destMask); destBits[destIndex >>> 2] = destWord; } destIndex += 4; if (words === 2) { /* e.g., is the next word the last word? */ /* set mask for last word in this row */ destMask = mask2; nPix = endBits; } else { /* use fullword mask for inner loop */ destMask = AllOnes; nPix = destPPW; } } while(!(((--words)) === 0)); sourceIndex += sourceDelta; destIndex += destDelta; } } /* Return the default translation table from 1..8 bit indexed colors to 32bit */ /* The table has been generated by the following statements */ /* | pvs hex | String streamContents:[:s| s nextPutAll:'static unsigned int theTable[256] = { '. pvs := (Color colorMapIfNeededFrom: 8 to: 32) asArray. 1 to: pvs size do:[:i| i > 1 ifTrue:[s nextPutAll:', ']. (i-1 \\ 8) = 0 ifTrue:[s cr]. s nextPutAll:'0x'. hex := (pvs at: i) printStringBase: 16. s nextPutAll: (hex copyFrom: 4 to: hex size). ]. s nextPutAll:'};'. ]. */ function default8To32Table() { var theTable = [ 0x0, 0xFF000001, 0xFFFFFFFF, 0xFF808080, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF00FFFF, 0xFFFFFF00, 0xFFFF00FF, 0xFF202020, 0xFF404040, 0xFF606060, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFDFDFDF, 0xFF080808, 0xFF101010, 0xFF181818, 0xFF282828, 0xFF303030, 0xFF383838, 0xFF484848, 0xFF505050, 0xFF585858, 0xFF686868, 0xFF707070, 0xFF787878, 0xFF878787, 0xFF8F8F8F, 0xFF979797, 0xFFA7A7A7, 0xFFAFAFAF, 0xFFB7B7B7, 0xFFC7C7C7, 0xFFCFCFCF, 0xFFD7D7D7, 0xFFE7E7E7, 0xFFEFEFEF, 0xFFF7F7F7, 0xFF000001, 0xFF003300, 0xFF006600, 0xFF009900, 0xFF00CC00, 0xFF00FF00, 0xFF000033, 0xFF003333, 0xFF006633, 0xFF009933, 0xFF00CC33, 0xFF00FF33, 0xFF000066, 0xFF003366, 0xFF006666, 0xFF009966, 0xFF00CC66, 0xFF00FF66, 0xFF000099, 0xFF003399, 0xFF006699, 0xFF009999, 0xFF00CC99, 0xFF00FF99, 0xFF0000CC, 0xFF0033CC, 0xFF0066CC, 0xFF0099CC, 0xFF00CCCC, 0xFF00FFCC, 0xFF0000FF, 0xFF0033FF, 0xFF0066FF, 0xFF0099FF, 0xFF00CCFF, 0xFF00FFFF, 0xFF330000, 0xFF333300, 0xFF336600, 0xFF339900, 0xFF33CC00, 0xFF33FF00, 0xFF330033, 0xFF333333, 0xFF336633, 0xFF339933, 0xFF33CC33, 0xFF33FF33, 0xFF330066, 0xFF333366, 0xFF336666, 0xFF339966, 0xFF33CC66, 0xFF33FF66, 0xFF330099, 0xFF333399, 0xFF336699, 0xFF339999, 0xFF33CC99, 0xFF33FF99, 0xFF3300CC, 0xFF3333CC, 0xFF3366CC, 0xFF3399CC, 0xFF33CCCC, 0xFF33FFCC, 0xFF3300FF, 0xFF3333FF, 0xFF3366FF, 0xFF3399FF, 0xFF33CCFF, 0xFF33FFFF, 0xFF660000, 0xFF663300, 0xFF666600, 0xFF669900, 0xFF66CC00, 0xFF66FF00, 0xFF660033, 0xFF663333, 0xFF666633, 0xFF669933, 0xFF66CC33, 0xFF66FF33, 0xFF660066, 0xFF663366, 0xFF666666, 0xFF669966, 0xFF66CC66, 0xFF66FF66, 0xFF660099, 0xFF663399, 0xFF666699, 0xFF669999, 0xFF66CC99, 0xFF66FF99, 0xFF6600CC, 0xFF6633CC, 0xFF6666CC, 0xFF6699CC, 0xFF66CCCC, 0xFF66FFCC, 0xFF6600FF, 0xFF6633FF, 0xFF6666FF, 0xFF6699FF, 0xFF66CCFF, 0xFF66FFFF, 0xFF990000, 0xFF993300, 0xFF996600, 0xFF999900, 0xFF99CC00, 0xFF99FF00, 0xFF990033, 0xFF993333, 0xFF996633, 0xFF999933, 0xFF99CC33, 0xFF99FF33, 0xFF990066, 0xFF993366, 0xFF996666, 0xFF999966, 0xFF99CC66, 0xFF99FF66, 0xFF990099, 0xFF993399, 0xFF996699, 0xFF999999, 0xFF99CC99, 0xFF99FF99, 0xFF9900CC, 0xFF9933CC, 0xFF9966CC, 0xFF9999CC, 0xFF99CCCC, 0xFF99FFCC, 0xFF9900FF, 0xFF9933FF, 0xFF9966FF, 0xFF9999FF, 0xFF99CCFF, 0xFF99FFFF, 0xFFCC0000, 0xFFCC3300, 0xFFCC6600, 0xFFCC9900, 0xFFCCCC00, 0xFFCCFF00, 0xFFCC0033, 0xFFCC3333, 0xFFCC6633, 0xFFCC9933, 0xFFCCCC33, 0xFFCCFF33, 0xFFCC0066, 0xFFCC3366, 0xFFCC6666, 0xFFCC9966, 0xFFCCCC66, 0xFFCCFF66, 0xFFCC0099, 0xFFCC3399, 0xFFCC6699, 0xFFCC9999, 0xFFCCCC99, 0xFFCCFF99, 0xFFCC00CC, 0xFFCC33CC, 0xFFCC66CC, 0xFFCC99CC, 0xFFCCCCCC, 0xFFCCFFCC, 0xFFCC00FF, 0xFFCC33FF, 0xFFCC66FF, 0xFFCC99FF, 0xFFCCCCFF, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF3300, 0xFFFF6600, 0xFFFF9900, 0xFFFFCC00, 0xFFFFFF00, 0xFFFF0033, 0xFFFF3333, 0xFFFF6633, 0xFFFF9933, 0xFFFFCC33, 0xFFFFFF33, 0xFFFF0066, 0xFFFF3366, 0xFFFF6666, 0xFFFF9966, 0xFFFFCC66, 0xFFFFFF66, 0xFFFF0099, 0xFFFF3399, 0xFFFF6699, 0xFFFF9999, 0xFFFFCC99, 0xFFFFFF99, 0xFFFF00CC, 0xFFFF33CC, 0xFFFF66CC, 0xFFFF99CC, 0xFFFFCCCC, 0xFFFFFFCC, 0xFFFF00FF, 0xFFFF33FF, 0xFFFF66FF, 0xFFFF99FF, 0xFFFFCCFF, 0xFFFFFFFF]; return theTable; } /* Utility routine for computing Warp increments. */ function deltaFromtonSteps(x1, x2, n) { if (x2 > x1) { return (DIV(((x2 - x1) + FixedPt1), (n + 1))) + 1; } else { if (x2 === x1) { return 0; } return 0 - ((DIV(((x1 - x2) + FixedPt1), (n + 1))) + 1); } } /* Compute masks for left and right destination words */ function destMaskAndPointerInit() { var endBits; var startBits; var pixPerM1; /* A mask, assuming power of two */ /* how many pixels in first word */ pixPerM1 = destPPW - 1; startBits = destPPW - (dx & pixPerM1); if (destMSB) { mask1 = SHR(AllOnes, (32 - (startBits * destDepth))); } else { mask1 = SHL(AllOnes, (32 - (startBits * destDepth))); } endBits = (((dx + bbW) - 1) & pixPerM1) + 1; if (destMSB) { mask2 = SHL(AllOnes, (32 - (endBits * destDepth))); } else { mask2 = SHR(AllOnes, (32 - (endBits * destDepth))); } if (bbW < startBits) { mask1 = mask1 & mask2; mask2 = 0; nWords = 1; } else { nWords = (DIV(((bbW - startBits) + pixPerM1), destPPW)) + 1; } /* defaults for no overlap with source */ /* calculate byte addr and delta, based on first word of data */ /* Note pitch is bytes and nWords is longs, not bytes */ hDir = (vDir = 1); destIndex = ((dy * destPitch)) + ((DIV(dx, destPPW)) * 4); destDelta = (destPitch * vDir) - (4 * (nWords * hDir)); } function destinationWordwith(sourceWord, destinationWord) { return destinationWord; } /* Dither the given 32bit word to 16 bit. Ignore alpha. */ function dither32To16threshold(srcWord, ditherValue) { var addThreshold; /* You bet */ addThreshold = ditherValue << 8; return ((dither8Lookup[addThreshold + ((srcWord >>> 16) & 255)] << 10) + (dither8Lookup[addThreshold + ((srcWord >>> 8) & 255)] << 5)) + dither8Lookup[addThreshold + (srcWord & 255)]; } /* This is the primitive implementation of the line-drawing loop. See the comments in BitBlt>>drawLoopX:Y: */ function drawLoopXY(xDelta, yDelta) { var P; var affT; var dx1; var px; var affR; var affL; var py; var i; var affB; var dy1; if (xDelta > 0) { dx1 = 1; } else { if (xDelta === 0) { dx1 = 0; } else { dx1 = -1; } } if (yDelta > 0) { dy1 = 1; } else { if (yDelta === 0) { dy1 = 0; } else { dy1 = -1; } } px = Math.abs(yDelta); py = Math.abs(xDelta); /* init null rectangle */ affL = (affT = 9999); affR = (affB = -9999); if (py > px) { /* more horizontal */ P = py >> 1; for (i = 1; i <= py; i++) { destX += dx1; if (((P -= px)) < 0) { destY += dy1; P += py; } if (i < py) { copyBits(); if (interpreterProxy.failed()) { return null; } if ((affectedL < affectedR) && (affectedT < affectedB)) { /* Affected rectangle grows along the line */ affL = Math.min(affL, affectedL); affR = Math.max(affR, affectedR); affT = Math.min(affT, affectedT); affB = Math.max(affB, affectedB); if (((affR - affL) * (affB - affT)) > 4000) { /* If affected rectangle gets large, update it in chunks */ affectedL = affL; affectedR = affR; affectedT = affT; affectedB = affB; showDisplayBits(); /* init null rectangle */ affL = (affT = 9999); affR = (affB = -9999); } } } } } else { /* more vertical */ P = px >> 1; for (i = 1; i <= px; i++) { destY += dy1; if (((P -= py)) < 0) { destX += dx1; P += px; } if (i < px) { copyBits(); if (interpreterProxy.failed()) { return null; } if ((affectedL < affectedR) && (affectedT < affectedB)) { /* Affected rectangle grows along the line */ affL = Math.min(affL, affectedL); affR = Math.max(affR, affectedR); affT = Math.min(affT, affectedT); affB = Math.max(affB, affectedB); if (((affR - affL) * (affB - affT)) > 4000) { /* If affected rectangle gets large, update it in chunks */ affectedL = affL; affectedR = affR; affectedT = affT; affectedB = affB; showDisplayBits(); /* init null rectangle */ affL = (affT = 9999); affR = (affB = -9999); } } } } } affectedL = affL; affectedR = affR; affectedT = affT; /* store destX, Y back */ affectedB = affB; interpreterProxy.storeIntegerofObjectwithValue(BBDestXIndex, bitBltOop, destX); interpreterProxy.storeIntegerofObjectwithValue(BBDestYIndex, bitBltOop, destY); } /* Store the given value back into destination form, using dstMask to mask out the bits to be modified. This is an essiantial read-modify-write operation on the destination form. */ function dstLongAtputmask(idx, srcValue, dstMask) { var dstValue; dstValue = destBits[idx >>> 2]; dstValue = dstValue & dstMask; dstValue = dstValue | srcValue; destBits[idx >>> 2] = dstValue; } /* Dither the given 32bit word to 16 bit. Ignore alpha. */ function expensiveDither32To16threshold(srcWord, ditherValue) { var pv; var threshold; var value; var out; /* You bet */ pv = srcWord & 255; threshold = ditherThresholds16[pv & 7]; value = ditherValues16[pv >>> 3]; if (ditherValue < threshold) { out = value + 1; } else { out = value; } pv = (srcWord >>> 8) & 255; threshold = ditherThresholds16[pv & 7]; value = ditherValues16[pv >>> 3]; if (ditherValue < threshold) { out = out | ((value + 1) << 5); } else { out = out | (value << 5); } pv = (srcWord >>> 16) & 255; threshold = ditherThresholds16[pv & 7]; value = ditherValues16[pv >>> 3]; if (ditherValue < threshold) { out = out | ((value + 1) << 10); } else { out = out | (value << 10); } return out; } /* Return the integer value of the given field of the given object. If the field contains a Float, truncate it and return its integral part. Fail if the given field does not contain a small integer or Float, or if the truncated Float is out of the range of small integers. */ function fetchIntOrFloatofObject(fieldIndex, objectPointer) { var floatValue; var fieldOop; fieldOop = interpreterProxy.fetchPointerofObject(fieldIndex, objectPointer); if (typeof fieldOop === "number") { return fieldOop; } floatValue = interpreterProxy.floatValueOf(fieldOop); if (!((-2147483648 <= floatValue) && (floatValue <= 2.147483647e9))) { interpreterProxy.primitiveFail(); return 0; } return (floatValue|0); } /* Return the integer value of the given field of the given object. If the field contains a Float, truncate it and return its integral part. Fail if the given field does not contain a small integer or Float, or if the truncated Float is out of the range of small integers. */ function fetchIntOrFloatofObjectifNil(fieldIndex, objectPointer, defaultValue) { var floatValue; var fieldOop; fieldOop = interpreterProxy.fetchPointerofObject(fieldIndex, objectPointer); if (typeof fieldOop === "number") { return fieldOop; } if (fieldOop.isNil) { return defaultValue; } floatValue = interpreterProxy.floatValueOf(fieldOop); if (!((-2147483648 <= floatValue) && (floatValue <= 2.147483647e9))) { interpreterProxy.primitiveFail(); return 0; } return (floatValue|0); } /* For any non-zero pixel value in destinationWord with zero alpha channel take the alpha from sourceWord and fill it in. Intended for fixing alpha channels left at zero during 16->32 bpp conversions. */ function fixAlphawith(sourceWord, destinationWord) { if (destDepth !== 32) { return destinationWord; } if (destinationWord === 0) { return 0; } if ((destinationWord & 4278190080) !== 0) { return destinationWord; } return destinationWord | (sourceWord & 4278190080); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Return a value from the halftone pattern. */ function halftoneAt(idx) { return halftoneBase[MOD(idx, halftoneHeight)]; } function ignoreSourceOrHalftone(formPointer) { if (formPointer.isNil) { return true; } if (combinationRule === 0) { return true; } if (combinationRule === 5) { return true; } if (combinationRule === 10) { return true; } if (combinationRule === 15) { return true; } return false; } function initBBOpTable() { opTable[0+1] = clearWordwith; opTable[1+1] = bitAndwith; opTable[2+1] = bitAndInvertwith; opTable[3+1] = sourceWordwith; opTable[4+1] = bitInvertAndwith; opTable[5+1] = destinationWordwith; opTable[6+1] = bitXorwith; opTable[7+1] = bitOrwith; opTable[8+1] = bitInvertAndInvertwith; opTable[9+1] = bitInvertXorwith; opTable[10+1] = bitInvertDestinationwith; opTable[11+1] = bitOrInvertwith; opTable[12+1] = bitInvertSourcewith; opTable[13+1] = bitInvertOrwith; opTable[14+1] = bitInvertOrInvertwith; opTable[15+1] = destinationWordwith; opTable[16+1] = destinationWordwith; opTable[17+1] = destinationWordwith; opTable[18+1] = addWordwith; opTable[19+1] = subWordwith; opTable[20+1] = rgbAddwith; opTable[21+1] = rgbSubwith; opTable[22+1] = OLDrgbDiffwith; opTable[23+1] = OLDtallyIntoMapwith; opTable[24+1] = alphaBlendwith; opTable[25+1] = pixPaintwith; opTable[26+1] = pixMaskwith; opTable[27+1] = rgbMaxwith; opTable[28+1] = rgbMinwith; opTable[29+1] = rgbMinInvertwith; opTable[30+1] = alphaBlendConstwith; opTable[31+1] = alphaPaintConstwith; opTable[32+1] = rgbDiffwith; opTable[33+1] = tallyIntoMapwith; opTable[34+1] = alphaBlendScaledwith; opTable[35+1] = alphaBlendScaledwith; opTable[36+1] = alphaBlendScaledwith; opTable[37+1] = rgbMulwith; opTable[38+1] = pixSwapwith; opTable[39+1] = pixClearwith; opTable[40+1] = fixAlphawith; opTable[41+1] = rgbComponentAlphawith; } function initDither8Lookup() { var t; var b; var value; for (b = 0; b <= 255; b++) { for (t = 0; t <= 15; t++) { value = expensiveDither32To16threshold(b, t); dither8Lookup[(t << 8) + b] = value; } } } function initialiseModule() { initBBOpTable(); initDither8Lookup(); // skipping ifdef ENABLE_FAST_BLT return true; } /* Return true if shiftTable/maskTable define an identity mapping. */ function isIdentityMapwith(shifts, masks) { if ((!shifts) || (!masks)) { return true; } if ((shifts[RedIndex] === 0) && ((shifts[GreenIndex] === 0) && ((shifts[BlueIndex] === 0) && ((shifts[AlphaIndex] === 0) && ((masks[RedIndex] === 16711680) && ((masks[GreenIndex] === 65280) && ((masks[BlueIndex] === 255) && (masks[AlphaIndex] === 4278190080)))))))) { return true; } return false; } /* Load the dest form for BitBlt. Return false if anything is wrong, true otherwise. */ function loadBitBltDestForm() { var destBitsSize; destBits = interpreterProxy.fetchPointerofObject(FormBitsIndex, destForm); destWidth = interpreterProxy.fetchIntegerofObject(FormWidthIndex, destForm); destHeight = interpreterProxy.fetchIntegerofObject(FormHeightIndex, destForm); if (!((destWidth >= 0) && (destHeight >= 0))) { return false; } destDepth = interpreterProxy.fetchIntegerofObject(FormDepthIndex, destForm); destMSB = destDepth > 0; if (destDepth < 0) { destDepth = 0 - destDepth; } if (typeof destBits === "number") { /* Query for actual surface dimensions */ if (!queryDestSurface(destBits)) { return false; } destPPW = DIV(32, destDepth); destBits = (destPitch = 0); } else { destPPW = DIV(32, destDepth); destPitch = (DIV((destWidth + (destPPW - 1)), destPPW)) * 4; destBitsSize = BYTESIZEOF(destBits); if (!(interpreterProxy.isWordsOrBytes(destBits) && (destBitsSize === (destPitch * destHeight)))) { if (interpreterProxy.isWordsOrBytes(destBits) && (destBitsSize > (destPitch * destHeight))) { interpreterProxy.vm.warnOnce("BitBlt>>loadBitBltDestForm: destBitsSize != destPitch * destHeight, expected " + destPitch + "*" + destHeight + "=" + (destPitch * destHeight) + ", got " + destBitsSize); } else { return false; } } destBits = destBits.wordsOrBytes(); } return true; } /* Load BitBlt from the oop. This function is exported for the Balloon engine. */ function loadBitBltFrom(bbObj) { return loadBitBltFromwarping(bbObj, false); } /* Load context from BitBlt instance. Return false if anything is amiss */ /* NOTE this should all be changed to minX/maxX coordinates for simpler clipping -- once it works! */ function loadBitBltFromwarping(bbObj, aBool) { var ok; bitBltOop = bbObj; isWarping = aBool; combinationRule = interpreterProxy.fetchIntegerofObject(BBRuleIndex, bitBltOop); if (interpreterProxy.failed() || ((combinationRule < 0) || (combinationRule > (OpTableSize - 2)))) { return false; } if ((combinationRule >= 16) && (combinationRule <= 17)) { return false; } sourceForm = interpreterProxy.fetchPointerofObject(BBSourceFormIndex, bitBltOop); noSource = ignoreSourceOrHalftone(sourceForm); halftoneForm = interpreterProxy.fetchPointerofObject(BBHalftoneFormIndex, bitBltOop); noHalftone = ignoreSourceOrHalftone(halftoneForm); destForm = interpreterProxy.fetchPointerofObject(BBDestFormIndex, bbObj); if (!(interpreterProxy.isPointers(destForm) && (SIZEOF(destForm) >= 4))) { return false; } ok = loadBitBltDestForm(); if (!ok) { return false; } destX = fetchIntOrFloatofObjectifNil(BBDestXIndex, bitBltOop, 0); destY = fetchIntOrFloatofObjectifNil(BBDestYIndex, bitBltOop, 0); width = fetchIntOrFloatofObjectifNil(BBWidthIndex, bitBltOop, destWidth); height = fetchIntOrFloatofObjectifNil(BBHeightIndex, bitBltOop, destHeight); if (interpreterProxy.failed()) { return false; } if (noSource) { sourceX = (sourceY = 0); } else { if (!(interpreterProxy.isPointers(sourceForm) && (SIZEOF(sourceForm) >= 4))) { return false; } ok = loadBitBltSourceForm(); if (!ok) { return false; } ok = loadColorMap(); if (!ok) { return false; } if ((cmFlags & ColorMapNewStyle) === 0) { setupColorMasks(); } sourceX = fetchIntOrFloatofObjectifNil(BBSourceXIndex, bitBltOop, 0); sourceY = fetchIntOrFloatofObjectifNil(BBSourceYIndex, bitBltOop, 0); } ok = loadHalftoneForm(); if (!ok) { return false; } clipX = fetchIntOrFloatofObjectifNil(BBClipXIndex, bitBltOop, 0); clipY = fetchIntOrFloatofObjectifNil(BBClipYIndex, bitBltOop, 0); clipWidth = fetchIntOrFloatofObjectifNil(BBClipWidthIndex, bitBltOop, destWidth); clipHeight = fetchIntOrFloatofObjectifNil(BBClipHeightIndex, bitBltOop, destHeight); if (interpreterProxy.failed()) { return false; } if (clipX < 0) { clipWidth += clipX; clipX = 0; } if (clipY < 0) { clipHeight += clipY; clipY = 0; } if ((clipX + clipWidth) > destWidth) { clipWidth = destWidth - clipX; } if ((clipY + clipHeight) > destHeight) { clipHeight = destHeight - clipY; } return true; } /* Load the source form for BitBlt. Return false if anything is wrong, true otherwise. */ function loadBitBltSourceForm() { var sourceBitsSize; sourceBits = interpreterProxy.fetchPointerofObject(FormBitsIndex, sourceForm); sourceWidth = fetchIntOrFloatofObject(FormWidthIndex, sourceForm); sourceHeight = fetchIntOrFloatofObject(FormHeightIndex, sourceForm); if (!((sourceWidth >= 0) && (sourceHeight >= 0))) { return false; } sourceDepth = interpreterProxy.fetchIntegerofObject(FormDepthIndex, sourceForm); sourceMSB = sourceDepth > 0; if (sourceDepth < 0) { sourceDepth = 0 - sourceDepth; } if (typeof sourceBits === "number") { /* Query for actual surface dimensions */ if (!querySourceSurface(sourceBits)) { return false; } sourcePPW = DIV(32, sourceDepth); sourceBits = (sourcePitch = 0); } else { sourcePPW = DIV(32, sourceDepth); sourcePitch = (DIV((sourceWidth + (sourcePPW - 1)), sourcePPW)) * 4; sourceBitsSize = BYTESIZEOF(sourceBits); if (!(interpreterProxy.isWordsOrBytes(sourceBits) && (sourceBitsSize === (sourcePitch * sourceHeight)))) { if (interpreterProxy.isWordsOrBytes(sourceBits) && (sourceBitsSize > (sourcePitch * sourceHeight))) { interpreterProxy.vm.warnOnce("BitBlt>>loadBitBltSourceForm: sourceBitsSize != sourcePitch * sourceHeight, expected " + sourcePitch + "*" + sourceHeight + "=" + (sourcePitch * sourceHeight) + ", got " + sourceBitsSize); } else { return false; } } sourceBits = sourceBits.wordsOrBytes(); } return true; } /* ColorMap, if not nil, must be longWords, and 2^N long, where N = sourceDepth for 1, 2, 4, 8 bits, or N = 9, 12, or 15 (3, 4, 5 bits per color) for 16 or 32 bits. */ function loadColorMap() { var oop; var cmOop; var cmSize; var oldStyle; cmFlags = (cmMask = (cmBitsPerColor = 0)); cmShiftTable = null; cmMaskTable = null; cmLookupTable = null; cmOop = interpreterProxy.fetchPointerofObject(BBColorMapIndex, bitBltOop); if (cmOop.isNil) { return true; } /* even if identity or somesuch - may be cleared later */ cmFlags = ColorMapPresent; oldStyle = false; if (interpreterProxy.isWords(cmOop)) { /* This is an old-style color map (indexed only, with implicit RGBA conversion) */ cmSize = SIZEOF(cmOop); cmLookupTable = cmOop.words; oldStyle = true; } else { /* A new-style color map (fully qualified) */ if (!(interpreterProxy.isPointers(cmOop) && (SIZEOF(cmOop) >= 3))) { return false; } cmShiftTable = loadColorMapShiftOrMaskFrom(interpreterProxy.fetchPointerofObject(0, cmOop)); cmMaskTable = loadColorMapShiftOrMaskFrom(interpreterProxy.fetchPointerofObject(1, cmOop)); oop = interpreterProxy.fetchPointerofObject(2, cmOop); if (oop.isNil) { cmSize = 0; } else { if (!interpreterProxy.isWords(oop)) { return false; } cmSize = SIZEOF(oop); cmLookupTable = oop.words; } cmFlags = cmFlags | ColorMapNewStyle; } if ((cmSize & (cmSize - 1)) !== 0) { return false; } cmMask = cmSize - 1; cmBitsPerColor = 0; if (cmSize === 512) { cmBitsPerColor = 3; } if (cmSize === 4096) { cmBitsPerColor = 4; } if (cmSize === 32768) { cmBitsPerColor = 5; } if (cmSize === 0) { cmLookupTable = null; cmMask = 0; } else { cmFlags = cmFlags | ColorMapIndexedPart; } if (oldStyle) { /* needs implicit conversion */ setupColorMasks(); } if (isIdentityMapwith(cmShiftTable, cmMaskTable)) { cmMaskTable = null; cmShiftTable = null; } else { cmFlags = cmFlags | ColorMapFixedPart; } return true; } function loadColorMapShiftOrMaskFrom(mapOop) { if (mapOop.isNil) { return null; } if (typeof mapOop === "number") { interpreterProxy.primitiveFail(); return null; } if (!(interpreterProxy.isWords(mapOop) && (SIZEOF(mapOop) === 4))) { interpreterProxy.primitiveFail(); return null; } // hand-edited generated code: shifts needs to be signed! return mapOop.wordsAsInt32Array(); } /* Load the halftone form */ function loadHalftoneForm() { var halftoneBits; if (noHalftone) { halftoneBase = null; return true; } if (interpreterProxy.isPointers(halftoneForm) && (SIZEOF(halftoneForm) >= 4)) { /* Old-style 32xN monochrome halftone Forms */ halftoneBits = interpreterProxy.fetchPointerofObject(FormBitsIndex, halftoneForm); halftoneHeight = interpreterProxy.fetchIntegerofObject(FormHeightIndex, halftoneForm); if (!interpreterProxy.isWords(halftoneBits)) { noHalftone = true; } } else { /* New spec accepts, basically, a word array */ if (!(!interpreterProxy.isPointers(halftoneForm) && (interpreterProxy.isWords(halftoneForm)))) { return false; } halftoneBits = halftoneForm; halftoneHeight = SIZEOF(halftoneBits); } halftoneBase = halftoneBits.wordsOrBytes(); return true; } /* Load the surface support plugin */ function loadSurfacePlugin() { querySurfaceFn = interpreterProxy.ioLoadFunctionFrom("ioGetSurfaceFormat", "SurfacePlugin"); lockSurfaceFn = interpreterProxy.ioLoadFunctionFrom("ioLockSurface", "SurfacePlugin"); unlockSurfaceFn = interpreterProxy.ioLoadFunctionFrom("ioUnlockSurface", "SurfacePlugin"); return (!!querySurfaceFn) && ((!!lockSurfaceFn) && (!!unlockSurfaceFn)); } function loadWarpBltFrom(bbObj) { return loadBitBltFromwarping(bbObj, true); } /* Get a pointer to the bits of any OS surfaces. */ /* Notes: * For equal source/dest handles only one locking operation is performed. This is to prevent locking of overlapping areas which does not work with certain APIs (as an example, DirectDraw prevents locking of overlapping areas). A special case for non-overlapping but equal source/dest handle would be possible but we would have to transfer this information over to unlockSurfaces somehow (currently, only one unlock operation is performed for equal source and dest handles). Also, this would require a change in the notion of ioLockSurface() which is right now interpreted as a hint and not as a requirement to lock only the specific portion of the surface. * The arguments in ioLockSurface() provide the implementation with an explicit hint what area is affected. It can be very useful to know the max. affected area beforehand if getting the bits requires expensive copy operations (e.g., like a roundtrip to the X server or a glReadPixel op). However, the returned pointer *MUST* point to the virtual origin of the surface and not to the beginning of the rectangle. The promise made by BitBlt is to never access data outside the given rectangle (aligned to 4byte boundaries!) so it is okay to return a pointer to the virtual origin that is actually outside the valid memory area. * The area provided in ioLockSurface() is already clipped (e.g., it will always be inside the source and dest boundingBox) but it is not aligned to word boundaries yet. It is up to the support code to compute accurate alignment if necessary. * Warping always requires the entire source surface to be locked because there is no beforehand knowledge about what area will actually be traversed. */ function lockSurfaces() { var destHandle; var sourceHandle; var t; var fn; var r; var b; var l; hasSurfaceLock = false; if (destBits === 0) { /* Blitting *to* OS surface */ if (!lockSurfaceFn) { if (!loadSurfacePlugin()) { return null; } } fn = lockSurfaceFn; destHandle = interpreterProxy.fetchIntegerofObject(FormBitsIndex, destForm); if ((sourceBits === 0) && (!noSource)) { /* Handle the special case of equal source and dest handles */ sourceHandle = interpreterProxy.fetchIntegerofObject(FormBitsIndex, sourceForm); if (sourceHandle === destHandle) { /* If we have overlapping source/dest we lock the entire area so that there is only one area transmitted */ if (isWarping) { /* Otherwise use overlapping area */ l = Math.min(sx, dx); r = Math.max(sx, dx) + bbW; t = Math.min(sy, dy); b = Math.max(sy, dy) + bbH; sourceBits = fn(sourceHandle, function(p){sourcePitch = p;}, l, t, r-l, b-t); } else { /* When warping we always need the entire surface for the source */ sourceBits = fn(sourceHandle, function(p){sourcePitch = p;}, 0,0, sourceWidth, sourceHeight); } destBits = sourceBits; destPitch = sourcePitch; hasSurfaceLock = true; return destBits !== 0; } } destBits = fn(destHandle, function(p){destPitch = p;}, dx, dy, bbW, bbH); hasSurfaceLock = true; } if ((sourceBits === 0) && (!noSource)) { /* Blitting *from* OS surface */ sourceHandle = interpreterProxy.fetchIntegerofObject(FormBitsIndex, sourceForm); if (!lockSurfaceFn) { if (!loadSurfacePlugin()) { return null; } } /* Warping requiring the entire surface */ fn = lockSurfaceFn; if (isWarping) { sourceBits = fn(sourceHandle, function(p){sourcePitch = p;}, 0, 0, sourceWidth, sourceHeight); } else { sourceBits = fn(sourceHandle, function(p){sourcePitch = p;}, sx, sy, bbW, bbH); } hasSurfaceLock = true; } return (destBits !== 0) && ((sourceBits !== 0) || (noSource)); } /* Color map the given source pixel. */ function mapPixelflags(sourcePixel, mapperFlags) { var pv; pv = sourcePixel; if ((mapperFlags & ColorMapPresent) !== 0) { if ((mapperFlags & ColorMapFixedPart) !== 0) { /* avoid introducing transparency by color reduction */ pv = rgbMapPixelflags(sourcePixel); if ((pv === 0) && (sourcePixel !== 0)) { pv = 1; } } if ((mapperFlags & ColorMapIndexedPart) !== 0) { pv = cmLookupTable[pv & cmMask]; } } return pv; } /* The module with the given name was just unloaded. Make sure we have no dangling references. */ function moduleUnloaded(aModuleName) { if (strcmp(aModuleName, "SurfacePlugin") === 0) { /* The surface plugin just shut down. How nasty. */ querySurfaceFn = (lockSurfaceFn = (unlockSurfaceFn = 0)); } } /* AND word1 to word2 as nParts partitions of nBits each. Any field of word1 not all-ones is treated as all-zeroes. Used for erasing, eg, brush shapes prior to ORing in a color */ function partitionedANDtonBitsnPartitions(word1, word2, nBits, nParts) { var result; var i; var mask; /* partition mask starts at the right */ mask = maskTable[nBits]; result = 0; for (i = 1; i <= nParts; i++) { if ((word1 & mask) === mask) { result = result | (word2 & mask); } /* slide left to next partition */ mask = SHL(mask, nBits); } return result; } /* Add word1 to word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors */ /* Use unsigned int everywhere because it has a well known arithmetic model without undefined behavior w.r.t. overflow and shifts */ function partitionedAddtonBitscomponentMaskcarryOverflowMask(word1, word2, nBits, componentMask, carryOverflowMask) { var w2; var carryOverflow; var sum; var w1; /* mask to remove high bit of each component */ w1 = word1 & carryOverflowMask; w2 = word2 & carryOverflowMask; /* sum without high bit to avoid overflowing over next component */ sum = (word1 ^ w1) + (word2 ^ w2); /* detect overflow condition for saturating */ carryOverflow = (w1 & w2) | ((w1 | w2) & sum); return ((sum ^ w1) ^ w2) | ((SHR(carryOverflow, (nBits - 1))) * componentMask); } /* Max word1 to word2 as nParts partitions of nBits each */ /* In C, most arithmetic operations answer the same bit pattern regardless of the operands being signed or unsigned ints (this is due to the way 2's complement numbers work). However, comparisions might fail. Add the proper declaration of words as unsigned int in those cases where comparisions are done (jmv) */ function partitionedMaxwithnBitsnPartitions(word1, word2, nBits, nParts) { var result; var i; var mask; /* partition mask starts at the right */ mask = maskTable[nBits]; result = 0; for (i = 1; i <= nParts; i++) { result = result | Math.max((word2 & mask), (word1 & mask)); /* slide left to next partition */ mask = SHL(mask, nBits); } return result; } /* Min word1 to word2 as nParts partitions of nBits each */ /* In C, most arithmetic operations answer the same bit pattern regardless of the operands being signed or unsigned ints (this is due to the way 2's complement numbers work). However, comparisions might fail. Add the proper declaration of words as unsigned int in those cases where comparisions are done (jmv) */ function partitionedMinwithnBitsnPartitions(word1, word2, nBits, nParts) { var result; var i; var mask; /* partition mask starts at the right */ mask = maskTable[nBits]; result = 0; for (i = 1; i <= nParts; i++) { result = result | Math.min((word2 & mask), (word1 & mask)); /* slide left to next partition */ mask = SHL(mask, nBits); } return result; } /* Multiply word1 with word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors. Bug in loop version when non-white background */ /* In C, integer multiplication might answer a wrong value if the unsigned values are declared as signed. This problem does not affect this method, because the most significant bit (i.e. the sign bit) will always be zero (jmv) */ function partitionedMulwithnBitsnPartitions(word1, word2, nBits, nParts) { var dMask; var result; var product; var sMask; /* partition mask starts at the right */ sMask = maskTable[nBits]; dMask = SHL(sMask, nBits); /* optimized first step */ result = SHR((((((word1 & sMask) + 1) * ((word2 & sMask) + 1)) - 1) & dMask), nBits); if (nParts === 1) { return result; } product = (((((SHR(word1, nBits)) & sMask) + 1) * (((SHR(word2, nBits)) & sMask) + 1)) - 1) & dMask; result = result | product; if (nParts === 2) { return result; } product = (((((SHR(word1, (2 * nBits))) & sMask) + 1) * (((SHR(word2, (2 * nBits))) & sMask) + 1)) - 1) & dMask; result = result | (SHL(product, nBits)); if (nParts === 3) { return result; } product = (((((SHR(word1, (3 * nBits))) & sMask) + 1) * (((SHR(word2, (3 * nBits))) & sMask) + 1)) - 1) & dMask; result = result | (SHL(product, (2 * nBits))); return result; } function partitionedRgbComponentAlphadestnBitsnPartitions(sourceWord, destWord, nBits, nParts) { var p2; var result; var p1; var i; var v; var mask; /* partition mask starts at the right */ mask = maskTable[nBits]; result = 0; for (i = 1; i <= nParts; i++) { p1 = SHR((sourceWord & mask), ((i - 1) * nBits)); p2 = SHR((destWord & mask), ((i - 1) * nBits)); if (nBits !== 32) { if (nBits === 16) { p1 = rgbMap16To32(p1) | 4278190080; p2 = rgbMap16To32(p2) | 4278190080; } else { p1 = rgbMapfromto(p1, nBits, 32) | 4278190080; p2 = rgbMapfromto(p2, nBits, 32) | 4278190080; } } v = rgbComponentAlpha32with(p1, p2); if (nBits !== 32) { v = rgbMapfromto(v, 32, nBits); } result = result | (SHL(v, ((i - 1) * nBits))); /* slide left to next partition */ mask = SHL(mask, nBits); } return result; } /* Subtract word1 from word2 as nParts partitions of nBits each. This is useful for packed pixels, or packed colors */ /* In C, most arithmetic operations answer the same bit pattern regardless of the operands being signed or unsigned ints (this is due to the way 2's complement numbers work). However, comparisions might fail. Add the proper declaration of words as unsigned int in those cases where comparisions are done (jmv) */ function partitionedSubfromnBitsnPartitions(word1, word2, nBits, nParts) { var p2; var result; var p1; var i; var mask; /* partition mask starts at the right */ mask = maskTable[nBits]; result = 0; for (i = 1; i <= nParts; i++) { p1 = word1 & mask; p2 = word2 & mask; if (p1 < p2) { /* result is really abs value of thedifference */ result = result | (p2 - p1); } else { result = result | (p1 - p2); } /* slide left to next partition */ mask = SHL(mask, nBits); } return result; } /* Based on the values provided during setup choose and perform the appropriate inner loop function. */ /* Should be inlined into caller for speed */ function performCopyLoop() { destMaskAndPointerInit(); if (noSource) { /* Simple fill loop */ copyLoopNoSource(); } else { /* Loop using source and dest */ checkSourceOverlap(); if ((sourceDepth !== destDepth) || ((cmFlags !== 0) || (sourceMSB !== destMSB))) { /* If we must convert between pixel depths or use color lookups or swap pixels use the general version */ copyLoopPixMap(); } else { /* Otherwise we simple copy pixels and can use a faster version */ sourceSkewAndPointerInit(); copyLoop(); } } } /* Pick nPix pixels starting at srcBitIndex from the source, map by the color map, and justify them according to dstBitIndex in the resulting destWord. */ function pickSourcePixelsflagssrcMaskdestMasksrcShiftIncdstShiftInc(nPixels, mapperFlags, srcMask, dstMask, srcShiftInc, dstShiftInc) { var sourcePix; var srcShift; var sourceWord; var dstShift; var destPix; var nPix; var destWord; /* oh please */ sourceWord = sourceBits[sourceIndex >>> 2]; destWord = 0; /* Hint: Keep in register */ srcShift = srcBitShift; /* Hint: Keep in register */ dstShift = dstBitShift; /* always > 0 so we can use do { } while(--nPix); */ nPix = nPixels; if (mapperFlags === (ColorMapPresent | ColorMapIndexedPart)) { /* a little optimization for (pretty crucial) blits using indexed lookups only */ /* grab, colormap and mix in pixel */ do { sourcePix = (SHR(sourceWord, srcShift)) & srcMask; destPix = cmLookupTable[sourcePix & cmMask]; /* adjust dest pix index */ destWord = destWord | (SHL((destPix & dstMask), dstShift)); /* adjust source pix index */ dstShift += dstShiftInc; if ((((srcShift += srcShiftInc)) & 4294967264) !== 0) { if (sourceMSB) { srcShift += 32; } else { srcShift -= 32; } sourceWord = sourceBits[(sourceIndex += 4) >>> 2]; } } while(!(((--nPix)) === 0)); } else { /* grab, colormap and mix in pixel */ do { sourcePix = (SHR(sourceWord, srcShift)) & srcMask; destPix = mapPixelflags(sourcePix, mapperFlags); /* adjust dest pix index */ destWord = destWord | (SHL((destPix & dstMask), dstShift)); /* adjust source pix index */ dstShift += dstShiftInc; if ((((srcShift += srcShiftInc)) & 4294967264) !== 0) { if (sourceMSB) { srcShift += 32; } else { srcShift -= 32; } sourceWord = sourceBits[(sourceIndex += 4) >>> 2]; } } while(!(((--nPix)) === 0)); } /* Store back */ srcBitShift = srcShift; return destWord; } /* Pick a single pixel from the source for WarpBlt. Note: This method is crucial for WarpBlt speed w/o smoothing and still relatively important when smoothing is used. */ function pickWarpPixelAtXy(xx, yy) { var sourcePix; var sourceWord; var srcIndex; var x; var y; /* *please* */ /* note: it would be much faster if we could just avoid these stupid tests for being inside sourceForm. */ if ((xx < 0) || ((yy < 0) || ((((x = xx >>> 14)) >= sourceWidth) || (((y = yy >>> 14)) >= sourceHeight)))) { return 0; } srcIndex = ((y * sourcePitch)) + ((SHR(x, warpAlignShift)) * 4); /* Extract pixel from word */ sourceWord = sourceBits[srcIndex >>> 2]; srcBitShift = warpBitShiftTable[x & warpAlignMask]; sourcePix = (SHR(sourceWord, srcBitShift)) & warpSrcMask; return sourcePix; } /* Clear all pixels in destinationWord for which the pixels of sourceWord have the same values. Used to clear areas of some constant color to zero. */ function pixClearwith(sourceWord, destinationWord) { var pv; var nBits; var result; var i; var mask; if (destDepth === 32) { if (sourceWord === destinationWord) { return 0; } else { return destinationWord; } } nBits = destDepth; /* partition mask starts at the right */ mask = maskTable[nBits]; result = 0; for (i = 1; i <= destPPW; i++) { pv = destinationWord & mask; if ((sourceWord & mask) === pv) { pv = 0; } result = result | pv; /* slide left to next partition */ mask = SHL(mask, nBits); } return result; } function pixMaskwith(sourceWord, destinationWord) { return partitionedANDtonBitsnPartitions(~sourceWord, destinationWord, destDepth, destPPW); } function pixPaintwith(sourceWord, destinationWord) { if (sourceWord === 0) { return destinationWord; } return sourceWord | partitionedANDtonBitsnPartitions(~sourceWord, destinationWord, destDepth, destPPW); } /* Swap the pixels in destWord */ function pixSwapwith(sourceWord, destWord) { var result; var shift; var lowMask; var highMask; var i; if (destPPW === 1) { return destWord; } result = 0; /* mask low pixel */ lowMask = (SHL(1, destDepth)) - 1; /* mask high pixel */ highMask = SHL(lowMask, ((destPPW - 1) * destDepth)); shift = 32 - destDepth; result = result | ((SHL((destWord & lowMask), shift)) | (SHR((destWord & highMask), shift))); if (destPPW <= 2) { return result; } for (i = 2; i <= (destPPW >> 1); i++) { lowMask = SHL(lowMask, destDepth); highMask = SHR(highMask, destDepth); shift -= destDepth * 2; result = result | ((SHL((destWord & lowMask), shift)) | (SHR((destWord & highMask), shift))); } return result; } /* Invoke the copyBits primitive. If the destination is the display, then copy it to the screen. */ function primitiveCopyBits() { var rcvr; rcvr = interpreterProxy.stackValue(interpreterProxy.methodArgumentCount()); if (!loadBitBltFrom(rcvr)) { return interpreterProxy.primitiveFail(); } copyBits(); if (interpreterProxy.failed()) { return null; } showDisplayBits(); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(interpreterProxy.methodArgumentCount()); if ((combinationRule === 22) || (combinationRule === 32)) { interpreterProxy.pop(1); return interpreterProxy.pushInteger(bitCount); } } function primitiveDisplayString() { var charIndex; var sourcePtr; var stopIndex; var bbObj; var xTable; var maxGlyph; var quickBlt; var glyphIndex; var glyphMap; var left; var kernDelta; var startIndex; var ascii; var sourceString; if (interpreterProxy.methodArgumentCount() !== 6) { return interpreterProxy.primitiveFail(); } kernDelta = interpreterProxy.stackIntegerValue(0); xTable = interpreterProxy.stackObjectValue(1); glyphMap = interpreterProxy.stackObjectValue(2); if (!((CLASSOF(xTable) === interpreterProxy.classArray()) && (CLASSOF(glyphMap) === interpreterProxy.classArray()))) { return interpreterProxy.primitiveFail(); } if (SIZEOF(glyphMap) !== 256) { return interpreterProxy.primitiveFail(); } if (interpreterProxy.failed()) { return null; } maxGlyph = SIZEOF(xTable) - 2; stopIndex = interpreterProxy.stackIntegerValue(3); startIndex = interpreterProxy.stackIntegerValue(4); sourceString = interpreterProxy.stackObjectValue(5); if (!interpreterProxy.isBytes(sourceString)) { return interpreterProxy.primitiveFail(); } if (!((startIndex > 0) && ((stopIndex > 0) && (stopIndex <= BYTESIZEOF(sourceString))))) { return interpreterProxy.primitiveFail(); } bbObj = interpreterProxy.stackObjectValue(6); if (!loadBitBltFrom(bbObj)) { return interpreterProxy.primitiveFail(); } if ((combinationRule === 30) || (combinationRule === 31)) { /* needs extra source alpha */ return interpreterProxy.primitiveFail(); } quickBlt = (destBits !== 0) && ((sourceBits !== 0) && ((noSource === false) && ((sourceForm !== destForm) && ((cmFlags !== 0) || ((sourceMSB !== destMSB) || (sourceDepth !== destDepth)))))); left = destX; sourcePtr = sourceString.bytes; for (charIndex = startIndex; charIndex <= stopIndex; charIndex++) { ascii = sourcePtr[charIndex - 1]; glyphIndex = interpreterProxy.fetchIntegerofObject(ascii, glyphMap); if ((glyphIndex < 0) || (glyphIndex > maxGlyph)) { return interpreterProxy.primitiveFail(); } sourceX = interpreterProxy.fetchIntegerofObject(glyphIndex, xTable); width = interpreterProxy.fetchIntegerofObject(glyphIndex + 1, xTable) - sourceX; if (interpreterProxy.failed()) { return null; } clipRange(); if ((bbW > 0) && (bbH > 0)) { if (quickBlt) { destMaskAndPointerInit(); copyLoopPixMap(); affectedL = dx; affectedR = dx + bbW; affectedT = dy; affectedB = dy + bbH; } else { copyBits(); } } if (interpreterProxy.failed()) { return null; } destX = (destX + width) + kernDelta; } affectedL = left; showDisplayBits(); interpreterProxy.storeIntegerofObjectwithValue(BBDestXIndex, bbObj, destX); interpreterProxy.pop(6); } /* Invoke the line drawing primitive. */ function primitiveDrawLoop() { var yDelta; var rcvr; var xDelta; rcvr = interpreterProxy.stackValue(2); xDelta = interpreterProxy.stackIntegerValue(1); yDelta = interpreterProxy.stackIntegerValue(0); if (!loadBitBltFrom(rcvr)) { return interpreterProxy.primitiveFail(); } if (!interpreterProxy.failed()) { drawLoopXY(xDelta, yDelta); showDisplayBits(); } if (!interpreterProxy.failed()) { interpreterProxy.pop(2); } } /* returns the single pixel at x@y. It does not handle LSB bitmaps right now. If x or y are < 0, return 0 to indicate transparent (cf BitBlt>bitPeekerFromForm: usage). Likewise if x>width or y>depth. Fail if the rcvr doesn't seem to be a Form, or x|y seem wrong */ function primitivePixelValueAt() { var pixel; var rcvr; var shift; var depth; var bitmap; var ppW; var word; var stride; var bitsSize; var mask; var xVal; var yVal; var _return_value; xVal = interpreterProxy.stackIntegerValue(1); yVal = interpreterProxy.stackIntegerValue(0); rcvr = interpreterProxy.stackValue(2); if (interpreterProxy.failed()) { return null; } if ((xVal < 0) || (yVal < 0)) { _return_value = 0; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } rcvr = interpreterProxy.stackValue(interpreterProxy.methodArgumentCount()); if (!(interpreterProxy.isPointers(rcvr) && (SIZEOF(rcvr) >= 4))) { interpreterProxy.primitiveFail(); return null; } bitmap = interpreterProxy.fetchPointerofObject(FormBitsIndex, rcvr); if (!interpreterProxy.isWordsOrBytes(bitmap)) { interpreterProxy.primitiveFail(); return null; } width = interpreterProxy.fetchIntegerofObject(FormWidthIndex, rcvr); height = interpreterProxy.fetchIntegerofObject(FormHeightIndex, rcvr); /* if width/height/depth are not integer, fail */ depth = interpreterProxy.fetchIntegerofObject(FormDepthIndex, rcvr); if (interpreterProxy.failed()) { return null; } if ((xVal >= width) || (yVal >= height)) { _return_value = 0; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } if (depth < 0) { interpreterProxy.primitiveFail(); return null; } /* pixels in each word */ ppW = DIV(32, depth); /* how many words per row of pixels */ stride = DIV((width + (ppW - 1)), ppW); bitsSize = BYTESIZEOF(bitmap); if (bitsSize !== ((stride * height) * 4)) { /* bytes per word */ interpreterProxy.primitiveFail(); return null; } /* load the word that contains our target */ word = interpreterProxy.fetchLong32ofObject((yVal * stride) + (DIV(xVal, ppW)), bitmap); /* make a mask to isolate the pixel within that word */ mask = SHR(4294967295, (32 - depth)); /* this is the tricky MSB part - we mask the xVal to find how far into the word we need, then add 1 for the pixel we're looking for, then * depth to get the bit shift */ shift = 32 - (((xVal & (ppW - 1)) + 1) * depth); /* shift, mask and dim the lights */ pixel = (SHR(word, shift)) & mask; _return_value = interpreterProxy.positive32BitIntegerFor(pixel); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } /* Invoke the warpBits primitive. If the destination is the display, then copy it to the screen. */ function primitiveWarpBits() { var rcvr; rcvr = interpreterProxy.stackValue(interpreterProxy.methodArgumentCount()); if (!loadWarpBltFrom(rcvr)) { return interpreterProxy.primitiveFail(); } warpBits(); if (interpreterProxy.failed()) { return null; } showDisplayBits(); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(interpreterProxy.methodArgumentCount()); } /* Query the dimension of an OS surface. This method is provided so that in case the inst vars of the source form are broken, *actual* values of the OS surface can be obtained. This might, for instance, happen if the user resizes the main window. Note: Moved to a separate function for better inlining of the caller. */ function queryDestSurface(handle) { if (!querySurfaceFn) { if (!loadSurfacePlugin()) { return false; } } return querySurfaceFn(handle, function(w, h, d, m){destWidth = w; destHeight = h; destDepth = d; destMSB = m; }); } /* Query the dimension of an OS surface. This method is provided so that in case the inst vars of the source form are broken, *actual* values of the OS surface can be obtained. This might, for instance, happen if the user resizes the main window. Note: Moved to a separate function for better inlining of the caller. */ function querySourceSurface(handle) { if (!querySurfaceFn) { if (!loadSurfacePlugin()) { return false; } } return querySurfaceFn(handle, function(w, h, d, m){sourceWidth = w; sourceHeight = h; sourceDepth = d; sourceMSB = m; }); } function rgbAddwith(sourceWord, destinationWord) { var carryOverflowMask; var componentMask; if (destDepth < 16) { /* Add each pixel separately */ componentMask = (SHL(1, destDepth)) - 1; carryOverflowMask = SHL((DIV(4294967295, componentMask)), (destDepth - 1)); return partitionedAddtonBitscomponentMaskcarryOverflowMask(sourceWord, destinationWord, destDepth, componentMask, carryOverflowMask); } if (destDepth === 16) { /* Add RGB components of each pixel separately */ componentMask = 31; carryOverflowMask = 1108361744; return partitionedAddtonBitscomponentMaskcarryOverflowMask(sourceWord & 2147450879, destinationWord & 2147450879, 5, componentMask, carryOverflowMask); } else { /* Add RGBA components of the pixel separately */ componentMask = 255; carryOverflowMask = 2155905152; return partitionedAddtonBitscomponentMaskcarryOverflowMask(sourceWord, destinationWord, 8, componentMask, carryOverflowMask); } } /* This version assumes combinationRule = 41 sourcePixSize = 32 destPixSize = 16 sourceForm ~= destForm. */ /* This particular method should be optimized in itself */ function rgbComponentAlpha16() { var ditherBase; var ditherThreshold; var srcShift; var sourceWord; var srcIndex; var deltaX; var dstIndex; var srcAlpha; var dstMask; var deltaY; var srcY; var destWord; var dstY; var ditherIndex; /* So we can pre-decrement */ deltaY = bbH + 1; srcY = sy; dstY = dy; srcShift = (dx & 1) * 16; if (destMSB) { srcShift = 16 - srcShift; } /* This is the outer loop */ mask1 = SHL(65535, (16 - srcShift)); while (((--deltaY)) !== 0) { srcIndex = ((srcY * sourcePitch)) + (sx * 4); dstIndex = ((dstY * destPitch)) + ((dx >> 1) * 4); ditherBase = (dstY & 3) * 4; /* For pre-increment */ ditherIndex = (sx & 3) - 1; /* So we can pre-decrement */ deltaX = bbW + 1; dstMask = mask1; if (dstMask === 65535) { srcShift = 16; } else { srcShift = 0; } while (((--deltaX)) !== 0) { ditherThreshold = ditherMatrix4x4[ditherBase + ((ditherIndex = (ditherIndex + 1) & 3))]; sourceWord = sourceBits[srcIndex >>> 2]; srcAlpha = sourceWord & 16777215; if (srcAlpha !== 0) { /* 0 < srcAlpha */ /* If we have to mix colors then just copy a single word */ destWord = destBits[dstIndex >>> 2]; destWord = destWord & ~dstMask; /* Expand from 16 to 32 bit by adding zero bits */ destWord = SHR(destWord, srcShift); /* Mix colors */ destWord = (((destWord & 31744) << 9) | ((destWord & 992) << 6)) | (((destWord & 31) << 3) | 4278190080); /* And dither */ sourceWord = rgbComponentAlpha32with(sourceWord, destWord); sourceWord = dither32To16threshold(sourceWord, ditherThreshold); if (sourceWord === 0) { sourceWord = SHL(1, srcShift); } else { sourceWord = SHL(sourceWord, srcShift); } dstLongAtputmask(dstIndex, sourceWord, dstMask); } srcIndex += 4; if (destMSB) { if (srcShift === 0) { dstIndex += 4; } } else { if (srcShift !== 0) { dstIndex += 4; } } /* Toggle between 0 and 16 */ srcShift = srcShift ^ 16; dstMask = ~dstMask; } ++srcY; ++dstY; } } /* This version assumes combinationRule = 41 sourcePixSize = destPixSize = 32 sourceForm ~= destForm. Note: The inner loop has been optimized for dealing with the special case of aR = aG = aB = 0 */ function rgbComponentAlpha32() { var sourceWord; var srcIndex; var deltaX; var dstIndex; var srcAlpha; var deltaY; var srcY; var destWord; var dstY; /* This particular method should be optimized in itself */ /* Give the compile a couple of hints */ /* The following should be declared as pointers so the compiler will notice that they're used for accessing memory locations (good to know on an Intel architecture) but then the increments would be different between ST code and C code so must hope the compiler notices what happens (MS Visual C does) */ /* So we can pre-decrement */ deltaY = bbH + 1; srcY = sy; /* This is the outer loop */ dstY = dy; while (((--deltaY)) !== 0) { srcIndex = ((srcY * sourcePitch)) + (sx * 4); dstIndex = ((dstY * destPitch)) + (dx * 4); /* So we can pre-decrement */ /* This is the inner loop */ deltaX = bbW + 1; while (((--deltaX)) !== 0) { sourceWord = sourceBits[srcIndex >>> 2]; srcAlpha = sourceWord & 16777215; if (srcAlpha === 0) { srcIndex += 4; /* Now skip as many words as possible, */ dstIndex += 4; while ((((--deltaX)) !== 0) && ((((sourceWord = sourceBits[srcIndex >>> 2])) & 16777215) === 0)) { srcIndex += 4; dstIndex += 4; } ++deltaX; } else { /* 0 < srcAlpha */ /* If we have to mix colors then just copy a single word */ destWord = destBits[dstIndex >>> 2]; destWord = rgbComponentAlpha32with(sourceWord, destWord); destBits[dstIndex >>> 2] = destWord; srcIndex += 4; dstIndex += 4; } } ++srcY; ++dstY; } } /* componentAlphaModeColor is the color, sourceWord contains an alpha value for each component of RGB each of which is encoded as0 meaning 0.0 and 255 meaning 1.0 . the rule is... color = componentAlphaModeColor. colorAlpha = componentAlphaModeAlpha. mask = sourceWord. dst.A = colorAlpha + (1 - colorAlpha) * dst.A dst.R = color.R * mask.R * colorAlpha + (1 - (mask.R * colorAlpha)) * dst.R dst.G = color.G * mask.G * colorAlpha + (1 - (mask.G* colorAlpha)) * dst.G dst.B = color.B * mask.B * colorAlpha + (1 - (mask.B* colorAlpha)) * dst.B */ /* Do NOT inline this into optimized loops */ function rgbComponentAlpha32with(sourceWord, destinationWord) { var g; var srcColor; var aG; var d; var a; var aA; var aR; var dstMask; var srcAlpha; var r; var b; var aB; var alpha; var answer; var s; alpha = sourceWord; if (alpha === 0) { return destinationWord; } srcColor = componentAlphaModeColor; srcAlpha = componentAlphaModeAlpha & 255; aB = alpha & 255; alpha = alpha >>> 8; aG = alpha & 255; alpha = alpha >>> 8; aR = alpha & 255; alpha = alpha >>> 8; aA = alpha & 255; if (srcAlpha !== 255) { aA = (aA * srcAlpha) >>> 8; aR = (aR * srcAlpha) >>> 8; aG = (aG * srcAlpha) >>> 8; aB = (aB * srcAlpha) >>> 8; } dstMask = destinationWord; d = dstMask & 255; s = srcColor & 255; if (!!ungammaLookupTable) { d = ungammaLookupTable[d]; s = ungammaLookupTable[s]; } b = ((d * (255 - aB)) >>> 8) + ((s * aB) >>> 8); if (b > 255) { b = 255; } if (!!gammaLookupTable) { b = gammaLookupTable[b]; } dstMask = dstMask >>> 8; srcColor = srcColor >>> 8; d = dstMask & 255; s = srcColor & 255; if (!!ungammaLookupTable) { d = ungammaLookupTable[d]; s = ungammaLookupTable[s]; } g = ((d * (255 - aG)) >>> 8) + ((s * aG) >>> 8); if (g > 255) { g = 255; } if (!!gammaLookupTable) { g = gammaLookupTable[g]; } dstMask = dstMask >>> 8; srcColor = srcColor >>> 8; d = dstMask & 255; s = srcColor & 255; if (!!ungammaLookupTable) { d = ungammaLookupTable[d]; s = ungammaLookupTable[s]; } r = ((d * (255 - aR)) >>> 8) + ((s * aR) >>> 8); if (r > 255) { r = 255; } if (!!gammaLookupTable) { r = gammaLookupTable[r]; } dstMask = dstMask >>> 8; srcColor = srcColor >>> 8; /* no need to gamma correct alpha value ? */ a = (((dstMask & 255) * (255 - aA)) >>> 8) + aA; if (a > 255) { a = 255; } answer = (((((a << 8) + r) << 8) + g) << 8) + b; return answer; } /* This version assumes combinationRule = 41 sourcePixSize = 32 destPixSize = 8 sourceForm ~= destForm. Note: This is not real blending since we don't have the source colors available. */ function rgbComponentAlpha8() { var srcShift; var sourceWord; var srcIndex; var deltaX; var mappingTable; var dstIndex; var adjust; var mapperFlags; var srcAlpha; var dstMask; var deltaY; var srcY; var destWord; var dstY; /* This particular method should be optimized in itself */ mappingTable = default8To32Table(); mapperFlags = cmFlags & -9; /* So we can pre-decrement */ deltaY = bbH + 1; srcY = sy; dstY = dy; mask1 = (dx & 3) * 8; if (destMSB) { mask1 = 24 - mask1; } mask2 = AllOnes ^ (SHL(255, mask1)); if ((dx & 1) === 0) { adjust = 0; } else { adjust = 522133279; } if ((dy & 1) === 0) { adjust = adjust ^ 522133279; } while (((--deltaY)) !== 0) { adjust = adjust ^ 522133279; srcIndex = ((srcY * sourcePitch)) + (sx * 4); dstIndex = ((dstY * destPitch)) + ((dx >> 2) * 4); /* So we can pre-decrement */ deltaX = bbW + 1; srcShift = mask1; /* This is the inner loop */ dstMask = mask2; while (((--deltaX)) !== 0) { sourceWord = (sourceBits[srcIndex >>> 2] & ~adjust) + adjust; /* set srcAlpha to the average of the 3 separate aR,Ag,AB values */ srcAlpha = sourceWord & 16777215; srcAlpha = DIV((((srcAlpha >>> 16) + ((srcAlpha >>> 8) & 255)) + (srcAlpha & 255)), 3); if (srcAlpha > 31) { /* Everything below 31 is transparent */ if (srcAlpha > 224) { /* treat everything above 224 as opaque */ sourceWord = 4294967295; } destWord = destBits[dstIndex >>> 2]; destWord = destWord & ~dstMask; destWord = SHR(destWord, srcShift); destWord = mappingTable[destWord]; sourceWord = rgbComponentAlpha32with(sourceWord, destWord); sourceWord = mapPixelflags(sourceWord, mapperFlags); /* Store back */ sourceWord = SHL(sourceWord, srcShift); dstLongAtputmask(dstIndex, sourceWord, dstMask); } srcIndex += 4; if (destMSB) { if (srcShift === 0) { dstIndex += 4; srcShift = 24; dstMask = 16777215; } else { srcShift -= 8; dstMask = (dstMask >>> 8) | 4278190080; } } else { if (srcShift === 32) { dstIndex += 4; srcShift = 0; dstMask = 4294967040; } else { srcShift += 8; dstMask = (dstMask << 8) | 255; } } adjust = adjust ^ 522133279; } ++srcY; ++dstY; } } /* componentAlphaModeColor is the color, sourceWord contains an alpha value for each component of RGB each of which is encoded as0 meaning 0.0 and 255 meaning 1.0 . the rule is... color = componentAlphaModeColor. colorAlpha = componentAlphaModeAlpha. mask = sourceWord. dst.A = colorAlpha + (1 - colorAlpha) * dst.A dst.R = color.R * mask.R * colorAlpha + (1 - (mask.R * colorAlpha)) * dst.R dst.G = color.G * mask.G * colorAlpha + (1 - (mask.G* colorAlpha)) * dst.G dst.B = color.B * mask.B * colorAlpha + (1 - (mask.B* colorAlpha)) * dst.B */ /* Do NOT inline this into optimized loops */ function rgbComponentAlphawith(sourceWord, destinationWord) { var alpha; alpha = sourceWord; if (alpha === 0) { return destinationWord; } return partitionedRgbComponentAlphadestnBitsnPartitions(sourceWord, destinationWord, destDepth, destPPW); } /* Subract the pixels in the source and destination, color by color, and return the sum of the absolute value of all the differences. For non-rgb, return the number of differing pixels. */ function rgbDiffwith(sourceWord, destinationWord) { var sourcePixVal; var bitsPerColor; var diff; var sourceShifted; var pixMask; var rgbMask; var destShifted; var i; var maskShifted; var destPixVal; pixMask = maskTable[destDepth]; if (destDepth === 16) { bitsPerColor = 5; rgbMask = 31; } else { bitsPerColor = 8; rgbMask = 255; } maskShifted = destMask; destShifted = destinationWord; sourceShifted = sourceWord; for (i = 1; i <= destPPW; i++) { if ((maskShifted & pixMask) > 0) { /* Only tally pixels within the destination rectangle */ destPixVal = destShifted & pixMask; sourcePixVal = sourceShifted & pixMask; if (destDepth < 16) { if (sourcePixVal === destPixVal) { diff = 0; } else { diff = 1; } } else { diff = partitionedSubfromnBitsnPartitions(sourcePixVal, destPixVal, bitsPerColor, 3); diff = ((diff & rgbMask) + ((SHR(diff, bitsPerColor)) & rgbMask)) + ((SHR((SHR(diff, bitsPerColor)), bitsPerColor)) & rgbMask); } bitCount += diff; } maskShifted = SHR(maskShifted, destDepth); sourceShifted = SHR(sourceShifted, destDepth); destShifted = SHR(destShifted, destDepth); } return destinationWord; } /* Convert the given 16bit pixel value to a 32bit RGBA value. Note: This method is intended to deal with different source formats. */ function rgbMap16To32(sourcePixel) { return (((sourcePixel & 31) << 3) | ((sourcePixel & 992) << 6)) | ((sourcePixel & 31744) << 9); } /* Convert the given 32bit pixel value to a 32bit RGBA value. Note: This method is intended to deal with different source formats. */ function rgbMap32To32(sourcePixel) { return sourcePixel; } /* Convert the given pixel value with nBitsIn bits for each color component to a pixel value with nBitsOut bits for each color component. Typical values for nBitsIn/nBitsOut are 3, 5, or 8. */ function rgbMapfromto(sourcePixel, nBitsIn, nBitsOut) { var d; var destPix; var srcPix; var mask; if (((d = nBitsOut - nBitsIn)) > 0) { /* Expand to more bits by zero-fill */ /* Transfer mask */ mask = (SHL(1, nBitsIn)) - 1; srcPix = SHL(sourcePixel, d); mask = SHL(mask, d); destPix = srcPix & mask; mask = SHL(mask, nBitsOut); srcPix = SHL(srcPix, d); return (destPix + (srcPix & mask)) + ((SHL(srcPix, d)) & (SHL(mask, nBitsOut))); } else { /* Compress to fewer bits by truncation */ if (d === 0) { if (nBitsIn === 5) { /* Sometimes called with 16 bits, though pixel is 15, but we must never return more than 15. */ return sourcePixel & 32767; } if (nBitsIn === 8) { /* Sometimes called with 32 bits, though pixel is 24, but we must never return more than 24. */ return sourcePixel & 16777215; } return sourcePixel; } if (sourcePixel === 0) { return sourcePixel; } d = nBitsIn - nBitsOut; /* Transfer mask */ mask = (SHL(1, nBitsOut)) - 1; srcPix = SHR(sourcePixel, d); destPix = srcPix & mask; mask = SHL(mask, nBitsOut); srcPix = SHR(srcPix, d); destPix = (destPix + (srcPix & mask)) + ((SHR(srcPix, d)) & (SHL(mask, nBitsOut))); if (destPix === 0) { return 1; } return destPix; } } /* Perform the RGBA conversion for the given source pixel */ function rgbMapPixelflags(sourcePixel, mapperFlags) { var val; val = SHIFT((sourcePixel & cmMaskTable[0]), cmShiftTable[0]); val = val | (SHIFT((sourcePixel & cmMaskTable[1]), cmShiftTable[1])); val = val | (SHIFT((sourcePixel & cmMaskTable[2]), cmShiftTable[2])); return val | (SHIFT((sourcePixel & cmMaskTable[3]), cmShiftTable[3])); } function rgbMaxwith(sourceWord, destinationWord) { if (destDepth < 16) { /* Max each pixel separately */ return partitionedMaxwithnBitsnPartitions(sourceWord, destinationWord, destDepth, destPPW); } if (destDepth === 16) { /* Max RGB components of each pixel separately */ return partitionedMaxwithnBitsnPartitions(sourceWord, destinationWord, 5, 3) + (partitionedMaxwithnBitsnPartitions(sourceWord >>> 16, destinationWord >>> 16, 5, 3) << 16); } else { /* Max RGBA components of the pixel separately */ return partitionedMaxwithnBitsnPartitions(sourceWord, destinationWord, 8, 4); } } function rgbMinwith(sourceWord, destinationWord) { if (destDepth < 16) { /* Min each pixel separately */ return partitionedMinwithnBitsnPartitions(sourceWord, destinationWord, destDepth, destPPW); } if (destDepth === 16) { /* Min RGB components of each pixel separately */ return partitionedMinwithnBitsnPartitions(sourceWord, destinationWord, 5, 3) + (partitionedMinwithnBitsnPartitions(sourceWord >>> 16, destinationWord >>> 16, 5, 3) << 16); } else { /* Min RGBA components of the pixel separately */ return partitionedMinwithnBitsnPartitions(sourceWord, destinationWord, 8, 4); } } function rgbMinInvertwith(wordToInvert, destinationWord) { var sourceWord; sourceWord = ~wordToInvert; if (destDepth < 16) { /* Min each pixel separately */ return partitionedMinwithnBitsnPartitions(sourceWord, destinationWord, destDepth, destPPW); } if (destDepth === 16) { /* Min RGB components of each pixel separately */ return partitionedMinwithnBitsnPartitions(sourceWord, destinationWord, 5, 3) + (partitionedMinwithnBitsnPartitions(sourceWord >>> 16, destinationWord >>> 16, 5, 3) << 16); } else { /* Min RGBA components of the pixel separately */ return partitionedMinwithnBitsnPartitions(sourceWord, destinationWord, 8, 4); } } function rgbMulwith(sourceWord, destinationWord) { if (destDepth < 16) { /* Mul each pixel separately */ return partitionedMulwithnBitsnPartitions(sourceWord, destinationWord, destDepth, destPPW); } if (destDepth === 16) { /* Mul RGB components of each pixel separately */ return partitionedMulwithnBitsnPartitions(sourceWord, destinationWord, 5, 3) + (partitionedMulwithnBitsnPartitions(sourceWord >>> 16, destinationWord >>> 16, 5, 3) << 16); } else { /* Mul RGBA components of the pixel separately */ return partitionedMulwithnBitsnPartitions(sourceWord, destinationWord, 8, 4); } } function rgbSubwith(sourceWord, destinationWord) { if (destDepth < 16) { /* Sub each pixel separately */ return partitionedSubfromnBitsnPartitions(sourceWord, destinationWord, destDepth, destPPW); } if (destDepth === 16) { /* Sub RGB components of each pixel separately */ return partitionedSubfromnBitsnPartitions(sourceWord, destinationWord, 5, 3) + (partitionedSubfromnBitsnPartitions(sourceWord >>> 16, destinationWord >>> 16, 5, 3) << 16); } else { /* Sub RGBA components of the pixel separately */ return partitionedSubfromnBitsnPartitions(sourceWord, destinationWord, 8, 4); } } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } /* WARNING: For WarpBlt w/ smoothing the source depth is wrong here! */ function setupColorMasks() { var bits; var targetBits; bits = (targetBits = 0); if (sourceDepth <= 8) { return null; } if (sourceDepth === 16) { bits = 5; } if (sourceDepth === 32) { bits = 8; } if (cmBitsPerColor === 0) { /* Convert to destDepth */ if (destDepth <= 8) { return null; } if (destDepth === 16) { targetBits = 5; } if (destDepth === 32) { targetBits = 8; } } else { targetBits = cmBitsPerColor; } setupColorMasksFromto(bits, targetBits); } /* Setup color masks for converting an incoming RGB pixel value from srcBits to targetBits. */ function setupColorMasksFromto(srcBits, targetBits) { var shifts = [0, 0, 0, 0]; var masks = [0, 0, 0, 0]; var deltaBits; var mask; deltaBits = targetBits - srcBits; if (deltaBits === 0) { return 0; } if (deltaBits <= 0) { /* Mask for extracting a color part of the source */ mask = (SHL(1, targetBits)) - 1; masks[RedIndex] = (SHL(mask, ((srcBits * 2) - deltaBits))); masks[GreenIndex] = (SHL(mask, (srcBits - deltaBits))); masks[BlueIndex] = (SHL(mask, (0 - deltaBits))); masks[AlphaIndex] = 0; } else { /* Mask for extracting a color part of the source */ mask = (SHL(1, srcBits)) - 1; masks[RedIndex] = (SHL(mask, (srcBits * 2))); masks[GreenIndex] = (SHL(mask, srcBits)); masks[BlueIndex] = mask; } shifts[RedIndex] = (deltaBits * 3); shifts[GreenIndex] = (deltaBits * 2); shifts[BlueIndex] = deltaBits; shifts[AlphaIndex] = 0; cmShiftTable = shifts; cmMaskTable = masks; cmFlags = cmFlags | (ColorMapPresent | ColorMapFixedPart); } function showDisplayBits() { interpreterProxy.showDisplayBitsLeftTopRightBottom(destForm, affectedL, affectedT, affectedR, affectedB); } /* This is only used when source and dest are same depth, ie, when the barrel-shift copy loop is used. */ function sourceSkewAndPointerInit() { var dxLowBits; var sxLowBits; var dWid; var pixPerM1; /* A mask, assuming power of two */ pixPerM1 = destPPW - 1; sxLowBits = sx & pixPerM1; /* check if need to preload buffer (i.e., two words of source needed for first word of destination) */ dxLowBits = dx & pixPerM1; if (hDir > 0) { /* n Bits stored in 1st word of dest */ dWid = Math.min(bbW, (destPPW - dxLowBits)); preload = (sxLowBits + dWid) > pixPerM1; } else { dWid = Math.min(bbW, (dxLowBits + 1)); preload = ((sxLowBits - dWid) + 1) < 0; } if (sourceMSB) { skew = (sxLowBits - dxLowBits) * destDepth; } else { skew = (dxLowBits - sxLowBits) * destDepth; } if (preload) { if (skew < 0) { skew += 32; } else { skew -= 32; } } /* calculate increments from end of 1 line to start of next */ sourceIndex = ((sy * sourcePitch)) + ((DIV(sx, (DIV(32, sourceDepth)))) * 4); sourceDelta = (sourcePitch * vDir) - (4 * (nWords * hDir)); if (preload) { /* Compensate for extra source word fetched */ sourceDelta -= 4 * hDir; } } function sourceWordwith(sourceWord, destinationWord) { return sourceWord; } function subWordwith(sourceWord, destinationWord) { return sourceWord - destinationWord; } /* Tally pixels into the color map. Those tallied are exactly those in the destination rectangle. Note that the source should be specified == destination, in order for the proper color map checks to be performed at setup. */ function tallyIntoMapwith(sourceWord, destinationWord) { var pixMask; var mapIndex; var destShifted; var i; var maskShifted; var pixVal; if ((cmFlags & (ColorMapPresent | ColorMapIndexedPart)) !== (ColorMapPresent | ColorMapIndexedPart)) { return destinationWord; } pixMask = maskTable[destDepth]; destShifted = destinationWord; maskShifted = destMask; for (i = 1; i <= destPPW; i++) { if ((maskShifted & pixMask) !== 0) { /* Only tally pixels within the destination rectangle */ pixVal = destShifted & pixMask; if (destDepth < 16) { mapIndex = pixVal; } else { if (destDepth === 16) { mapIndex = rgbMapfromto(pixVal, 5, cmBitsPerColor); } else { mapIndex = rgbMapfromto(pixVal, 8, cmBitsPerColor); } } tallyMapAtput(mapIndex, tallyMapAt(mapIndex) + 1); } maskShifted = SHR(maskShifted, destDepth); destShifted = SHR(destShifted, destDepth); } return destinationWord; } /* Return the word at position idx from the colorMap */ function tallyMapAt(idx) { return cmLookupTable[idx & cmMask]; } /* Store the word at position idx in the colorMap */ function tallyMapAtput(idx, value) { return cmLookupTable[idx & cmMask] = value; } /* Shortcut for stuff that's being run from the balloon engine. Since we do this at each scan line we should avoid the expensive setup for source and destination. */ /* We need a source. */ function tryCopyingBitsQuickly() { if (noSource) { return false; } if (!((combinationRule === 34) || (combinationRule === 41))) { return false; } if (sourceDepth !== 32) { return false; } if (sourceForm === destForm) { return false; } if (combinationRule === 41) { if (destDepth === 32) { rgbComponentAlpha32(); affectedL = dx; affectedR = dx + bbW; affectedT = dy; affectedB = dy + bbH; return true; } if (destDepth === 16) { rgbComponentAlpha16(); affectedL = dx; affectedR = dx + bbW; affectedT = dy; affectedB = dy + bbH; return true; } if (destDepth === 8) { rgbComponentAlpha8(); affectedL = dx; affectedR = dx + bbW; affectedT = dy; affectedB = dy + bbH; return true; } return false; } if (destDepth < 8) { return false; } if ((destDepth === 8) && ((cmFlags & ColorMapPresent) === 0)) { return false; } if (destDepth === 32) { alphaSourceBlendBits32(); } if (destDepth === 16) { alphaSourceBlendBits16(); } if (destDepth === 8) { alphaSourceBlendBits8(); } affectedL = dx; affectedR = dx + bbW; affectedT = dy; affectedB = dy + bbH; return true; } /* Unlock the bits of any OS surfaces. */ /* See the comment in lockSurfaces. Similar rules apply. That is, the area provided in ioUnlockSurface can be used to determine the dirty region after drawing. If a source is unlocked, then the area will be (0,0,0,0) to indicate that no portion is dirty. */ function unlockSurfaces() { var destHandle; var sourceHandle; var fn; var destLocked; if (hasSurfaceLock) { if (!unlockSurfaceFn) { if (!loadSurfacePlugin()) { return null; } } fn = unlockSurfaceFn; destLocked = false; destHandle = interpreterProxy.fetchPointerofObject(FormBitsIndex, destForm); if (typeof destHandle === "number") { /* The destBits are always assumed to be dirty */ destHandle = destHandle; fn(destHandle, affectedL, affectedT, affectedR-affectedL, affectedB-affectedT); destBits = (destPitch = 0); destLocked = true; } if (!noSource) { sourceHandle = interpreterProxy.fetchPointerofObject(FormBitsIndex, sourceForm); if (typeof sourceHandle === "number") { /* Only unlock sourceHandle if different from destHandle */ sourceHandle = sourceHandle; if (!(destLocked && (sourceHandle === destHandle))) { fn(sourceHandle, 0, 0, 0, 0); } sourceBits = (sourcePitch = 0); } } hasSurfaceLock = false; } } function warpBits() { var ns; ns = noSource; noSource = true; clipRange(); noSource = ns; if (noSource || ((bbW <= 0) || (bbH <= 0))) { /* zero width or height; noop */ affectedL = (affectedR = (affectedT = (affectedB = 0))); return null; } if (!lockSurfaces()) { return interpreterProxy.primitiveFail(); } destMaskAndPointerInit(); warpLoop(); if (hDir > 0) { affectedL = dx; affectedR = dx + bbW; } else { affectedL = (dx - bbW) + 1; affectedR = dx + 1; } if (vDir > 0) { affectedT = dy; affectedB = dy + bbH; } else { affectedT = (dy - bbH) + 1; affectedB = dy + 1; } unlockSurfaces(); } /* This version of the inner loop traverses an arbirary quadrilateral source, thus producing a general affine transformation. */ function warpLoop() { var mapperFlags; var dstShiftLeft; var words; var skewWord; var nSteps; var deltaP43y; var destWord; var startBits; var mergeFnwith; var deltaP43x; var pBy; var i; var yDelta; var halftoneWord; var mergeWord; var pAy; var dstShiftInc; var pBx; var sourceMapOop; var xDelta; var pAx; var deltaP12y; var endBits; var nPix; var deltaP12x; var smoothingCount; mergeFnwith = opTable[combinationRule + 1]; if (!(SIZEOF(bitBltOop) >= (BBWarpBase + 12))) { return interpreterProxy.primitiveFail(); } nSteps = height - 1; if (nSteps <= 0) { nSteps = 1; } pAx = fetchIntOrFloatofObject(BBWarpBase, bitBltOop); words = fetchIntOrFloatofObject(BBWarpBase + 3, bitBltOop); deltaP12x = deltaFromtonSteps(pAx, words, nSteps); if (deltaP12x < 0) { pAx = words - (nSteps * deltaP12x); } pAy = fetchIntOrFloatofObject(BBWarpBase + 1, bitBltOop); words = fetchIntOrFloatofObject(BBWarpBase + 4, bitBltOop); deltaP12y = deltaFromtonSteps(pAy, words, nSteps); if (deltaP12y < 0) { pAy = words - (nSteps * deltaP12y); } pBx = fetchIntOrFloatofObject(BBWarpBase + 9, bitBltOop); words = fetchIntOrFloatofObject(BBWarpBase + 6, bitBltOop); deltaP43x = deltaFromtonSteps(pBx, words, nSteps); if (deltaP43x < 0) { pBx = words - (nSteps * deltaP43x); } pBy = fetchIntOrFloatofObject(BBWarpBase + 10, bitBltOop); words = fetchIntOrFloatofObject(BBWarpBase + 7, bitBltOop); deltaP43y = deltaFromtonSteps(pBy, words, nSteps); if (deltaP43y < 0) { pBy = words - (nSteps * deltaP43y); } if (interpreterProxy.failed()) { return false; } if (interpreterProxy.methodArgumentCount() === 2) { smoothingCount = interpreterProxy.stackIntegerValue(1); sourceMapOop = interpreterProxy.stackValue(0); if (sourceMapOop.isNil) { if (sourceDepth < 16) { /* color map is required to smooth non-RGB dest */ return interpreterProxy.primitiveFail(); } } else { if (SIZEOF(sourceMapOop) < (SHL(1, sourceDepth))) { /* sourceMap must be long enough for sourceDepth */ return interpreterProxy.primitiveFail(); } sourceMapOop = sourceMapOop.wordsOrBytes(); } } else { smoothingCount = 1; sourceMapOop = interpreterProxy.nilObject(); } nSteps = width - 1; if (nSteps <= 0) { nSteps = 1; } startBits = destPPW - (dx & (destPPW - 1)); endBits = (((dx + bbW) - 1) & (destPPW - 1)) + 1; if (bbW < startBits) { startBits = bbW; } if (destY < clipY) { /* Advance increments if there was clipping in y */ pAx += (clipY - destY) * deltaP12x; pAy += (clipY - destY) * deltaP12y; pBx += (clipY - destY) * deltaP43x; pBy += (clipY - destY) * deltaP43y; } warpLoopSetup(); if ((smoothingCount > 1) && ((cmFlags & ColorMapNewStyle) === 0)) { if (!cmLookupTable) { if (destDepth === 16) { setupColorMasksFromto(8, 5); } } else { setupColorMasksFromto(8, cmBitsPerColor); } } mapperFlags = cmFlags & -9; if (destMSB) { dstShiftInc = 0 - destDepth; dstShiftLeft = 32 - destDepth; } else { dstShiftInc = destDepth; dstShiftLeft = 0; } for (i = 1; i <= bbH; i++) { /* here is the vertical loop... */ xDelta = deltaFromtonSteps(pAx, pBx, nSteps); if (xDelta >= 0) { sx = pAx; } else { sx = pBx - (nSteps * xDelta); } yDelta = deltaFromtonSteps(pAy, pBy, nSteps); if (yDelta >= 0) { sy = pAy; } else { sy = pBy - (nSteps * yDelta); } if (destMSB) { dstBitShift = 32 - (((dx & (destPPW - 1)) + 1) * destDepth); } else { dstBitShift = (dx & (destPPW - 1)) * destDepth; } if (destX < clipX) { /* Advance increments if there was clipping in x */ sx += (clipX - destX) * xDelta; sy += (clipX - destX) * yDelta; } if (noHalftone) { halftoneWord = AllOnes; } else { halftoneWord = halftoneAt((dy + i) - 1); } destMask = mask1; /* Here is the inner loop... */ nPix = startBits; words = nWords; do { /* pick up word */ if (smoothingCount === 1) { /* Faster if not smoothing */ skewWord = warpPickSourcePixelsxDeltahyDeltahxDeltavyDeltavdstShiftIncflags(nPix, xDelta, yDelta, deltaP12x, deltaP12y, dstShiftInc, mapperFlags); } else { /* more difficult with smoothing */ skewWord = warpPickSmoothPixelsxDeltahyDeltahxDeltavyDeltavsourceMapsmoothingdstShiftInc(nPix, xDelta, yDelta, deltaP12x, deltaP12y, sourceMapOop, smoothingCount, dstShiftInc); } dstBitShift = dstShiftLeft; if (destMask === AllOnes) { /* avoid read-modify-write */ mergeWord = mergeFnwith(skewWord & halftoneWord, destBits[destIndex >>> 2]); destBits[destIndex >>> 2] = destMask & mergeWord; } else { /* General version using dest masking */ destWord = destBits[destIndex >>> 2]; mergeWord = mergeFnwith(skewWord & halftoneWord, destWord & destMask); destWord = (destMask & mergeWord) | (destWord & ~destMask); destBits[destIndex >>> 2] = destWord; } destIndex += 4; if (words === 2) { /* e.g., is the next word the last word? */ /* set mask for last word in this row */ destMask = mask2; nPix = endBits; } else { /* use fullword mask for inner loop */ destMask = AllOnes; nPix = destPPW; } } while(!(((--words)) === 0)); pAx += deltaP12x; pAy += deltaP12y; pBx += deltaP43x; pBy += deltaP43y; destIndex += destDelta; } } /* Setup values for faster pixel fetching. */ function warpLoopSetup() { var i; var words; /* warpSrcShift = log2(sourceDepth) */ warpSrcShift = 0; /* recycle temp */ words = sourceDepth; while (!(words === 1)) { ++warpSrcShift; words = words >>> 1; } /* warpAlignShift: Shift for aligning x position to word boundary */ warpSrcMask = maskTable[sourceDepth]; /* warpAlignMask: Mask for extracting the pixel position from an x position */ warpAlignShift = 5 - warpSrcShift; /* Setup the lookup table for source bit shifts */ /* warpBitShiftTable: given an sub-word x value what's the bit shift? */ warpAlignMask = (SHL(1, warpAlignShift)) - 1; for (i = 0; i <= warpAlignMask; i++) { if (sourceMSB) { warpBitShiftTable[i] = (32 - (SHL((i + 1), warpSrcShift))); } else { warpBitShiftTable[i] = (SHL(i, warpSrcShift)); } } } /* Pick n (sub-) pixels from the source form, mapped by sourceMap, average the RGB values, map by colorMap and return the new word. This version is only called from WarpBlt with smoothingCount > 1 */ function warpPickSmoothPixelsxDeltahyDeltahxDeltavyDeltavsourceMapsmoothingdstShiftInc(nPixels, xDeltah, yDeltah, xDeltav, yDeltav, sourceMap, n, dstShiftInc) { var k; var destWord; var xdh; var j; var ydh; var i; var xdv; var dstMask; var ydv; var rgb; var y; var b; var yy; var g; var x; var a; var r; var nPix; var xx; /* nope - too much stuff in here */ dstMask = maskTable[destDepth]; destWord = 0; if (n === 2) { /* Try avoiding divides for most common n (divide by 2 is generated as shift) */ xdh = xDeltah >> 1; ydh = yDeltah >> 1; xdv = xDeltav >> 1; ydv = yDeltav >> 1; } else { xdh = DIV(xDeltah, n); ydh = DIV(yDeltah, n); xdv = DIV(xDeltav, n); ydv = DIV(yDeltav, n); } i = nPixels; do { x = sx; y = sy; /* Pick and average n*n subpixels */ a = (r = (g = (b = 0))); /* actual number of pixels (not clipped and not transparent) */ nPix = 0; j = n; do { xx = x; yy = y; k = n; do { /* get a single subpixel */ rgb = pickWarpPixelAtXy(xx, yy); if (!((combinationRule === 25) && (rgb === 0))) { /* If not clipped and not transparent, then tally rgb values */ ++nPix; if (sourceDepth < 16) { /* Get RGBA values from sourcemap table */ rgb = sourceMap[rgb]; } else { /* Already in RGB format */ if (sourceDepth === 16) { rgb = rgbMap16To32(rgb); } else { rgb = rgbMap32To32(rgb); } } b += rgb & 255; g += (rgb >>> 8) & 255; r += (rgb >>> 16) & 255; a += rgb >>> 24; } xx += xdh; yy += ydh; } while(!(((--k)) === 0)); x += xdv; y += ydv; } while(!(((--j)) === 0)); if ((nPix === 0) || ((combinationRule === 25) && (nPix < ((n * n) >> 1)))) { /* All pixels were 0, or most were transparent */ rgb = 0; } else { /* normalize rgba sums */ if (nPix === 4) { /* Try to avoid divides for most common n */ r = r >>> 2; g = g >>> 2; b = b >>> 2; a = a >>> 2; } else { r = DIV(r, nPix); g = DIV(g, nPix); b = DIV(b, nPix); a = DIV(a, nPix); } /* map the pixel */ rgb = (((a << 24) + (r << 16)) + (g << 8)) + b; if (rgb === 0) { /* only generate zero if pixel is really transparent */ if ((((r + g) + b) + a) > 0) { rgb = 1; } } rgb = mapPixelflags(rgb, cmFlags); } destWord = destWord | (SHL((rgb & dstMask), dstBitShift)); dstBitShift += dstShiftInc; sx += xDeltah; sy += yDeltah; } while(!(((--i)) === 0)); return destWord; } /* Pick n pixels from the source form, map by colorMap and return aligned by dstBitShift. This version is only called from WarpBlt with smoothingCount = 1 */ function warpPickSourcePixelsxDeltahyDeltahxDeltavyDeltavdstShiftIncflags(nPixels, xDeltah, yDeltah, xDeltav, yDeltav, dstShiftInc, mapperFlags) { var sourcePix; var nPix; var destPix; var dstMask; var destWord; /* Yepp - this should go into warpLoop */ dstMask = maskTable[destDepth]; destWord = 0; nPix = nPixels; if (mapperFlags === (ColorMapPresent | ColorMapIndexedPart)) { /* a little optimization for (pretty crucial) blits using indexed lookups only */ /* grab, colormap and mix in pixel */ do { sourcePix = pickWarpPixelAtXy(sx, sy); destPix = cmLookupTable[sourcePix & cmMask]; destWord = destWord | (SHL((destPix & dstMask), dstBitShift)); dstBitShift += dstShiftInc; sx += xDeltah; sy += yDeltah; } while(!(((--nPix)) === 0)); } else { /* grab, colormap and mix in pixel */ do { sourcePix = pickWarpPixelAtXy(sx, sy); destPix = mapPixelflags(sourcePix, mapperFlags); destWord = destWord | (SHL((destPix & dstMask), dstBitShift)); dstBitShift += dstShiftInc; sx += xDeltah; sy += yDeltah; } while(!(((--nPix)) === 0)); } return destWord; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("BitBltPlugin", { primitiveCopyBits: primitiveCopyBits, copyBits: copyBits, moduleUnloaded: moduleUnloaded, primitiveDrawLoop: primitiveDrawLoop, primitiveDisplayString: primitiveDisplayString, initialiseModule: initialiseModule, loadBitBltFrom: loadBitBltFrom, setInterpreter: setInterpreter, primitiveWarpBits: primitiveWarpBits, getModuleName: getModuleName, primitivePixelValueAt: primitivePixelValueAt, copyBitsFromtoat: copyBitsFromtoat, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin function CroquetPlugin() { return { getModuleName: function() { return "CroquetPlugin"; }, interpreterProxy: null, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; return true; }, primitiveGatherEntropy: function(argCount) { var rcvr = this.interpreterProxy.stackObjectValue(0); if (this.interpreterProxy.failed()) { return null; } if (!rcvr.isBytes()) { return this.interpreterProxy.primitiveFail(); } window.crypto.getRandomValues(rcvr.bytes); this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.trueObject()); return true; }, }; } function registerCroquetPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("CroquetPlugin", CroquetPlugin()); } else self.setTimeout(registerCroquetPlugin, 100); } registerCroquetPlugin(); /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:20 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from FFTPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function FFTPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift /*** Variables ***/ var fftSize = 0; var imagData = null; var imagDataSize = 0; var interpreterProxy = null; var moduleName = "FFTPlugin 3 November 2014 (e)"; var nu = 0; var permTable = null; var permTableSize = 0; var realData = null; var realDataSize = 0; var sinTable = null; var sinTableSize = 0; /* Return the first indexable word of oop which is assumed to be variableWordSubclass */ function checkedFloatPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWords(oop)); if (interpreterProxy.failed()) { return 0; } return oop.wordsAsFloat32Array(); } /* Return the first indexable word of oop which is assumed to be variableWordSubclass */ function checkedWordPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWords(oop)); return oop.words; } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function loadFFTFrom(fftOop) { var oop; interpreterProxy.success(SIZEOF(fftOop) >= 6); if (interpreterProxy.failed()) { return false; } nu = interpreterProxy.fetchIntegerofObject(0, fftOop); fftSize = interpreterProxy.fetchIntegerofObject(1, fftOop); oop = interpreterProxy.fetchPointerofObject(2, fftOop); sinTableSize = SIZEOF(oop); sinTable = checkedFloatPtrOf(oop); oop = interpreterProxy.fetchPointerofObject(3, fftOop); permTableSize = SIZEOF(oop); permTable = checkedWordPtrOf(oop); oop = interpreterProxy.fetchPointerofObject(4, fftOop); realDataSize = SIZEOF(oop); realData = checkedFloatPtrOf(oop); oop = interpreterProxy.fetchPointerofObject(5, fftOop); imagDataSize = SIZEOF(oop); /* Check assumptions about sizes */ imagData = checkedFloatPtrOf(oop); interpreterProxy.success((((((SHL(1, nu)) === fftSize) && (((fftSize >> 2) + 1) === sinTableSize)) && (fftSize === realDataSize)) && (fftSize === imagDataSize)) && (realDataSize === imagDataSize)); return interpreterProxy.failed() === false; } function permuteData() { var a; var b; var end; var i; var tmp; i = 0; end = permTableSize; while (i < end) { a = permTable[i] - 1; b = permTable[i + 1] - 1; if (!((a < realDataSize) && (b < realDataSize))) { return interpreterProxy.success(false); } tmp = realData[a]; realData[a] = realData[b]; realData[b] = tmp; tmp = imagData[a]; imagData[a] = imagData[b]; imagData[b] = tmp; i += 2; } } function primitiveFFTPermuteData() { var rcvr; rcvr = interpreterProxy.stackObjectValue(0); if (!loadFFTFrom(rcvr)) { return null; } permuteData(); if (interpreterProxy.failed()) { /* permuteData went wrong. Do the permutation again -- this will restore the original order */ permuteData(); } } function primitiveFFTScaleData() { var rcvr; rcvr = interpreterProxy.stackObjectValue(0); if (!loadFFTFrom(rcvr)) { return null; } scaleData(); } function primitiveFFTTransformData() { var forward; var rcvr; forward = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); rcvr = interpreterProxy.stackObjectValue(1); if (!loadFFTFrom(rcvr)) { return null; } transformData(forward); if (!interpreterProxy.failed()) { interpreterProxy.pop(1); } } /* Scale all elements by 1/n when doing inverse */ function scaleData() { var i; var realN; if (fftSize <= 1) { return null; } realN = (1.0 / fftSize); for (i = 0; i <= (fftSize - 1); i++) { realData[i] = (realData[i] * realN); imagData[i] = (imagData[i] * realN); } } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function transformData(forward) { permuteData(); if (interpreterProxy.failed()) { /* permuteData went wrong. Do the permutation again -- this will restore the original order */ permuteData(); return null; } transformForward(forward); if (!forward) { scaleData(); } } function transformForward(forward) { var fftScale; var fftSize2; var fftSize4; var i; var ii; var imagT; var imagU; var ip; var j; var lev; var lev1; var level; var realT; var realU; var theta; fftSize2 = fftSize >> 1; fftSize4 = fftSize >> 2; for (level = 1; level <= nu; level++) { lev = SHL(1, level); lev1 = lev >> 1; fftScale = DIV(fftSize, lev); for (j = 1; j <= lev1; j++) { /* pi * (j-1) / lev1 mapped onto 0..n/2 */ theta = (j - 1) * fftScale; if (theta < fftSize4) { /* Compute U, the complex multiplier for each level */ realU = sinTable[(sinTableSize - theta) - 1]; imagU = sinTable[theta]; } else { realU = 0.0 - sinTable[theta - fftSize4]; imagU = sinTable[fftSize2 - theta]; } if (!forward) { imagU = 0.0 - imagU; } i = j; while (i <= fftSize) { ip = (i + lev1) - 1; ii = i - 1; realT = (realData[ip] * realU) - (imagData[ip] * imagU); imagT = (realData[ip] * imagU) + (imagData[ip] * realU); realData[ip] = (realData[ii] - realT); imagData[ip] = (imagData[ii] - imagT); realData[ii] = (realData[ii] + realT); imagData[ii] = (imagData[ii] + imagT); i += lev; } } } } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("FFTPlugin", { primitiveFFTTransformData: primitiveFFTTransformData, setInterpreter: setInterpreter, primitiveFFTPermuteData: primitiveFFTPermuteData, primitiveFFTScaleData: primitiveFFTScaleData, getModuleName: getModuleName, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:20 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from FloatArrayPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function FloatArrayPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } /*** Variables ***/ var interpreterProxy = null; var moduleName = "FloatArrayPlugin 3 November 2014 (e)"; /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver. */ function primitiveAddFloatArray() { var arg; var argPtr; var i; var length; var rcvr; var rcvrPtr; arg = interpreterProxy.stackObjectValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(arg)); interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(arg); interpreterProxy.success(length === SIZEOF(rcvr)); if (interpreterProxy.failed()) { return null; } rcvrPtr = rcvr.wordsAsFloat32Array(); argPtr = arg.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] + argPtr[i]); } interpreterProxy.pop(1); } /* Primitive. Add the argument, a scalar value to the receiver, a FloatArray */ function primitiveAddScalar() { var i; var length; var rcvr; var rcvrPtr; var value; value = interpreterProxy.stackFloatValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); rcvrPtr = rcvr.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] + value); } interpreterProxy.pop(1); } function primitiveAt() { var floatPtr; var floatValue; var index; var rcvr; index = interpreterProxy.stackIntegerValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); interpreterProxy.success((index > 0) && (index <= SIZEOF(rcvr))); if (interpreterProxy.failed()) { return null; } floatPtr = rcvr.wordsAsFloat32Array(); floatValue = floatPtr[index - 1]; interpreterProxy.pop(2); interpreterProxy.pushFloat(floatValue); } function primitiveAtPut() { var floatPtr; var floatValue; var index; var rcvr; var value; value = interpreterProxy.stackValue(0); if (typeof value === "number") { floatValue = value; } else { floatValue = interpreterProxy.floatValueOf(value); } index = interpreterProxy.stackIntegerValue(1); rcvr = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); interpreterProxy.success((index > 0) && (index <= SIZEOF(rcvr))); if (interpreterProxy.failed()) { return null; } floatPtr = rcvr.wordsAsFloat32Array(); floatPtr[index - 1] = floatValue; if (!interpreterProxy.failed()) { interpreterProxy.popthenPush(3, value); } } /* Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver. */ function primitiveDivFloatArray() { var arg; var argPtr; var i; var length; var rcvr; var rcvrPtr; arg = interpreterProxy.stackObjectValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(arg)); interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(arg); interpreterProxy.success(length === SIZEOF(rcvr)); if (interpreterProxy.failed()) { return null; } rcvrPtr = rcvr.wordsAsFloat32Array(); /* Check if any of the argument's values is zero */ argPtr = arg.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { if (argPtr[i] === 0) { return interpreterProxy.primitiveFail(); } } for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] / argPtr[i]); } interpreterProxy.pop(1); } /* Primitive. Add the argument, a scalar value to the receiver, a FloatArray */ function primitiveDivScalar() { var i; var inverse; var length; var rcvr; var rcvrPtr; var value; value = interpreterProxy.stackFloatValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (value === 0.0) { return interpreterProxy.primitiveFail(); } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); rcvrPtr = rcvr.wordsAsFloat32Array(); inverse = 1.0 / value; for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] * inverse); } interpreterProxy.pop(1); } /* Primitive. Compute the dot product of the receiver and the argument. The dot product is defined as the sum of the products of the individual elements. */ function primitiveDotProduct() { var arg; var argPtr; var i; var length; var rcvr; var rcvrPtr; var result; arg = interpreterProxy.stackObjectValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(arg)); interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(arg); interpreterProxy.success(length === SIZEOF(rcvr)); if (interpreterProxy.failed()) { return null; } rcvrPtr = rcvr.wordsAsFloat32Array(); argPtr = arg.wordsAsFloat32Array(); result = 0.0; for (i = 0; i <= (length - 1); i++) { result += rcvrPtr[i] * argPtr[i]; } interpreterProxy.pop(2); interpreterProxy.pushFloat(result); } function primitiveEqual() { var arg; var argPtr; var i; var length; var rcvr; var rcvrPtr; arg = interpreterProxy.stackObjectValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(arg)); interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(2); length = SIZEOF(arg); if (length !== SIZEOF(rcvr)) { return interpreterProxy.pushBool(false); } rcvrPtr = rcvr.wordsAsFloat32Array(); argPtr = arg.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { if (rcvrPtr[i] !== argPtr[i]) { return interpreterProxy.pushBool(false); } } return interpreterProxy.pushBool(true); } function primitiveHashArray() { var i; var length; var rcvr; var rcvrPtr; var result; rcvr = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); rcvrPtr = rcvr.wordsAsInt32Array(); result = 0; for (i = 0; i <= (length - 1); i++) { result += rcvrPtr[i]; } interpreterProxy.pop(1); return interpreterProxy.pushInteger(result & 536870911); } /* Primitive. Compute the length of the argument (sqrt of sum of component squares). */ function primitiveLength() { var i; var length; var rcvr; var rcvrPtr; var result; rcvr = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); interpreterProxy.success(true); rcvrPtr = rcvr.wordsAsFloat32Array(); result = 0.0; for (i = 0; i <= (length - 1); i++) { result += rcvrPtr[i] * rcvrPtr[i]; } result = Math.sqrt(result); interpreterProxy.popthenPush(1, interpreterProxy.floatObjectOf(result)); } /* Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver. */ function primitiveMulFloatArray() { var arg; var argPtr; var i; var length; var rcvr; var rcvrPtr; arg = interpreterProxy.stackObjectValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(arg)); interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(arg); interpreterProxy.success(length === SIZEOF(rcvr)); if (interpreterProxy.failed()) { return null; } rcvrPtr = rcvr.wordsAsFloat32Array(); argPtr = arg.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] * argPtr[i]); } interpreterProxy.pop(1); } /* Primitive. Add the argument, a scalar value to the receiver, a FloatArray */ function primitiveMulScalar() { var i; var length; var rcvr; var rcvrPtr; var value; value = interpreterProxy.stackFloatValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); rcvrPtr = rcvr.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] * value); } interpreterProxy.pop(1); } /* Primitive. Normalize the argument (A FloatArray) in place. */ function primitiveNormalize() { var i; var len; var length; var rcvr; var rcvrPtr; rcvr = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); interpreterProxy.success(true); rcvrPtr = rcvr.wordsAsFloat32Array(); len = 0.0; for (i = 0; i <= (length - 1); i++) { len += rcvrPtr[i] * rcvrPtr[i]; } interpreterProxy.success(len > 0.0); if (interpreterProxy.failed()) { return null; } len = Math.sqrt(len); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] / len); } } /* Primitive. Add the receiver and the argument, both FloatArrays and store the result into the receiver. */ function primitiveSubFloatArray() { var arg; var argPtr; var i; var length; var rcvr; var rcvrPtr; arg = interpreterProxy.stackObjectValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(arg)); interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(arg); interpreterProxy.success(length === SIZEOF(rcvr)); if (interpreterProxy.failed()) { return null; } rcvrPtr = rcvr.wordsAsFloat32Array(); argPtr = arg.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] - argPtr[i]); } interpreterProxy.pop(1); } /* Primitive. Add the argument, a scalar value to the receiver, a FloatArray */ function primitiveSubScalar() { var i; var length; var rcvr; var rcvrPtr; var value; value = interpreterProxy.stackFloatValue(0); rcvr = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); rcvrPtr = rcvr.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { rcvrPtr[i] = (rcvrPtr[i] - value); } interpreterProxy.pop(1); } /* Primitive. Find the sum of each float in the receiver, a FloatArray, and stash the result into the argument Float. */ function primitiveSum() { var i; var length; var rcvr; var rcvrPtr; var sum; rcvr = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvr)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvr); rcvrPtr = rcvr.wordsAsFloat32Array(); sum = 0.0; for (i = 0; i <= (length - 1); i++) { sum += rcvrPtr[i]; } interpreterProxy.popthenPush(1, interpreterProxy.floatObjectOf(sum)); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("FloatArrayPlugin", { primitiveMulFloatArray: primitiveMulFloatArray, primitiveEqual: primitiveEqual, primitiveAtPut: primitiveAtPut, primitiveAt: primitiveAt, primitiveNormalize: primitiveNormalize, primitiveSubFloatArray: primitiveSubFloatArray, primitiveDivFloatArray: primitiveDivFloatArray, primitiveAddScalar: primitiveAddScalar, primitiveDotProduct: primitiveDotProduct, primitiveSubScalar: primitiveSubScalar, setInterpreter: setInterpreter, primitiveSum: primitiveSum, getModuleName: getModuleName, primitiveHashArray: primitiveHashArray, primitiveMulScalar: primitiveMulScalar, primitiveLength: primitiveLength, primitiveAddFloatArray: primitiveAddFloatArray, primitiveDivScalar: primitiveDivScalar, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 14 November 2014 12:21:50 am */ /* Automatically generated by JSSmartSyntaxPluginCodeGenerator VMMakerJS-bf.17 uuid: 399be48b-95d8-4722-bdcc-39a94a12c486 from GeniePlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function GeniePlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function PTR_ADD(p, n) { return new Int32Array(p.buffer, p.byteOffset + n * 4); } /*** Variables ***/ var interpreterProxy = null; var moduleName = "GeniePlugin v2.0 14 November 2014 (e)"; /* arguments are pointer to ints paired as x,y coordinates of points */ function cSquaredDistanceFromto(aPoint, bPoint) { var aPointX; var bPointX; var xDiff; var aPointY; var bPointY; var yDiff; aPointX = aPoint[0]; aPointY = aPoint[1]; bPointX = bPoint[0]; bPointY = bPoint[1]; xDiff = bPointX - aPointX; yDiff = bPointY - aPointY; return (xDiff * xDiff) + (yDiff * yDiff); } function cSubstAngleFactorFromto(startDegreeNumber, endDegreeNumber) { var absDiff; absDiff = Math.abs(endDegreeNumber - startDegreeNumber); if (absDiff > 180) { absDiff = 360 - absDiff; } return (absDiff * absDiff) >>> 6; } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function majorNO() { return 2; } function minorNO() { return 0; } function msg(s) { console.log(moduleName + ": " + s); } function primSameClassAbsoluteStrokeDistanceMyPoints_otherPoints_myVectors_otherVectors_mySquaredLengths_otherSquaredLengths_myAngles_otherAngles_maxSizeAndReferenceFlag_rowBase_rowInsertRemove_rowInsertRemoveCount() { var otherAngles; var otherSquaredLengthsSize; var forReference; var jM1; var iM1; var jM1T2; var base; var insert; var otherVectors; var otherVectorsSize; var otherSquaredLengths; var rowBaseSize; var myPoints; var jLimiT; var mySquaredLengths; var additionalMultiInsertRemoveCost; var remove; var otherPoints; var otherPointsSize; var myVectors; var rowInsertRemoveCount; var rowBase; var maxDist; var iM1T2; var j; var insertRemove; var i; var myVectorsSize; var subst; var maxSize; var removeBase; var substBase; var myAngles; var insertRemoveCount; var rowInsertRemove; var insertBase; var myPointsOop; var otherPointsOop; var myVectorsOop; var otherVectorsOop; var mySquaredLengthsOop; var otherSquaredLengthsOop; var myAnglesOop; var otherAnglesOop; var maxSizeAndRefFlag; var rowBaseOop; var rowInsertRemoveOop; var rowInsertRemoveCountOop; var _return_value; myPointsOop = interpreterProxy.stackValue(11); otherPointsOop = interpreterProxy.stackValue(10); myVectorsOop = interpreterProxy.stackValue(9); otherVectorsOop = interpreterProxy.stackValue(8); mySquaredLengthsOop = interpreterProxy.stackValue(7); otherSquaredLengthsOop = interpreterProxy.stackValue(6); myAnglesOop = interpreterProxy.stackValue(5); otherAnglesOop = interpreterProxy.stackValue(4); maxSizeAndRefFlag = interpreterProxy.stackIntegerValue(3); rowBaseOop = interpreterProxy.stackValue(2); rowInsertRemoveOop = interpreterProxy.stackValue(1); rowInsertRemoveCountOop = interpreterProxy.stackValue(0); if (interpreterProxy.failed()) { return null; } if (interpreterProxy.failed()) { msg("failed 1"); return null; } interpreterProxy.success((((((((((interpreterProxy.isWords(myPointsOop) && interpreterProxy.isWords(otherPointsOop)) && interpreterProxy.isWords(myVectorsOop)) && interpreterProxy.isWords(otherVectorsOop)) && interpreterProxy.isWords(mySquaredLengthsOop)) && interpreterProxy.isWords(otherSquaredLengthsOop)) && interpreterProxy.isWords(myAnglesOop)) && interpreterProxy.isWords(otherAnglesOop)) && interpreterProxy.isWords(rowBaseOop)) && interpreterProxy.isWords(rowInsertRemoveOop)) && interpreterProxy.isWords(rowInsertRemoveCountOop)); if (interpreterProxy.failed()) { msg("failed 2"); return null; } interpreterProxy.success(interpreterProxy.isMemberOf(myPointsOop, "PointArray") && interpreterProxy.isMemberOf(otherPointsOop, "PointArray")); if (interpreterProxy.failed()) { msg("failed 3"); return null; } myPoints = myPointsOop.wordsAsInt32Array(); otherPoints = otherPointsOop.wordsAsInt32Array(); myVectors = myVectorsOop.wordsAsInt32Array(); otherVectors = otherVectorsOop.wordsAsInt32Array(); mySquaredLengths = mySquaredLengthsOop.wordsAsInt32Array(); otherSquaredLengths = otherSquaredLengthsOop.wordsAsInt32Array(); myAngles = myAnglesOop.wordsAsInt32Array(); otherAngles = otherAnglesOop.wordsAsInt32Array(); rowBase = rowBaseOop.wordsAsInt32Array(); rowInsertRemove = rowInsertRemoveOop.wordsAsInt32Array(); /* Note: myPointsSize and mySquaredLengthsSize variables eliminated to reduce method temporary variable count for closure-enabled images */ /* PointArrays */ /* myPointsSize := (interpreterProxy stSizeOf: myPointsOop) bitShift: -1. */ rowInsertRemoveCount = rowInsertRemoveCountOop.wordsAsInt32Array(); otherPointsSize = SIZEOF(otherPointsOop) >>> 1; myVectorsSize = SIZEOF(myVectorsOop) >>> 1; /* IntegerArrays */ /* mySquaredLengthsSize := interpreterProxy stSizeOf: mySquaredLengthsOop. */ otherVectorsSize = SIZEOF(otherVectorsOop) >>> 1; otherSquaredLengthsSize = SIZEOF(otherSquaredLengthsOop); rowBaseSize = SIZEOF(rowBaseOop); interpreterProxy.success(((rowBaseSize === SIZEOF(rowInsertRemoveOop)) && (rowBaseSize === SIZEOF(rowInsertRemoveCountOop))) && (rowBaseSize > otherVectorsSize)); if (interpreterProxy.failed()) { msg("failed 4"); return null; } interpreterProxy.success((((((SIZEOF(mySquaredLengthsOop) >= (myVectorsSize - 1)) && ((SIZEOF(myPointsOop) >>> 1) >= myVectorsSize)) && (otherSquaredLengthsSize >= (otherVectorsSize - 1))) && (otherPointsSize >= otherVectorsSize)) && (SIZEOF(myAnglesOop) >= (myVectorsSize - 1))) && (SIZEOF(otherAnglesOop) >= (otherVectorsSize - 1))); if (interpreterProxy.failed()) { msg("failed 5"); return null; } forReference = maxSizeAndRefFlag & 1; maxSize = maxSizeAndRefFlag >>> 1; maxDist = 1 << 29; if (forReference) { additionalMultiInsertRemoveCost = 0; } else { additionalMultiInsertRemoveCost = (maxSize * maxSize) >>> 10; } rowBase[0] = 0; rowInsertRemove[0] = 0; rowInsertRemoveCount[0] = 2; insertRemove = 0 - additionalMultiInsertRemoveCost; jLimiT = otherVectorsSize; if (!((otherPointsSize >= (jLimiT - 1)) && (otherSquaredLengthsSize >= (jLimiT - 1)))) { interpreterProxy.primitiveFail(); return null; } for (j = 1; j <= jLimiT; j++) { jM1 = j - 1; insertRemove = (insertRemove + ((otherSquaredLengths[jM1] + cSquaredDistanceFromto(PTR_ADD(otherPoints, (jM1 << 1)), myPoints)) >>> 7)) + additionalMultiInsertRemoveCost; rowInsertRemove[j] = insertRemove; rowBase[j] = (insertRemove * j); rowInsertRemoveCount[j] = (j + 1); } insertRemove = rowInsertRemove[0] - additionalMultiInsertRemoveCost; for (i = 1; i <= myVectorsSize; i++) { iM1 = i - 1; iM1T2 = iM1 << 1; substBase = rowBase[0]; insertRemove = (insertRemove + ((mySquaredLengths[iM1] + cSquaredDistanceFromto(PTR_ADD(myPoints, iM1T2), otherPoints)) >>> 7)) + additionalMultiInsertRemoveCost; rowInsertRemove[0] = insertRemove; rowBase[0] = (insertRemove * i); rowInsertRemoveCount[0] = (i + 1); jLimiT = otherVectorsSize; for (j = 1; j <= jLimiT; j++) { jM1 = j - 1; jM1T2 = jM1 << 1; removeBase = rowBase[j]; insertBase = rowBase[jM1]; remove = (mySquaredLengths[iM1] + cSquaredDistanceFromto(PTR_ADD(myPoints, iM1T2), PTR_ADD(otherPoints, (j << 1)))) >>> 7; if (((insertRemove = rowInsertRemove[j])) === 0) { removeBase += remove; } else { removeBase = (removeBase + insertRemove) + (remove * rowInsertRemoveCount[j]); remove += insertRemove; } insert = (otherSquaredLengths[jM1] + cSquaredDistanceFromto(PTR_ADD(otherPoints, jM1T2), PTR_ADD(myPoints, (i << 1)))) >>> 7; if (((insertRemove = rowInsertRemove[jM1])) === 0) { insertBase += insert; } else { insertBase = (insertBase + insertRemove) + (insert * rowInsertRemoveCount[jM1]); insert += insertRemove; } if (forReference) { substBase = maxDist; } else { subst = ((cSquaredDistanceFromto(PTR_ADD(otherVectors, jM1T2), PTR_ADD(myVectors, iM1T2)) + cSquaredDistanceFromto(PTR_ADD(otherPoints, jM1T2), PTR_ADD(myPoints, iM1T2))) * (16 + cSubstAngleFactorFromto(otherAngles[jM1], myAngles[iM1]))) >>> 11; substBase += subst; } if ((substBase <= removeBase) && (substBase <= insertBase)) { base = substBase; insertRemove = 0; insertRemoveCount = 1; } else { if (removeBase <= insertBase) { base = removeBase; insertRemove = remove + additionalMultiInsertRemoveCost; insertRemoveCount = rowInsertRemoveCount[j] + 1; } else { base = insertBase; insertRemove = insert + additionalMultiInsertRemoveCost; insertRemoveCount = rowInsertRemoveCount[jM1] + 1; } } substBase = rowBase[j]; rowBase[j] = Math.min(base, maxDist); rowInsertRemove[j] = Math.min(insertRemove, maxDist); rowInsertRemoveCount[j] = insertRemoveCount; } insertRemove = rowInsertRemove[0]; } _return_value = base; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(13, _return_value); return null; } /* majorNO * 1000 + minorNO */ function primVersionNO() { var _return_value; _return_value = ((majorNO() * 1000) + minorNO()); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(1, _return_value); return null; } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("GeniePlugin", { primVersionNO: primVersionNO, setInterpreter: setInterpreter, primSameClassAbsoluteStrokeDistanceMyPoints_otherPoints_myVectors_otherVectors_mySquaredLengths_otherSquaredLengths_myAngles_otherAngles_maxSizeAndReferenceFlag_rowBase_rowInsertRemove_rowInsertRemoveCount: primSameClassAbsoluteStrokeDistanceMyPoints_otherPoints_myVectors_otherVectors_mySquaredLengths_otherSquaredLengths_myAngles_otherAngles_maxSizeAndReferenceFlag_rowBase_rowInsertRemove_rowInsertRemoveCount, getModuleName: getModuleName, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:20 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from JPEGReaderPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function JPEGReaderPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function BYTESIZEOF(obj) { return obj.bytes ? obj.bytes.length : obj.words ? obj.words.length * 4 : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift /*** Constants ***/ var BlockWidthIndex = 5; var BlueIndex = 2; var CurrentXIndex = 0; var CurrentYIndex = 1; var DCTSize = 8; var DCTSize2 = 64; var FIXn0n298631336 = 2446; var FIXn0n34414 = 22554; var FIXn0n390180644 = 3196; var FIXn0n541196100 = 4433; var FIXn0n71414 = 46802; var FIXn0n765366865 = 6270; var FIXn0n899976223 = 7373; var FIXn1n175875602 = 9633; var FIXn1n40200 = 91881; var FIXn1n501321110 = 12299; var FIXn1n77200 = 116130; var FIXn1n847759065 = 15137; var FIXn1n961570560 = 16069; var FIXn2n053119869 = 16819; var FIXn2n562915447 = 20995; var FIXn3n072711026 = 25172; var GreenIndex = 1; var HScaleIndex = 2; var MCUBlockIndex = 4; var MCUWidthIndex = 8; var MaxBits = 16; var MaxMCUBlocks = 128; var MaxSample = 255; var MinComponentSize = 11; var PriorDCValueIndex = 10; var RedIndex = 0; var SampleOffset = 127; var VScaleIndex = 3; /*** Variables ***/ var acTable = null; var acTableSize = 0; var cbBlocks = new Array(128); var cbComponent = new Array(11); var crBlocks = new Array(128); var crComponent = new Array(11); var dcTable = null; var dcTableSize = 0; var ditherMask = 0; var interpreterProxy = null; var jpegBits = null; var jpegBitsSize = 0; var jpegNaturalOrder = [ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 ]; var jsBitBuffer = 0; var jsBitCount = 0; var jsCollection = null; var jsPosition = 0; var jsReadLimit = 0; var moduleName = "JPEGReaderPlugin 3 November 2014 (e)"; var residuals = null; var yBlocks = new Array(128); var yComponent = new Array(11); function cbColorComponentFrom(oop) { return colorComponentfrom(cbComponent, oop) && (colorComponentBlocksfrom(cbBlocks, oop)); } function colorComponentfrom(aColorComponent, oop) { if (typeof oop === "number") { return false; } if (!interpreterProxy.isPointers(oop)) { return false; } if (SIZEOF(oop) < MinComponentSize) { return false; } aColorComponent[CurrentXIndex] = interpreterProxy.fetchIntegerofObject(CurrentXIndex, oop); aColorComponent[CurrentYIndex] = interpreterProxy.fetchIntegerofObject(CurrentYIndex, oop); aColorComponent[HScaleIndex] = interpreterProxy.fetchIntegerofObject(HScaleIndex, oop); aColorComponent[VScaleIndex] = interpreterProxy.fetchIntegerofObject(VScaleIndex, oop); aColorComponent[BlockWidthIndex] = interpreterProxy.fetchIntegerofObject(BlockWidthIndex, oop); aColorComponent[MCUWidthIndex] = interpreterProxy.fetchIntegerofObject(MCUWidthIndex, oop); aColorComponent[PriorDCValueIndex] = interpreterProxy.fetchIntegerofObject(PriorDCValueIndex, oop); return !interpreterProxy.failed(); } function colorComponentBlocksfrom(blocks, oop) { var arrayOop; var blockOop; var i; var max; if (typeof oop === "number") { return false; } if (!interpreterProxy.isPointers(oop)) { return false; } if (SIZEOF(oop) < MinComponentSize) { return false; } arrayOop = interpreterProxy.fetchPointerofObject(MCUBlockIndex, oop); if (typeof arrayOop === "number") { return false; } if (!interpreterProxy.isPointers(arrayOop)) { return false; } max = SIZEOF(arrayOop); if (max > MaxMCUBlocks) { return false; } for (i = 0; i <= (max - 1); i++) { blockOop = interpreterProxy.fetchPointerofObject(i, arrayOop); if (typeof blockOop === "number") { return false; } if (!interpreterProxy.isWords(blockOop)) { return false; } if (SIZEOF(blockOop) !== DCTSize2) { return false; } blocks[i] = blockOop.wordsAsInt32Array(); } return !interpreterProxy.failed(); } function colorConvertGrayscaleMCU() { var i; var y; yComponent[CurrentXIndex] = 0; yComponent[CurrentYIndex] = 0; for (i = 0; i <= (jpegBitsSize - 1); i++) { y = nextSampleY(); y += residuals[GreenIndex]; y = Math.min(y, MaxSample); residuals[GreenIndex] = (y & ditherMask); y = y & (MaxSample - ditherMask); y = Math.max(y, 1); jpegBits[i] = (((4278190080 + (y << 16)) + (y << 8)) + y); } } function colorConvertMCU() { var blue; var cb; var cr; var green; var i; var red; var y; yComponent[CurrentXIndex] = 0; yComponent[CurrentYIndex] = 0; cbComponent[CurrentXIndex] = 0; cbComponent[CurrentYIndex] = 0; crComponent[CurrentXIndex] = 0; crComponent[CurrentYIndex] = 0; for (i = 0; i <= (jpegBitsSize - 1); i++) { y = nextSampleY(); cb = nextSampleCb(); cb -= SampleOffset; cr = nextSampleCr(); cr -= SampleOffset; red = (y + ((FIXn1n40200 * cr) >> 16)) + residuals[RedIndex]; red = Math.min(red, MaxSample); red = Math.max(red, 0); residuals[RedIndex] = (red & ditherMask); red = red & (MaxSample - ditherMask); red = Math.max(red, 1); green = ((y - ((FIXn0n34414 * cb) >> 16)) - ((FIXn0n71414 * cr) >> 16)) + residuals[GreenIndex]; green = Math.min(green, MaxSample); green = Math.max(green, 0); residuals[GreenIndex] = (green & ditherMask); green = green & (MaxSample - ditherMask); green = Math.max(green, 1); blue = (y + ((FIXn1n77200 * cb) >> 16)) + residuals[BlueIndex]; blue = Math.min(blue, MaxSample); blue = Math.max(blue, 0); residuals[BlueIndex] = (blue & ditherMask); blue = blue & (MaxSample - ditherMask); blue = Math.max(blue, 1); jpegBits[i] = (((4278190080 + (red << 16)) + (green << 8)) + blue); } } function crColorComponentFrom(oop) { return colorComponentfrom(crComponent, oop) && (colorComponentBlocksfrom(crBlocks, oop)); } function decodeBlockIntocomponent(anArray, aColorComponent) { var bits; var byte; var i; var index; var zeroCount; byte = jpegDecodeValueFromsize(dcTable, dcTableSize); if (byte < 0) { return interpreterProxy.primitiveFail(); } if (byte !== 0) { bits = getBits(byte); byte = scaleAndSignExtendinFieldWidth(bits, byte); } byte = aColorComponent[PriorDCValueIndex] = (aColorComponent[PriorDCValueIndex] + byte); anArray[0] = byte; for (i = 1; i <= (DCTSize2 - 1); i++) { anArray[i] = 0; } index = 1; while (index < DCTSize2) { byte = jpegDecodeValueFromsize(acTable, acTableSize); if (byte < 0) { return interpreterProxy.primitiveFail(); } zeroCount = byte >>> 4; byte = byte & 15; if (byte !== 0) { index += zeroCount; bits = getBits(byte); byte = scaleAndSignExtendinFieldWidth(bits, byte); if ((index < 0) || (index >= DCTSize2)) { return interpreterProxy.primitiveFail(); } anArray[jpegNaturalOrder[index]] = byte; } else { if (zeroCount === 15) { index += zeroCount; } else { return null; } } ++index; } } function fillBuffer() { var byte; while (jsBitCount <= 16) { if (!(jsPosition < jsReadLimit)) { return jsBitCount; } byte = jsCollection[jsPosition]; ++jsPosition; if (byte === 255) { /* peek for 00 */ if (!((jsPosition < jsReadLimit) && (jsCollection[jsPosition] === 0))) { --jsPosition; return jsBitCount; } ++jsPosition; } jsBitBuffer = (jsBitBuffer << 8) | byte; jsBitCount += 8; } return jsBitCount; } function getBits(requestedBits) { var value; if (requestedBits > jsBitCount) { fillBuffer(); if (requestedBits > jsBitCount) { return -1; } } jsBitCount -= requestedBits; value = SHR(jsBitBuffer, jsBitCount); jsBitBuffer = jsBitBuffer & ((SHL(1, jsBitCount)) - 1); return value; } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function idctBlockIntqt(anArray, qt) { var anACTerm; var dcval; var i; var j; var row; var t0; var t1; var t10; var t11; var t12; var t13; var t2; var t3; var v; var ws = new Array(64); var z1; var z2; var z3; var z4; var z5; for (i = 0; i <= (DCTSize - 1); i++) { anACTerm = -1; for (row = 1; row <= (DCTSize - 1); row++) { if (anACTerm === -1) { if (anArray[(row * DCTSize) + i] !== 0) { anACTerm = row; } } } if (anACTerm === -1) { dcval = (anArray[i] * qt[0]) << 2; for (j = 0; j <= (DCTSize - 1); j++) { ws[(j * DCTSize) + i] = dcval; } } else { z2 = anArray[(DCTSize * 2) + i] * qt[(DCTSize * 2) + i]; z3 = anArray[(DCTSize * 6) + i] * qt[(DCTSize * 6) + i]; z1 = (z2 + z3) * FIXn0n541196100; t2 = z1 + (z3 * (0 - FIXn1n847759065)); t3 = z1 + (z2 * FIXn0n765366865); z2 = anArray[i] * qt[i]; z3 = anArray[(DCTSize * 4) + i] * qt[(DCTSize * 4) + i]; t0 = (z2 + z3) << 13; t1 = (z2 - z3) << 13; t10 = t0 + t3; t13 = t0 - t3; t11 = t1 + t2; t12 = t1 - t2; t0 = anArray[(DCTSize * 7) + i] * qt[(DCTSize * 7) + i]; t1 = anArray[(DCTSize * 5) + i] * qt[(DCTSize * 5) + i]; t2 = anArray[(DCTSize * 3) + i] * qt[(DCTSize * 3) + i]; t3 = anArray[DCTSize + i] * qt[DCTSize + i]; z1 = t0 + t3; z2 = t1 + t2; z3 = t0 + t2; z4 = t1 + t3; z5 = (z3 + z4) * FIXn1n175875602; t0 = t0 * FIXn0n298631336; t1 = t1 * FIXn2n053119869; t2 = t2 * FIXn3n072711026; t3 = t3 * FIXn1n501321110; z1 = z1 * (0 - FIXn0n899976223); z2 = z2 * (0 - FIXn2n562915447); z3 = z3 * (0 - FIXn1n961570560); z4 = z4 * (0 - FIXn0n390180644); z3 += z5; z4 += z5; t0 = (t0 + z1) + z3; t1 = (t1 + z2) + z4; t2 = (t2 + z2) + z3; t3 = (t3 + z1) + z4; ws[i] = ((t10 + t3) >> 11); ws[(DCTSize * 7) + i] = ((t10 - t3) >> 11); ws[(DCTSize * 1) + i] = ((t11 + t2) >> 11); ws[(DCTSize * 6) + i] = ((t11 - t2) >> 11); ws[(DCTSize * 2) + i] = ((t12 + t1) >> 11); ws[(DCTSize * 5) + i] = ((t12 - t1) >> 11); ws[(DCTSize * 3) + i] = ((t13 + t0) >> 11); ws[(DCTSize * 4) + i] = ((t13 - t0) >> 11); } } for (i = 0; i <= (DCTSize2 - DCTSize); i += DCTSize) { z2 = ws[i + 2]; z3 = ws[i + 6]; z1 = (z2 + z3) * FIXn0n541196100; t2 = z1 + (z3 * (0 - FIXn1n847759065)); t3 = z1 + (z2 * FIXn0n765366865); t0 = (ws[i] + ws[i + 4]) << 13; t1 = (ws[i] - ws[i + 4]) << 13; t10 = t0 + t3; t13 = t0 - t3; t11 = t1 + t2; t12 = t1 - t2; t0 = ws[i + 7]; t1 = ws[i + 5]; t2 = ws[i + 3]; t3 = ws[i + 1]; z1 = t0 + t3; z2 = t1 + t2; z3 = t0 + t2; z4 = t1 + t3; z5 = (z3 + z4) * FIXn1n175875602; t0 = t0 * FIXn0n298631336; t1 = t1 * FIXn2n053119869; t2 = t2 * FIXn3n072711026; t3 = t3 * FIXn1n501321110; z1 = z1 * (0 - FIXn0n899976223); z2 = z2 * (0 - FIXn2n562915447); z3 = z3 * (0 - FIXn1n961570560); z4 = z4 * (0 - FIXn0n390180644); z3 += z5; z4 += z5; t0 = (t0 + z1) + z3; t1 = (t1 + z2) + z4; t2 = (t2 + z2) + z3; t3 = (t3 + z1) + z4; v = ((t10 + t3) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i] = v; v = ((t10 - t3) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 7] = v; v = ((t11 + t2) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 1] = v; v = ((t11 - t2) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 6] = v; v = ((t12 + t1) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 2] = v; v = ((t12 - t1) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 5] = v; v = ((t13 + t0) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 3] = v; v = ((t13 - t0) >> 18) + SampleOffset; v = Math.min(v, MaxSample); v = Math.max(v, 0); anArray[i + 4] = v; } } /* Decode the next value in the receiver using the given huffman table. */ function jpegDecodeValueFromsize(table, tableSize) { var bits; var bitsNeeded; var index; var tableIndex; var value; /* Initial bits needed */ bitsNeeded = table[0] >>> 24; if (bitsNeeded > MaxBits) { return -1; } /* First real table */ tableIndex = 2; while (true) { /* Get bits */ bits = getBits(bitsNeeded); if (bits < 0) { return -1; } index = (tableIndex + bits) - 1; if (index >= tableSize) { return -1; } /* Lookup entry in table */ value = table[index]; if ((value & 1056964608) === 0) { return value; } /* Table offset in low 16 bit */ tableIndex = value & 65535; /* Additional bits in high 8 bit */ bitsNeeded = (value >>> 24) & 255; if (bitsNeeded > MaxBits) { return -1; } } return -1; } function loadJPEGStreamFrom(streamOop) { var oop; var sz; if (SIZEOF(streamOop) < 5) { return false; } if (!interpreterProxy.isPointers(streamOop)) { return false; } oop = interpreterProxy.fetchPointerofObject(0, streamOop); if (typeof oop === "number") { return false; } if (!interpreterProxy.isBytes(oop)) { return false; } jsCollection = oop.bytes; sz = BYTESIZEOF(oop); jsPosition = interpreterProxy.fetchIntegerofObject(1, streamOop); jsReadLimit = interpreterProxy.fetchIntegerofObject(2, streamOop); jsBitBuffer = interpreterProxy.fetchIntegerofObject(3, streamOop); jsBitCount = interpreterProxy.fetchIntegerofObject(4, streamOop); if (interpreterProxy.failed()) { return false; } if (sz < jsReadLimit) { return false; } if ((jsPosition < 0) || (jsPosition >= jsReadLimit)) { return false; } return true; } function nextSampleCb() { var blockIndex; var curX; var dx; var dy; var sample; var sampleIndex; var sx; var sy; dx = (curX = cbComponent[CurrentXIndex]); dy = cbComponent[CurrentYIndex]; sx = cbComponent[HScaleIndex]; sy = cbComponent[VScaleIndex]; if ((sx !== 0) && (sy !== 0)) { dx = DIV(dx, sx); dy = DIV(dy, sy); } blockIndex = ((dy >>> 3) * cbComponent[BlockWidthIndex]) + (dx >>> 3); sampleIndex = ((dy & 7) << 3) + (dx & 7); sample = cbBlocks[blockIndex][sampleIndex]; ++curX; if (curX < (cbComponent[MCUWidthIndex] * 8)) { cbComponent[CurrentXIndex] = curX; } else { cbComponent[CurrentXIndex] = 0; cbComponent[CurrentYIndex]++; } return sample; } function nextSampleCr() { var blockIndex; var curX; var dx; var dy; var sample; var sampleIndex; var sx; var sy; dx = (curX = crComponent[CurrentXIndex]); dy = crComponent[CurrentYIndex]; sx = crComponent[HScaleIndex]; sy = crComponent[VScaleIndex]; if ((sx !== 0) && (sy !== 0)) { dx = DIV(dx, sx); dy = DIV(dy, sy); } blockIndex = ((dy >>> 3) * crComponent[BlockWidthIndex]) + (dx >>> 3); sampleIndex = ((dy & 7) << 3) + (dx & 7); sample = crBlocks[blockIndex][sampleIndex]; ++curX; if (curX < (crComponent[MCUWidthIndex] * 8)) { crComponent[CurrentXIndex] = curX; } else { crComponent[CurrentXIndex] = 0; crComponent[CurrentYIndex]++; } return sample; } function nextSampleY() { var blockIndex; var curX; var dx; var dy; var sample; var sampleIndex; var sx; var sy; dx = (curX = yComponent[CurrentXIndex]); dy = yComponent[CurrentYIndex]; sx = yComponent[HScaleIndex]; sy = yComponent[VScaleIndex]; if ((sx !== 0) && (sy !== 0)) { dx = DIV(dx, sx); dy = DIV(dy, sy); } blockIndex = ((dy >>> 3) * yComponent[BlockWidthIndex]) + (dx >>> 3); sampleIndex = ((dy & 7) << 3) + (dx & 7); sample = yBlocks[blockIndex][sampleIndex]; ++curX; if (curX < (yComponent[MCUWidthIndex] * 8)) { yComponent[CurrentXIndex] = curX; } else { yComponent[CurrentXIndex] = 0; yComponent[CurrentYIndex]++; } return sample; } /* Requires: JPEGColorComponent bits WordArray with: 3*Integer (residuals) ditherMask */ function primitiveColorConvertGrayscaleMCU() { var arrayOop; if (interpreterProxy.methodArgumentCount() !== 4) { return interpreterProxy.primitiveFail(); } ditherMask = interpreterProxy.stackIntegerValue(0); arrayOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (!(interpreterProxy.isWords(arrayOop) && (SIZEOF(arrayOop) === 3))) { return interpreterProxy.primitiveFail(); } residuals = arrayOop.wordsAsInt32Array(); arrayOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(arrayOop)) { return interpreterProxy.primitiveFail(); } jpegBitsSize = SIZEOF(arrayOop); jpegBits = arrayOop.wordsAsInt32Array(); arrayOop = interpreterProxy.stackObjectValue(3); if (interpreterProxy.failed()) { return null; } if (!yColorComponentFrom(arrayOop)) { return interpreterProxy.primitiveFail(); } colorConvertGrayscaleMCU(); interpreterProxy.pop(4); } /* Requires: Array with: 3*JPEGColorComponent bits WordArray with: 3*Integer (residuals) ditherMask */ function primitiveColorConvertMCU() { var arrayOop; if (interpreterProxy.methodArgumentCount() !== 4) { return interpreterProxy.primitiveFail(); } ditherMask = interpreterProxy.stackIntegerValue(0); arrayOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (!(interpreterProxy.isWords(arrayOop) && (SIZEOF(arrayOop) === 3))) { return interpreterProxy.primitiveFail(); } residuals = arrayOop.wordsAsInt32Array(); arrayOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(arrayOop)) { return interpreterProxy.primitiveFail(); } jpegBitsSize = SIZEOF(arrayOop); jpegBits = arrayOop.wordsAsInt32Array(); arrayOop = interpreterProxy.stackObjectValue(3); if (interpreterProxy.failed()) { return null; } if (!(interpreterProxy.isPointers(arrayOop) && (SIZEOF(arrayOop) === 3))) { return interpreterProxy.primitiveFail(); } if (!yColorComponentFrom(interpreterProxy.fetchPointerofObject(0, arrayOop))) { return interpreterProxy.primitiveFail(); } if (!cbColorComponentFrom(interpreterProxy.fetchPointerofObject(1, arrayOop))) { return interpreterProxy.primitiveFail(); } if (!crColorComponentFrom(interpreterProxy.fetchPointerofObject(2, arrayOop))) { return interpreterProxy.primitiveFail(); } colorConvertMCU(); interpreterProxy.pop(4); } /* In: anArray WordArray of: DCTSize2 aColorComponent JPEGColorComponent dcTable WordArray acTable WordArray stream JPEGStream */ function primitiveDecodeMCU() { var anArray; var arrayOop; var oop; if (interpreterProxy.methodArgumentCount() !== 5) { return interpreterProxy.primitiveFail(); } oop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } if (!loadJPEGStreamFrom(oop)) { return interpreterProxy.primitiveFail(); } arrayOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(arrayOop)) { return interpreterProxy.primitiveFail(); } acTableSize = SIZEOF(arrayOop); acTable = arrayOop.wordsAsInt32Array(); arrayOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(arrayOop)) { return interpreterProxy.primitiveFail(); } dcTableSize = SIZEOF(arrayOop); dcTable = arrayOop.wordsAsInt32Array(); oop = interpreterProxy.stackObjectValue(3); if (interpreterProxy.failed()) { return null; } if (!colorComponentfrom(yComponent, oop)) { return interpreterProxy.primitiveFail(); } arrayOop = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(arrayOop)) { return interpreterProxy.primitiveFail(); } if (SIZEOF(arrayOop) !== DCTSize2) { return interpreterProxy.primitiveFail(); } anArray = arrayOop.wordsAsInt32Array(); if (interpreterProxy.failed()) { return null; } decodeBlockIntocomponent(anArray, yComponent); if (interpreterProxy.failed()) { return null; } storeJPEGStreamOn(interpreterProxy.stackValue(0)); interpreterProxy.storeIntegerofObjectwithValue(PriorDCValueIndex, interpreterProxy.stackValue(3), yComponent[PriorDCValueIndex]); interpreterProxy.pop(5); } /* In: anArray: IntegerArray new: DCTSize2 qt: IntegerArray new: DCTSize2. */ function primitiveIdctInt() { var anArray; var arrayOop; var qt; if (interpreterProxy.methodArgumentCount() !== 2) { return interpreterProxy.primitiveFail(); } arrayOop = interpreterProxy.stackObjectValue(0); if (interpreterProxy.failed()) { return null; } if (!(interpreterProxy.isWords(arrayOop) && (SIZEOF(arrayOop) === DCTSize2))) { return interpreterProxy.primitiveFail(); } qt = arrayOop.wordsAsInt32Array(); arrayOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (!(interpreterProxy.isWords(arrayOop) && (SIZEOF(arrayOop) === DCTSize2))) { return interpreterProxy.primitiveFail(); } anArray = arrayOop.wordsAsInt32Array(); idctBlockIntqt(anArray, qt); interpreterProxy.pop(2); } function scaleAndSignExtendinFieldWidth(aNumber, w) { if (aNumber < (SHL(1, (w - 1)))) { return (aNumber - (SHL(1, w))) + 1; } else { return aNumber; } } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function storeJPEGStreamOn(streamOop) { interpreterProxy.storeIntegerofObjectwithValue(1, streamOop, jsPosition); interpreterProxy.storeIntegerofObjectwithValue(3, streamOop, jsBitBuffer); interpreterProxy.storeIntegerofObjectwithValue(4, streamOop, jsBitCount); } function yColorComponentFrom(oop) { return colorComponentfrom(yComponent, oop) && (colorComponentBlocksfrom(yBlocks, oop)); } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("JPEGReaderPlugin", { setInterpreter: setInterpreter, primitiveIdctInt: primitiveIdctInt, primitiveColorConvertMCU: primitiveColorConvertMCU, primitiveColorConvertGrayscaleMCU: primitiveColorConvertGrayscaleMCU, primitiveDecodeMCU: primitiveDecodeMCU, getModuleName: getModuleName, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:21 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from KedamaPlugin Kedama-Plugins-yo.1 uuid: 3fc7d691-0149-ba4d-a339-5d27cd44a2f8 */ (function KedamaPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHIFT(a, b) { return b < 0 ? (b < -31 ? 0 : a >>> (0-b) ) : (b > 31 ? 0 : a << b); } /*** Variables ***/ var interpreterProxy = null; var kedamaRandomSeed = 0; var moduleName = "KedamaPlugin 3 November 2014 (e)"; var randA = 0; var randM = 0; var randQ = 0; var randR = 0; function degreesFromXy(x, y) { var tanVal; var theta; if (x === 0.0) { if (y >= 0.0) { return 90.0; } else { return 270.0; } } else { tanVal = y / x; theta = Math.atan(tanVal); if (x >= 0.0) { if (y >= 0.0) { return theta / 0.0174532925199433; } else { return 360.0 + (theta / 0.0174532925199433); } } else { return 180.0 + (theta / 0.0174532925199433); } } } function degreesToRadians(degrees) { var deg; var headingRadians; var q; deg = 90.0 - degrees; q = deg / 360.0|0; if (deg < 0.0) { --q; } headingRadians = (deg - (q * 360.0)) * 0.0174532925199433; return headingRadians; } function drawTurtlesInArray() { var bitsIndex; var colorArray; var colorOop; var destBits; var destHeight; var destOop; var destWidth; var i; var size; var visible; var visibleArray; var visibleOop; var x; var xArray; var xOop; var y; var yArray; var yOop; visibleOop = interpreterProxy.stackValue(0); colorOop = interpreterProxy.stackValue(1); yOop = interpreterProxy.stackValue(2); xOop = interpreterProxy.stackValue(3); destHeight = interpreterProxy.stackIntegerValue(4); destWidth = interpreterProxy.stackIntegerValue(5); destOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(destOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(colorOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isBytes(visibleOop)) { interpreterProxy.primitiveFail(); return null; } if ((destHeight * destWidth) !== SIZEOF(destOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xOop); if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(colorOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(visibleOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); colorArray = colorOop.words; visibleArray = visibleOop.bytes; destBits = destOop.words; for (i = 0; i <= (size - 1); i++) { x = (xArray[i]|0); y = (yArray[i]|0); visible = visibleArray[i]; if ((visible !== 0) && (((x >= 0) && (y >= 0)) && ((x < destWidth) && (y < destHeight)))) { bitsIndex = (y * destWidth) + x; destBits[bitsIndex] = colorArray[i]; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function getHeadingArrayInto() { var heading; var headingArray; var headingOop; var i; var resultArray; var resultOop; var size; resultOop = interpreterProxy.stackValue(0); headingOop = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(resultOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(headingOop); if (SIZEOF(resultOop) !== size) { interpreterProxy.primitiveFail(); return null; } headingArray = headingOop.wordsAsFloat32Array(); resultArray = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (size - 1); i++) { heading = headingArray[i]; heading = heading / 0.0174532925199433; heading = 90.0 - heading; if (!(heading > 0.0)) { heading += 360.0; } resultArray[i] = heading; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(2); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function getScalarHeading() { var heading; var headingArray; var headingOop; var index; headingOop = interpreterProxy.stackValue(0); index = interpreterProxy.stackIntegerValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) < index) { interpreterProxy.primitiveFail(); return null; } headingArray = headingOop.wordsAsFloat32Array(); heading = headingArray[index - 1]; heading = radiansToDegrees(heading); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(3); interpreterProxy.pushFloat(heading); } function initialiseModule() { kedamaRandomSeed = 17; /* magic constant = 16807 */ randA = 16807; /* magic constant = 2147483647 */ randM = 2147483647; randQ = DIV(randM, randA); randR = MOD(randM, randA); } function kedamaRandom2(range) { var hi; var lo; var r; var v; var val; if (range < 0) { r = 0 - range; } else { r = range; } hi = DIV(kedamaRandomSeed, randQ); lo = MOD(kedamaRandomSeed, randQ); kedamaRandomSeed = (randA * lo) - (randR * hi); v = kedamaRandomSeed & 65535; val = (v * (r + 1)) >>> 16; if (range < 0) { return 0 - val; } else { return val; } } function kedamaSetRandomSeed() { var seed; seed = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } kedamaRandomSeed = seed & 65536; interpreterProxy.pop(1); } function makeMask() { var alpha; var dOrigin; var data; var dataBits; var dataSize; var highMask; var i; var mOrigin; var maskBits; var maskSize; var pixel; var shiftAmount; shiftAmount = interpreterProxy.stackIntegerValue(0); pixel = interpreterProxy.stackIntegerValue(1); maskBits = interpreterProxy.stackValue(2); dataBits = interpreterProxy.stackValue(3); if (interpreterProxy.failed()) { return null; } dataSize = SIZEOF(dataBits); maskSize = SIZEOF(maskBits); if (dataSize !== maskSize) { interpreterProxy.primitiveFail(); return null; } if (shiftAmount < -32) { interpreterProxy.primitiveFail(); return null; } if (shiftAmount > 8) { interpreterProxy.primitiveFail(); return null; } dOrigin = dataBits.words; mOrigin = maskBits.words; highMask = 4278190080; for (i = 0; i <= (dataSize - 1); i++) { data = dOrigin[i]; alpha = SHIFT(data, shiftAmount); if (alpha > 255) { alpha = 255; } if (alpha < 0) { alpha = 0; } mOrigin[i] = (((alpha << 24) & highMask) | pixel); } interpreterProxy.pop(4); } function makeMaskLog() { var alpha; var dOrigin; var data; var dataBits; var dataSize; var highMask; var i; var mOrigin; var maskBits; var maskSize; var max; var maxFirst; var maxLog; var maxOop; var pixel; maxOop = interpreterProxy.stackValue(0); pixel = interpreterProxy.stackIntegerValue(1); maskBits = interpreterProxy.stackValue(2); dataBits = interpreterProxy.stackValue(3); if (interpreterProxy.failed()) { return null; } maxFirst = maxOop.words; max = maxFirst[0]; if (interpreterProxy.failed()) { return null; } maxLog = Math.log(max); dataSize = SIZEOF(dataBits); maskSize = SIZEOF(maskBits); if (dataSize !== maskSize) { interpreterProxy.primitiveFail(); return null; } dOrigin = dataBits.words; mOrigin = maskBits.words; highMask = 4278190080; for (i = 0; i <= (dataSize - 1); i++) { data = dOrigin[i]; if (data === 0) { alpha = 0; } else { alpha = (((255.0 / maxLog) * Math.log(data))|0); } if (alpha > 255) { alpha = 255; } mOrigin[i] = (((alpha << 24) & highMask) | pixel); } interpreterProxy.pop(4); } function makeTurtlesMap() { var height; var index; var map; var mapIndex; var mapOop; var size; var whoArray; var whoOop; var width; var x; var xArray; var xOop; var y; var yArray; var yOop; height = interpreterProxy.stackIntegerValue(0); width = interpreterProxy.stackIntegerValue(1); yOop = interpreterProxy.stackValue(2); xOop = interpreterProxy.stackValue(3); whoOop = interpreterProxy.stackValue(4); mapOop = interpreterProxy.stackValue(5); if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(whoOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(mapOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(whoOop); if (SIZEOF(xOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(mapOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); whoArray = whoOop.words; map = mapOop.words; for (index = 0; index <= ((height * width) - 1); index++) { map[index] = 0; } for (index = 0; index <= (size - 1); index++) { x = xArray[index]; y = yArray[index]; mapIndex = (width * y) + x; if ((mapIndex >= 0) && (mapIndex < (height * width))) { map[mapIndex] = whoArray[index]; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primPixelAtXY() { var bits; var bitsOop; var height; var index; var ret; var width; var x; var xPos; var y; var yPos; height = interpreterProxy.stackIntegerValue(0); width = interpreterProxy.stackIntegerValue(1); yPos = interpreterProxy.stackFloatValue(2); xPos = interpreterProxy.stackFloatValue(3); bitsOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(bitsOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } x = xPos|0; y = yPos|0; bits = bitsOop.words; if ((((x >= 0) && (x < width)) && (y >= 0)) && (y < height)) { index = (y * width) + x; ret = bits[index]; } else { ret = 0; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); interpreterProxy.pushInteger(ret); } function primPixelAtXYPut() { var bits; var bitsOop; var height; var index; var v; var value; var width; var x; var xPos; var y; var yPos; height = interpreterProxy.stackIntegerValue(0); width = interpreterProxy.stackIntegerValue(1); value = interpreterProxy.stackIntegerValue(2); yPos = interpreterProxy.stackFloatValue(3); xPos = interpreterProxy.stackFloatValue(4); bitsOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(bitsOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } x = xPos|0; y = yPos|0; v = value; if (v > 1073741823) { v = 1073741823; } if (v < 0) { v = 0; } bits = bitsOop.words; if ((((x >= 0) && (x < width)) && (y >= 0)) && (y < height)) { index = (y * width) + x; bits[index] = v; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primPixelsAtXY() { var bits; var bitsHeight; var bitsIndex; var bitsOop; var bitsWidth; var destWords; var destWordsOop; var i; var size; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; destWordsOop = interpreterProxy.stackValue(0); bitsHeight = interpreterProxy.stackIntegerValue(1); bitsWidth = interpreterProxy.stackIntegerValue(2); bitsOop = interpreterProxy.stackValue(3); yArrayOop = interpreterProxy.stackValue(4); xArrayOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(destWordsOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if ((bitsHeight * bitsWidth) !== SIZEOF(bitsOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xArrayOop); if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(destWordsOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); destWords = destWordsOop.words; bits = bitsOop.words; for (i = 0; i <= (size - 1); i++) { x = (xArray[i]|0); y = (yArray[i]|0); if (((x >= 0) && (y >= 0)) && ((x < bitsWidth) && (y < bitsHeight))) { bitsIndex = (y * bitsWidth) + x; destWords[i] = bits[bitsIndex]; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primScalarForward() { var bottomEdgeMode; var destHeight; var destWidth; var dist; var headingArray; var headingOop; var i; var index; var leftEdgeMode; var newX; var newY; var rightEdgeMode; var size; var topEdgeMode; var val; var xArray; var xOop; var yArray; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); rightEdgeMode = interpreterProxy.stackIntegerValue(2); leftEdgeMode = interpreterProxy.stackIntegerValue(3); destHeight = interpreterProxy.stackFloatValue(4); destWidth = interpreterProxy.stackFloatValue(5); val = interpreterProxy.stackFloatValue(6); headingOop = interpreterProxy.stackValue(7); yOop = interpreterProxy.stackValue(8); xOop = interpreterProxy.stackValue(9); index = interpreterProxy.stackIntegerValue(10); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xOop); if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); dist = val; i = index - 1; newX = xArray[i] + (dist * Math.cos(headingArray[i])); newY = yArray[i] - (dist * Math.sin(headingArray[i])); scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(i, xArray, headingArray, newX, destWidth, leftEdgeMode, rightEdgeMode); scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(i, yArray, headingArray, newY, destHeight, topEdgeMode, bottomEdgeMode); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(11); } function primSetPixelsAtXY() { var bits; var bitsHeight; var bitsIndex; var bitsOop; var bitsWidth; var i; var intValue; var isValueInt; var size; var value; var valueOop; var wordsValue; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; valueOop = interpreterProxy.stackValue(0); bitsHeight = interpreterProxy.stackIntegerValue(1); bitsWidth = interpreterProxy.stackIntegerValue(2); bitsOop = interpreterProxy.stackValue(3); yArrayOop = interpreterProxy.stackValue(4); xArrayOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if ((bitsHeight * bitsWidth) !== SIZEOF(bitsOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xArrayOop); if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } isValueInt = typeof valueOop === "number"; if (isValueInt) { intValue = valueOop; } if (!isValueInt) { if (!interpreterProxy.isMemberOf(valueOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(valueOop) !== size) { interpreterProxy.primitiveFail(); return null; } } xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); if (!isValueInt) { wordsValue = valueOop.words; } bits = bitsOop.words; if (isValueInt) { value = intValue; } for (i = 0; i <= (size - 1); i++) { x = (xArray[i]|0); y = (yArray[i]|0); if (((x >= 0) && (y >= 0)) && ((x < bitsWidth) && (y < bitsHeight))) { bitsIndex = (y * bitsWidth) + x; if (!isValueInt) { value = wordsValue[i]; } bits[bitsIndex] = value; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primTurtlesForward() { var bottomEdgeMode; var destHeight; var destWidth; var dist; var headingArray; var headingOop; var i; var isValVector; var leftEdgeMode; var newX; var newY; var rightEdgeMode; var size; var topEdgeMode; var val; var valArray; var valOop; var xArray; var xOop; var yArray; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); rightEdgeMode = interpreterProxy.stackIntegerValue(2); leftEdgeMode = interpreterProxy.stackIntegerValue(3); destHeight = interpreterProxy.stackFloatValue(4); destWidth = interpreterProxy.stackFloatValue(5); valOop = interpreterProxy.stackValue(6); headingOop = interpreterProxy.stackValue(7); yOop = interpreterProxy.stackValue(8); xOop = interpreterProxy.stackValue(9); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (valOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(valOop)) { isValVector = true; } else { interpreterProxy.primitiveFail(); return null; } } size = SIZEOF(xOop); if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (isValVector) { if (SIZEOF(valOop) !== size) { interpreterProxy.primitiveFail(); return null; } } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { valArray = valOop.wordsAsFloat32Array(); } else { val = interpreterProxy.floatValueOf(valOop); } for (i = 0; i <= (size - 1); i++) { if (isValVector) { dist = valArray[i]; } else { dist = val; } newX = xArray[i] + (dist * Math.cos(headingArray[i])); newY = yArray[i] - (dist * Math.sin(headingArray[i])); scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(i, xArray, headingArray, newX, destWidth, leftEdgeMode, rightEdgeMode); scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(i, yArray, headingArray, newY, destHeight, topEdgeMode, bottomEdgeMode); } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(10); } function primUpHill() { var bits; var bitsOop; var endX; var endY; var height; var maxVal; var maxValX; var maxValY; var ret; var rowOffset; var sniffRange; var startX; var startY; var tH; var tX; var tY; var thisVal; var turtleX; var turtleY; var width; var x; var y; sniffRange = interpreterProxy.stackIntegerValue(0); height = interpreterProxy.stackIntegerValue(1); width = interpreterProxy.stackIntegerValue(2); bitsOop = interpreterProxy.stackValue(3); tH = interpreterProxy.stackFloatValue(4); tY = interpreterProxy.stackFloatValue(5); tX = interpreterProxy.stackFloatValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(bitsOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } bits = bitsOop.words; turtleX = tX; turtleY = tY; turtleX = Math.max(turtleX, 0); turtleY = Math.max(turtleY, 0); turtleX = Math.min(turtleX, (width - 1)); turtleY = Math.min(turtleY, (height - 1)); startX = Math.max((turtleX - sniffRange), 0); endX = Math.min((turtleX + sniffRange), (width - 1)); startY = Math.max((turtleY - sniffRange), 0); endY = Math.min((turtleY + sniffRange), (height - 1)); maxVal = bits[(turtleY * width) + turtleX]; maxValX = -1; for (y = startY; y <= endY; y++) { rowOffset = y * width; for (x = startX; x <= endX; x++) { thisVal = bits[rowOffset + x]; if (thisVal > maxVal) { maxValX = x; maxValY = y; maxVal = thisVal; } } } if (-1 === maxValX) { ret = radiansToDegrees(tH); } else { ret = degreesFromXy((maxValX - turtleX), (maxValY - turtleY)) + 90.0|0; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(8); interpreterProxy.pushFloat(ret); } function primitiveAddArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] + wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] + floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveAddScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] + intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] + floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveDivArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] / wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] / floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveDivScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (DIV(wordsRcvr[i], intArg)); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] / floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveMulArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] * wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] * floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveMulScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] * intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] * floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveSubArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] - wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] - floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveSubScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] - intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] - floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function radiansToDegrees(radians) { var deg; var degrees; degrees = radians / 0.0174532925199433; deg = 90.0 - degrees; if (!(deg > 0.0)) { deg += 360.0; } return deg; } function randomIntoFloatArray() { var factor; var floatArray; var floatArrayOop; var from; var index; var range; var size; var to; factor = interpreterProxy.stackFloatValue(0); floatArrayOop = interpreterProxy.stackValue(1); to = interpreterProxy.stackIntegerValue(2); from = interpreterProxy.stackIntegerValue(3); range = interpreterProxy.stackIntegerValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(floatArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(floatArrayOop); if (!((size >= to) && ((from >= 1) && (to >= from)))) { interpreterProxy.primitiveFail(); return null; } floatArray = floatArrayOop.wordsAsFloat32Array(); if (interpreterProxy.failed()) { return null; } for (index = from; index <= to; index++) { floatArray[index - 1] = (kedamaRandom2(range) * factor); } interpreterProxy.pop(5); } function randomIntoIntegerArray() { var factor; var from; var index; var integerArray; var integerArrayOop; var range; var size; var to; factor = interpreterProxy.stackFloatValue(0); integerArrayOop = interpreterProxy.stackValue(1); to = interpreterProxy.stackIntegerValue(2); from = interpreterProxy.stackIntegerValue(3); range = interpreterProxy.stackIntegerValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(integerArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(integerArrayOop); if (!((size >= to) && ((from >= 1) && (to >= from)))) { interpreterProxy.primitiveFail(); return null; } integerArray = integerArrayOop.words; if (interpreterProxy.failed()) { return null; } for (index = from; index <= to; index++) { integerArray[index - 1] = ((kedamaRandom2(range) * factor)|0); } interpreterProxy.pop(5); } function randomRange() { var range; var ret; range = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } ret = kedamaRandom2(range); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(2); interpreterProxy.pushInteger(ret); } function scalarGetAngleTo() { var fromX; var fromY; var r; var toX; var toY; var x; var y; fromY = interpreterProxy.stackFloatValue(0); fromX = interpreterProxy.stackFloatValue(1); toY = interpreterProxy.stackFloatValue(2); toX = interpreterProxy.stackFloatValue(3); if (interpreterProxy.failed()) { return null; } x = toX - fromX; y = toY - fromY; r = degreesFromXy(x, y); r += 90.0; if (r > 360.0) { r -= 360.0; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(5); interpreterProxy.pushFloat(r); } function scalarGetDistanceTo() { var fromX; var fromY; var r; var toX; var toY; var x; var y; fromY = interpreterProxy.stackFloatValue(0); fromX = interpreterProxy.stackFloatValue(1); toY = interpreterProxy.stackFloatValue(2); toX = interpreterProxy.stackFloatValue(3); if (interpreterProxy.failed()) { return null; } x = fromX - toX; y = fromY - toY; r = Math.sqrt((x * x) + (y * y)); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(5); interpreterProxy.pushFloat(r); } function scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(index, xArray, headingArray, val, destWidth, leftEdgeMode, rightEdgeMode) { var headingRadians; var newX; newX = val; if (newX < 0.0) { if (leftEdgeMode === 1) { /* wrap */ newX += destWidth; } if (leftEdgeMode === 2) { /* stick */ newX = 0.0; } if (leftEdgeMode === 3) { /* bounce */ newX = 0.0 - newX; headingRadians = headingArray[index]; if (headingRadians < 3.141592653589793) { headingArray[index] = (3.141592653589793 - headingRadians); } else { headingArray[index] = (9.42477796076938 - headingRadians); } } } if (newX >= destWidth) { if (rightEdgeMode === 1) { newX -= destWidth; } if (rightEdgeMode === 2) { newX = destWidth - 1.0e-6; } if (rightEdgeMode === 3) { newX = (destWidth - 1.0e-6) - (newX - destWidth); headingRadians = headingArray[index]; if (headingRadians < 3.141592653589793) { headingArray[index] = (3.141592653589793 - headingRadians); } else { headingArray[index] = (9.42477796076938 - headingRadians); } } } xArray[index] = newX; } function scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(index, yArray, headingArray, val, destHeight, topEdgeMode, bottomEdgeMode) { var newY; newY = val; if (newY < 0.0) { if (topEdgeMode === 1) { /* wrap */ newY += destHeight; } if (topEdgeMode === 2) { /* stick */ newY = 0.0; } if (topEdgeMode === 3) { /* bounce */ newY = 0.0 - newY; headingArray[index] = (6.283185307179586 - headingArray[index]); } } if (newY >= destHeight) { if (bottomEdgeMode === 1) { newY -= destHeight; } if (bottomEdgeMode === 2) { newY = destHeight - 1.0e-6; } if (bottomEdgeMode === 3) { newY = (destHeight - 1.0e-6) - (newY - destHeight); headingArray[index] = (6.283185307179586 - headingArray[index]); } } yArray[index] = newY; } function setHeadingArrayFrom() { var heading; var headingArray; var headingOop; var i; var isValVector; var resultArray; var resultOop; var size; resultOop = interpreterProxy.stackValue(0); headingOop = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(headingOop); if (resultOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(resultOop)) { if (SIZEOF(resultOop) !== size) { interpreterProxy.primitiveFail(); return null; } isValVector = true; } else { interpreterProxy.primitiveFail(); return null; } } headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { resultArray = resultOop.wordsAsFloat32Array(); } else { heading = interpreterProxy.floatValueOf(resultOop); heading = degreesToRadians(heading); } for (i = 0; i <= (size - 1); i++) { if (isValVector) { heading = resultArray[i]; heading = degreesToRadians(heading); } headingArray[i] = heading; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(2); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function setScalarHeading() { var heading; var headingArray; var headingOop; var index; heading = interpreterProxy.stackFloatValue(0); headingOop = interpreterProxy.stackValue(1); index = interpreterProxy.stackIntegerValue(2); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) < index) { interpreterProxy.primitiveFail(); return null; } headingArray = headingOop.wordsAsFloat32Array(); headingArray[index - 1] = degreesToRadians(heading); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(3); } function shutdownModule() { return true; } function turtleScalarSetX() { var destWidth; var headingArray; var headingOop; var leftEdgeMode; var rightEdgeMode; var size; var val; var xArray; var xIndex; var xOop; rightEdgeMode = interpreterProxy.stackIntegerValue(0); leftEdgeMode = interpreterProxy.stackIntegerValue(1); destWidth = interpreterProxy.stackFloatValue(2); val = interpreterProxy.stackFloatValue(3); headingOop = interpreterProxy.stackValue(4); xIndex = interpreterProxy.stackIntegerValue(5); xOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xOop); if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(xIndex - 1, xArray, headingArray, val, destWidth, leftEdgeMode, rightEdgeMode); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function turtleScalarSetY() { var bottomEdgeMode; var destHeight; var headingArray; var headingOop; var size; var topEdgeMode; var val; var yArray; var yIndex; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); destHeight = interpreterProxy.stackFloatValue(2); val = interpreterProxy.stackFloatValue(3); headingOop = interpreterProxy.stackValue(4); yIndex = interpreterProxy.stackIntegerValue(5); yOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(yOop); if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(yIndex - 1, yArray, headingArray, val, destHeight, topEdgeMode, bottomEdgeMode); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function turtlesSetX() { var destWidth; var headingArray; var headingOop; var i; var isValVector; var leftEdgeMode; var newX; var rightEdgeMode; var size; var val; var valArray; var valOop; var xArray; var xOop; rightEdgeMode = interpreterProxy.stackIntegerValue(0); leftEdgeMode = interpreterProxy.stackIntegerValue(1); destWidth = interpreterProxy.stackFloatValue(2); valOop = interpreterProxy.stackValue(3); headingOop = interpreterProxy.stackValue(4); xOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (valOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(valOop)) { isValVector = true; } else { interpreterProxy.primitiveFail(); return null; } } size = SIZEOF(xOop); if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (isValVector) { if (SIZEOF(valOop) !== size) { interpreterProxy.primitiveFail(); return null; } } xArray = xOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { valArray = valOop.wordsAsFloat32Array(); } else { val = interpreterProxy.floatValueOf(valOop); } for (i = 0; i <= (size - 1); i++) { if (isValVector) { newX = valArray[i]; } else { newX = val; } scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(i, xArray, headingArray, newX, destWidth, leftEdgeMode, rightEdgeMode); } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function turtlesSetY() { var bottomEdgeMode; var destHeight; var headingArray; var headingOop; var i; var isValVector; var newY; var size; var topEdgeMode; var val; var valArray; var valOop; var yArray; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); destHeight = interpreterProxy.stackFloatValue(2); valOop = interpreterProxy.stackValue(3); headingOop = interpreterProxy.stackValue(4); yOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (valOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(valOop)) { isValVector = true; } else { interpreterProxy.primitiveFail(); return null; } } size = SIZEOF(yOop); if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (isValVector) { if (SIZEOF(valOop) !== size) { interpreterProxy.primitiveFail(); return null; } } yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { valArray = valOop.wordsAsFloat32Array(); } else { val = interpreterProxy.floatValueOf(valOop); } for (i = 0; i <= (size - 1); i++) { if (isValVector) { newY = valArray[i]; } else { newY = val; } scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(i, yArray, headingArray, newY, destHeight, topEdgeMode, bottomEdgeMode); } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function vectorGetAngleTo() { var index; var isVector; var pX; var pXOop; var pY; var pYOop; var ppx; var ppy; var r; var result; var resultOop; var size; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; resultOop = interpreterProxy.stackValue(0); yArrayOop = interpreterProxy.stackValue(1); xArrayOop = interpreterProxy.stackValue(2); pYOop = interpreterProxy.stackValue(3); pXOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(resultOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(resultOop); if (size < 0) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(xArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (pXOop.isFloat) { if (pYOop.isFloat) { isVector = false; } else { interpreterProxy.primitiveFail(); return null; } } else { if (pYOop.isFloat) { interpreterProxy.primitiveFail(); return null; } else { isVector = true; } } if (isVector) { if (SIZEOF(pXOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(pYOop) !== size) { interpreterProxy.primitiveFail(); return null; } } result = resultOop.wordsAsFloat32Array(); xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); if (isVector) { pX = pXOop.wordsAsFloat32Array(); pY = pYOop.wordsAsFloat32Array(); } if (!isVector) { ppx = interpreterProxy.floatValueOf(pXOop); ppy = interpreterProxy.floatValueOf(pYOop); } for (index = 0; index <= (size - 1); index++) { if (isVector) { ppx = pX[index]; ppy = pY[index]; } x = ppx - xArray[index]; y = ppy - yArray[index]; r = degreesFromXy(x, y); r += 90.0; if (r > 360.0) { r -= 360.0; } result[index] = r; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); interpreterProxy.push(resultOop); } function vectorGetDistanceTo() { var index; var isVector; var pX; var pXOop; var pY; var pYOop; var ppx; var ppy; var result; var resultOop; var size; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; resultOop = interpreterProxy.stackValue(0); yArrayOop = interpreterProxy.stackValue(1); xArrayOop = interpreterProxy.stackValue(2); pYOop = interpreterProxy.stackValue(3); pXOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(resultOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(resultOop); if (size < 0) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(xArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (pXOop.isFloat) { if (pYOop.isFloat) { isVector = false; } else { interpreterProxy.primitiveFail(); return null; } } else { if (pYOop.isFloat) { interpreterProxy.primitiveFail(); return null; } else { isVector = true; } } if (isVector) { if (SIZEOF(pXOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(pYOop) !== size) { interpreterProxy.primitiveFail(); return null; } } result = resultOop.wordsAsFloat32Array(); xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); if (isVector) { pX = pXOop.wordsAsFloat32Array(); pY = pYOop.wordsAsFloat32Array(); } if (!isVector) { ppx = interpreterProxy.floatValueOf(pXOop); ppy = interpreterProxy.floatValueOf(pYOop); } for (index = 0; index <= (size - 1); index++) { if (isVector) { ppx = pX[index]; ppy = pY[index]; } x = ppx - xArray[index]; y = ppy - yArray[index]; result[index] = Math.sqrt((x * x) + (y * y)); } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); interpreterProxy.push(resultOop); } function zoomBitmap() { var bit; var dOrigin; var dst; var dstIndex; var dstSize; var dummy; var sHeight; var sOrigin; var sWidth; var src; var srcIndex; var srcOrigin; var srcSize; var sx; var sy; var xFactor; var y; var yFactor; yFactor = interpreterProxy.stackIntegerValue(0); xFactor = interpreterProxy.stackIntegerValue(1); sHeight = interpreterProxy.stackIntegerValue(2); sWidth = interpreterProxy.stackIntegerValue(3); dst = interpreterProxy.stackValue(4); src = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } srcSize = SIZEOF(src); dstSize = SIZEOF(dst); if ((sWidth * sHeight) !== srcSize) { interpreterProxy.primitiveFail(); return null; } if (((srcSize * xFactor) * yFactor) !== dstSize) { interpreterProxy.primitiveFail(); return null; } sOrigin = src.words; dOrigin = dst.words; srcIndex = 0; srcOrigin = 0; dstIndex = 0; for (sy = 0; sy <= (sHeight - 1); sy++) { for (y = 0; y <= (yFactor - 1); y++) { for (sx = 0; sx <= (sWidth - 1); sx++) { bit = sOrigin[srcIndex]; ++srcIndex; for (dummy = 0; dummy <= (xFactor - 1); dummy++) { dOrigin[dstIndex] = bit; ++dstIndex; } } srcIndex = srcOrigin; } srcOrigin += sWidth; srcIndex = srcOrigin; } interpreterProxy.pop(6); } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("KedamaPlugin", { makeMaskLog: makeMaskLog, vectorGetDistanceTo: vectorGetDistanceTo, getScalarHeading: getScalarHeading, shutdownModule: shutdownModule, primitiveAddScalar: primitiveAddScalar, primSetPixelsAtXY: primSetPixelsAtXY, turtleScalarSetX: turtleScalarSetX, primPixelAtXY: primPixelAtXY, primUpHill: primUpHill, primScalarForward: primScalarForward, primitiveDivArrays: primitiveDivArrays, getModuleName: getModuleName, primitiveSubArrays: primitiveSubArrays, scalarGetAngleTo: scalarGetAngleTo, randomRange: randomRange, setInterpreter: setInterpreter, kedamaSetRandomSeed: kedamaSetRandomSeed, drawTurtlesInArray: drawTurtlesInArray, turtleScalarSetY: turtleScalarSetY, randomIntoIntegerArray: randomIntoIntegerArray, getHeadingArrayInto: getHeadingArrayInto, makeTurtlesMap: makeTurtlesMap, setHeadingArrayFrom: setHeadingArrayFrom, turtlesSetX: turtlesSetX, setScalarHeading: setScalarHeading, makeMask: makeMask, primitiveDivScalar: primitiveDivScalar, primitiveSubScalar: primitiveSubScalar, primPixelsAtXY: primPixelsAtXY, vectorGetAngleTo: vectorGetAngleTo, primitiveMulArrays: primitiveMulArrays, primPixelAtXYPut: primPixelAtXYPut, zoomBitmap: zoomBitmap, initialiseModule: initialiseModule, primitiveAddArrays: primitiveAddArrays, scalarGetDistanceTo: scalarGetDistanceTo, turtlesSetY: turtlesSetY, randomIntoFloatArray: randomIntoFloatArray, primTurtlesForward: primTurtlesForward, primitiveMulScalar: primitiveMulScalar, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 13 November 2014 7:11:01 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.16 uuid: aa9fd3da-7901-4c62-ae34-c166f1492d3b from KedamaPlugin2 Kedama-Plugins-yo.1 uuid: 3fc7d691-0149-ba4d-a339-5d27cd44a2f8 */ (function KedamaPlugin2() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHIFT(a, b) { return b < 0 ? (b < -31 ? 0 : a >>> (0-b) ) : (b > 31 ? 0 : a << b); } /*** Variables ***/ var interpreterProxy = null; var kedamaRandomSeed = 0; var moduleName = "KedamaPlugin2 13 November 2014 (e)"; var randA = 0; var randM = 0; var randQ = 0; var randR = 0; function degreesFromXy(x, y) { var tanVal; var theta; if (x === 0.0) { if (y >= 0.0) { return 90.0; } else { return 270.0; } } else { tanVal = y / x; theta = Math.atan(tanVal); if (x >= 0.0) { if (y >= 0.0) { return theta / 0.0174532925199433; } else { return 360.0 + (theta / 0.0174532925199433); } } else { return 180.0 + (theta / 0.0174532925199433); } } } function degreesToRadians(degrees) { var deg; var headingRadians; var q; deg = 90.0 - degrees; q = deg / 360.0|0; if (deg < 0.0) { --q; } headingRadians = (deg - (q * 360.0)) * 0.0174532925199433; return headingRadians; } function drawTurtlesInArray() { var bitsIndex; var colorArray; var colorOop; var destBits; var destHeight; var destOop; var destWidth; var i; var size; var visible; var visibleArray; var visibleOop; var x; var xArray; var xOop; var y; var yArray; var yOop; visibleOop = interpreterProxy.stackValue(0); colorOop = interpreterProxy.stackValue(1); yOop = interpreterProxy.stackValue(2); xOop = interpreterProxy.stackValue(3); destHeight = interpreterProxy.stackIntegerValue(4); destWidth = interpreterProxy.stackIntegerValue(5); destOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(destOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(colorOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isBytes(visibleOop)) { interpreterProxy.primitiveFail(); return null; } if ((destHeight * destWidth) !== SIZEOF(destOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xOop); if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(colorOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(visibleOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); colorArray = colorOop.words; visibleArray = visibleOop.bytes; destBits = destOop.words; for (i = 0; i <= (size - 1); i++) { x = (xArray[i]|0); y = (yArray[i]|0); visible = visibleArray[i]; if ((visible !== 0) && (((x >= 0) && (y >= 0)) && ((x < destWidth) && (y < destHeight)))) { bitsIndex = (y * destWidth) + x; destBits[bitsIndex] = colorArray[i]; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function getHeadingArrayInto() { var heading; var headingArray; var headingOop; var i; var resultArray; var resultOop; var size; resultOop = interpreterProxy.stackValue(0); headingOop = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(resultOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(headingOop); if (SIZEOF(resultOop) !== size) { interpreterProxy.primitiveFail(); return null; } headingArray = headingOop.wordsAsFloat32Array(); resultArray = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (size - 1); i++) { heading = headingArray[i]; heading = heading / 0.0174532925199433; heading = 90.0 - heading; if (!(heading > 0.0)) { heading += 360.0; } resultArray[i] = heading; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(2); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function getScalarHeading() { var heading; var headingArray; var headingOop; var index; headingOop = interpreterProxy.stackValue(0); index = interpreterProxy.stackIntegerValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) < index) { interpreterProxy.primitiveFail(); return null; } headingArray = headingOop.wordsAsFloat32Array(); heading = headingArray[index - 1]; heading = radiansToDegrees(heading); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(3); interpreterProxy.pushFloat(heading); } function initialiseModule() { kedamaRandomSeed = 17; /* magic constant = 16807 */ randA = 16807; /* magic constant = 2147483647 */ randM = 2147483647; randQ = DIV(randM, randA); randR = MOD(randM, randA); } function kedamaRandom2(range) { var hi; var lo; var r; var v; var val; if (range < 0) { r = 0 - range; } else { r = range; } hi = DIV(kedamaRandomSeed, randQ); lo = MOD(kedamaRandomSeed, randQ); kedamaRandomSeed = (randA * lo) - (randR * hi); v = kedamaRandomSeed & 65535; val = (v * (r + 1)) >>> 16; if (range < 0) { return 0 - val; } else { return val; } } function kedamaSetRandomSeed() { var seed; seed = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } kedamaRandomSeed = seed & 65536; interpreterProxy.pop(1); } function makeMask() { var alpha; var dOrigin; var data; var dataBits; var dataSize; var highMask; var i; var mOrigin; var maskBits; var maskSize; var pixel; var shiftAmount; shiftAmount = interpreterProxy.stackIntegerValue(0); pixel = interpreterProxy.stackIntegerValue(1); maskBits = interpreterProxy.stackValue(2); dataBits = interpreterProxy.stackValue(3); if (interpreterProxy.failed()) { return null; } dataSize = SIZEOF(dataBits); maskSize = SIZEOF(maskBits); if (dataSize !== maskSize) { interpreterProxy.primitiveFail(); return null; } if (shiftAmount < -32) { interpreterProxy.primitiveFail(); return null; } if (shiftAmount > 8) { interpreterProxy.primitiveFail(); return null; } dOrigin = dataBits.words; mOrigin = maskBits.words; highMask = 4278190080; for (i = 0; i <= (dataSize - 1); i++) { data = dOrigin[i]; alpha = SHIFT(data, shiftAmount); if (alpha > 255) { alpha = 255; } if (alpha < 0) { alpha = 0; } mOrigin[i] = (((alpha << 24) & highMask) | pixel); } interpreterProxy.pop(4); } function makeMaskLog() { var alpha; var dOrigin; var data; var dataBits; var dataSize; var highMask; var i; var mOrigin; var maskBits; var maskSize; var max; var maxFirst; var maxLog; var maxOop; var pixel; maxOop = interpreterProxy.stackValue(0); pixel = interpreterProxy.stackIntegerValue(1); maskBits = interpreterProxy.stackValue(2); dataBits = interpreterProxy.stackValue(3); if (interpreterProxy.failed()) { return null; } maxFirst = maxOop.words; max = maxFirst[0]; if (interpreterProxy.failed()) { return null; } maxLog = Math.log(max); dataSize = SIZEOF(dataBits); maskSize = SIZEOF(maskBits); if (dataSize !== maskSize) { interpreterProxy.primitiveFail(); return null; } dOrigin = dataBits.words; mOrigin = maskBits.words; highMask = 4278190080; for (i = 0; i <= (dataSize - 1); i++) { data = dOrigin[i]; if (data === 0) { alpha = 0; } else { alpha = (((255.0 / maxLog) * Math.log(data))|0); } if (alpha > 255) { alpha = 255; } mOrigin[i] = (((alpha << 24) & highMask) | pixel); } interpreterProxy.pop(4); } function makeTurtlesMap() { var height; var index; var map; var mapIndex; var mapOop; var size; var whoArray; var whoOop; var width; var x; var xArray; var xOop; var y; var yArray; var yOop; height = interpreterProxy.stackIntegerValue(0); width = interpreterProxy.stackIntegerValue(1); yOop = interpreterProxy.stackValue(2); xOop = interpreterProxy.stackValue(3); whoOop = interpreterProxy.stackValue(4); mapOop = interpreterProxy.stackValue(5); if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(whoOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(mapOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(whoOop); if (SIZEOF(xOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(mapOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); whoArray = whoOop.words; map = mapOop.words; for (index = 0; index <= ((height * width) - 1); index++) { map[index] = 0; } for (index = 0; index <= (size - 1); index++) { x = xArray[index]; y = yArray[index]; mapIndex = (width * y) + x; if ((mapIndex >= 0) && (mapIndex < (height * width))) { map[mapIndex] = whoArray[index]; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primPixelAtXY() { var bits; var bitsOop; var height; var index; var ret; var width; var x; var xPos; var y; var yPos; height = interpreterProxy.stackIntegerValue(0); width = interpreterProxy.stackIntegerValue(1); yPos = interpreterProxy.stackFloatValue(2); xPos = interpreterProxy.stackFloatValue(3); bitsOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(bitsOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } x = xPos|0; y = yPos|0; bits = bitsOop.words; if ((((x >= 0) && (x < width)) && (y >= 0)) && (y < height)) { index = (y * width) + x; ret = bits[index]; } else { ret = 0; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); interpreterProxy.pushInteger(ret); } function primPixelAtXYPut() { var bits; var bitsOop; var height; var index; var v; var value; var width; var x; var xPos; var y; var yPos; height = interpreterProxy.stackIntegerValue(0); width = interpreterProxy.stackIntegerValue(1); value = interpreterProxy.stackIntegerValue(2); yPos = interpreterProxy.stackFloatValue(3); xPos = interpreterProxy.stackFloatValue(4); bitsOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(bitsOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } x = xPos|0; y = yPos|0; v = value; if (v > 1073741823) { v = 1073741823; } if (v < 0) { v = 0; } bits = bitsOop.words; if ((((x >= 0) && (x < width)) && (y >= 0)) && (y < height)) { index = (y * width) + x; bits[index] = v; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primPixelsAtXY() { var bits; var bitsHeight; var bitsIndex; var bitsOop; var bitsWidth; var destWords; var destWordsOop; var i; var size; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; destWordsOop = interpreterProxy.stackValue(0); bitsHeight = interpreterProxy.stackIntegerValue(1); bitsWidth = interpreterProxy.stackIntegerValue(2); bitsOop = interpreterProxy.stackValue(3); yArrayOop = interpreterProxy.stackValue(4); xArrayOop = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(destWordsOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if ((bitsHeight * bitsWidth) !== SIZEOF(bitsOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xArrayOop); if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(destWordsOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); destWords = destWordsOop.words; bits = bitsOop.words; for (i = 0; i <= (size - 1); i++) { x = (xArray[i]|0); y = (yArray[i]|0); if (((x >= 0) && (y >= 0)) && ((x < bitsWidth) && (y < bitsHeight))) { bitsIndex = (y * bitsWidth) + x; destWords[i] = bits[bitsIndex]; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); } function primScalarForward() { var bottomEdgeMode; var destHeight; var destWidth; var dist; var headingArray; var headingOop; var i; var index; var leftEdgeMode; var newX; var newY; var rightEdgeMode; var size; var topEdgeMode; var val; var xArray; var xOop; var yArray; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); rightEdgeMode = interpreterProxy.stackIntegerValue(2); leftEdgeMode = interpreterProxy.stackIntegerValue(3); destHeight = interpreterProxy.stackFloatValue(4); destWidth = interpreterProxy.stackFloatValue(5); val = interpreterProxy.stackFloatValue(6); headingOop = interpreterProxy.stackValue(7); yOop = interpreterProxy.stackValue(8); xOop = interpreterProxy.stackValue(9); index = interpreterProxy.stackIntegerValue(10); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xOop); if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); dist = val; i = index - 1; newX = xArray[i] + (dist * Math.cos(headingArray[i])); newY = yArray[i] - (dist * Math.sin(headingArray[i])); scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(i, xArray, headingArray, newX, destWidth, leftEdgeMode, rightEdgeMode); scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(i, yArray, headingArray, newY, destHeight, topEdgeMode, bottomEdgeMode); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(11); } function primSetPixelsAtXY() { var bits; var bitsHeight; var bitsIndex; var bitsOop; var bitsWidth; var floatsValue; var fv; var i; var intValue; var isValueInt; var isValueWordArray; var pArray; var pArrayOop; var size; var value; var valueOop; var wordsValue; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; valueOop = interpreterProxy.stackValue(0); bitsHeight = interpreterProxy.stackIntegerValue(1); bitsWidth = interpreterProxy.stackIntegerValue(2); bitsOop = interpreterProxy.stackValue(3); yArrayOop = interpreterProxy.stackValue(4); xArrayOop = interpreterProxy.stackValue(5); pArrayOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(pArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if ((bitsHeight * bitsWidth) !== SIZEOF(bitsOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xArrayOop); if (SIZEOF(pArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } pArray = pArrayOop.bytes; xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); isValueInt = typeof valueOop === "number"; if (isValueInt) { intValue = valueOop; value = intValue; } else { if (SIZEOF(valueOop) !== size) { interpreterProxy.primitiveFail(); return null; } isValueWordArray = interpreterProxy.isMemberOf(valueOop, "WordArray"); if (isValueWordArray) { wordsValue = valueOop.words; } else { floatsValue = valueOop.wordsAsFloat32Array(); } } bits = bitsOop.words; for (i = 0; i <= (size - 1); i++) { if (pArray[i] === 1) { x = (xArray[i]|0); y = (yArray[i]|0); if (((x >= 0) && (y >= 0)) && ((x < bitsWidth) && (y < bitsHeight))) { bitsIndex = (y * bitsWidth) + x; if (isValueInt) { bits[bitsIndex] = value; } else { if (isValueWordArray) { bits[bitsIndex] = wordsValue[i]; } else { fv = floatsValue[i]; bits[bitsIndex] = fv; } } } } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function primTurtlesForward() { var bottomEdgeMode; var destHeight; var destWidth; var dist; var headingArray; var headingOop; var i; var isValVector; var leftEdgeMode; var newX; var newY; var pArray; var pOop; var rightEdgeMode; var size; var topEdgeMode; var val; var valArray; var valOop; var xArray; var xOop; var yArray; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); rightEdgeMode = interpreterProxy.stackIntegerValue(2); leftEdgeMode = interpreterProxy.stackIntegerValue(3); destHeight = interpreterProxy.stackFloatValue(4); destWidth = interpreterProxy.stackFloatValue(5); valOop = interpreterProxy.stackValue(6); headingOop = interpreterProxy.stackValue(7); yOop = interpreterProxy.stackValue(8); xOop = interpreterProxy.stackValue(9); pOop = interpreterProxy.stackValue(10); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(pOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (valOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(valOop)) { isValVector = true; } else { interpreterProxy.primitiveFail(); return null; } } size = SIZEOF(xOop); if (SIZEOF(yOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(pOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (isValVector) { if (SIZEOF(valOop) !== size) { interpreterProxy.primitiveFail(); return null; } } pArray = pOop.bytes; xArray = xOop.wordsAsFloat32Array(); yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { valArray = valOop.wordsAsFloat32Array(); } else { val = interpreterProxy.floatValueOf(valOop); } for (i = 0; i <= (size - 1); i++) { if (pArray[i] === 1) { if (isValVector) { dist = valArray[i]; } else { dist = val; } newX = xArray[i] + (dist * Math.cos(headingArray[i])); newY = yArray[i] - (dist * Math.sin(headingArray[i])); scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(i, xArray, headingArray, newX, destWidth, leftEdgeMode, rightEdgeMode); scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(i, yArray, headingArray, newY, destHeight, topEdgeMode, bottomEdgeMode); } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(11); } function primUpHill() { var bits; var bitsOop; var endX; var endY; var height; var maxVal; var maxValX; var maxValY; var ret; var rowOffset; var sniffRange; var startX; var startY; var tH; var tX; var tY; var thisVal; var turtleX; var turtleY; var width; var x; var y; sniffRange = interpreterProxy.stackIntegerValue(0); height = interpreterProxy.stackIntegerValue(1); width = interpreterProxy.stackIntegerValue(2); bitsOop = interpreterProxy.stackValue(3); tH = interpreterProxy.stackFloatValue(4); tY = interpreterProxy.stackFloatValue(5); tX = interpreterProxy.stackFloatValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(bitsOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(bitsOop) !== (height * width)) { interpreterProxy.primitiveFail(); return null; } bits = bitsOop.words; turtleX = tX; turtleY = tY; turtleX = Math.max(turtleX, 0); turtleY = Math.max(turtleY, 0); turtleX = Math.min(turtleX, (width - 1)); turtleY = Math.min(turtleY, (height - 1)); startX = Math.max((turtleX - sniffRange), 0); endX = Math.min((turtleX + sniffRange), (width - 1)); startY = Math.max((turtleY - sniffRange), 0); endY = Math.min((turtleY + sniffRange), (height - 1)); maxVal = bits[(turtleY * width) + turtleX]; maxValX = -1; for (y = startY; y <= endY; y++) { rowOffset = y * width; for (x = startX; x <= endX; x++) { thisVal = bits[rowOffset + x]; if (thisVal > maxVal) { maxValX = x; maxValY = y; maxVal = thisVal; } } } if (-1 === maxValX) { ret = radiansToDegrees(tH); } else { ret = degreesFromXy((maxValX - turtleX), (maxValY - turtleY)) + 90.0|0; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(8); interpreterProxy.pushFloat(ret); } function primitiveAddArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] + wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] + floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveAddScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] + intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] + floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] + floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveAndByteArray() { var i; var length; var length1; var length2; var otherArray; var otherOop; var rcvrArray; var rcvrOop; otherOop = interpreterProxy.stackObjectValue(0); rcvrOop = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isBytes(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(otherOop)); if (interpreterProxy.failed()) { return null; } length1 = SIZEOF(rcvrOop); length2 = SIZEOF(otherOop); if (interpreterProxy.failed()) { return null; } length = length1; if (length1 > length2) { length = length2; } otherArray = otherOop.bytes; rcvrArray = rcvrOop.bytes; for (i = 0; i <= (length - 1); i++) { rcvrArray[i] = ((rcvrArray[i] + otherArray[i]) === 2); } interpreterProxy.pop(1); } function primitiveDivArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] / wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] / floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveDivScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (DIV(wordsRcvr[i], intArg)); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] / floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] / floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveEQArrays() { var argOop; var bytesResult; var floatsArg; var floatsRcvr; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] === wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] === floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] === wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] === floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveEQScalar() { var argOop; var bytesResult; var floatArg; var floatsRcvr; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] === intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] === floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] === intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] === floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveGEArrays() { var argOop; var bytesResult; var floatsArg; var floatsRcvr; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] >= wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] >= floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] >= wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] >= floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveGEScalar() { var argOop; var bytesResult; var floatArg; var floatsRcvr; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] >= intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] >= floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] >= intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] >= floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveGTArrays() { var argOop; var bytesResult; var floatsArg; var floatsRcvr; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] > wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] > floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] > wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] > floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveGTScalar() { var argOop; var bytesResult; var floatArg; var floatsRcvr; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] > intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] > floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] > intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] > floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveLEArrays() { var argOop; var bytesResult; var floatsArg; var floatsRcvr; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] <= wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] <= floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] <= wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] <= floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveLEScalar() { var argOop; var bytesResult; var floatArg; var floatsRcvr; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] <= intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] <= floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] <= intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] <= floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveLTArrays() { var argOop; var bytesResult; var floatsArg; var floatsRcvr; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] < wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] < floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] < wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] < floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveLTScalar() { var argOop; var bytesResult; var floatArg; var floatsRcvr; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] < intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] < floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] < intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] < floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveMulArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] * wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] * floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveMulScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] * intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] * floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] * floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveNEArrays() { var argOop; var bytesResult; var floatsArg; var floatsRcvr; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] !== wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] !== floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] !== wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] !== floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveNEScalar() { var argOop; var bytesResult; var floatArg; var floatsRcvr; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] !== intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (wordsRcvr[i] !== floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] !== intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); bytesResult = resultOop.bytes; for (i = 0; i <= (length - 1); i++) { bytesResult[i] = (floatsRcvr[i] !== floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveNotByteArray() { var i; var length; var rcvrArray; var rcvrOop; rcvrOop = interpreterProxy.stackValue(0); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isBytes(rcvrOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); if (interpreterProxy.failed()) { return null; } rcvrArray = rcvrOop.bytes; for (i = 0; i <= (length - 1); i++) { if (rcvrArray[i] === 0) { rcvrArray[i] = 1; } else { rcvrArray[i] = 0; } } } function primitiveOrByteArray() { var i; var length; var length1; var length2; var otherArray; var otherOop; var rcvrArray; var rcvrOop; otherOop = interpreterProxy.stackObjectValue(0); rcvrOop = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isBytes(rcvrOop)); interpreterProxy.success(interpreterProxy.isBytes(otherOop)); if (interpreterProxy.failed()) { return null; } length1 = SIZEOF(rcvrOop); length2 = SIZEOF(otherOop); if (interpreterProxy.failed()) { return null; } length = length1; if (length1 > length2) { length = length2; } otherArray = otherOop.bytes; rcvrArray = rcvrOop.bytes; for (i = 0; i <= (length - 1); i++) { rcvrArray[i] = ((rcvrArray[i] + otherArray[i]) > 0); } interpreterProxy.pop(1); } function primitivePredicateAtAllPutBoolean() { var i; var predicates; var predicatesOop; var rcvrOop; var val; var valOop; var values; var valuesOop; valOop = interpreterProxy.stackValue(0); rcvrOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (interpreterProxy.isIntegerValue(valOop)) { val = valOop; } else { val = interpreterProxy.booleanValueOf(valOop); } valuesOop = interpreterProxy.fetchPointerofObject(1, rcvrOop); predicatesOop = interpreterProxy.fetchPointerofObject(0, rcvrOop); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(predicatesOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isBytes(valuesOop)) { interpreterProxy.primitiveFail(); return null; } values = valuesOop.bytes; predicates = predicatesOop.bytes; for (i = 0; i <= (SIZEOF(valuesOop) - 1); i++) { if (predicates[i] === 1) { values[i] = val; } } interpreterProxy.pop(1); } function primitivePredicateAtAllPutColor() { var i; var predicates; var predicatesOop; var rcvrOop; var val; var values; var valuesOop; val = interpreterProxy.stackIntegerValue(0); rcvrOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } val = val | 4278190080; valuesOop = interpreterProxy.fetchPointerofObject(1, rcvrOop); predicatesOop = interpreterProxy.fetchPointerofObject(0, rcvrOop); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(predicatesOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(valuesOop)) { interpreterProxy.primitiveFail(); return null; } values = valuesOop.words; predicates = predicatesOop.bytes; for (i = 0; i <= (SIZEOF(valuesOop) - 1); i++) { if (predicates[i] === 1) { values[i] = val; } } interpreterProxy.pop(1); } function primitivePredicateAtAllPutNumber() { var i; var predicates; var predicatesOop; var rcvrOop; var val; var values; var valuesOop; val = interpreterProxy.stackFloatValue(0); rcvrOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } valuesOop = interpreterProxy.fetchPointerofObject(1, rcvrOop); predicatesOop = interpreterProxy.fetchPointerofObject(0, rcvrOop); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(predicatesOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(valuesOop)) { interpreterProxy.primitiveFail(); return null; } values = valuesOop.wordsAsFloat32Array(); predicates = predicatesOop.bytes; for (i = 0; i <= (SIZEOF(valuesOop) - 1); i++) { if (predicates[i] === 1) { values[i] = val; } } interpreterProxy.pop(1); } function primitivePredicateAtAllPutObject() { var i; var predicates; var predicatesOop; var rcvrOop; var valOop; var values; var valuesOop; valOop = interpreterProxy.stackValue(0); rcvrOop = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } valuesOop = interpreterProxy.fetchPointerofObject(1, rcvrOop); predicatesOop = interpreterProxy.fetchPointerofObject(0, rcvrOop); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(predicatesOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isPointers(valuesOop)) { interpreterProxy.primitiveFail(); return null; } values = valuesOop.wordsAsInt32Array(); predicates = predicatesOop.bytes; for (i = 0; i <= (SIZEOF(valuesOop) - 1); i++) { if (predicates[i] === 1) { values[i] = valOop; } } interpreterProxy.pop(1); } function primitivePredicateReplaceBytes() { var i; var predicates; var predicatesOop; var predicatesSize; var rcvrOop; var repOop; var repStart; var replacement; var replacementSize; var start; var stop; var values; var valuesOop; var valuesSize; repStart = interpreterProxy.stackIntegerValue(0); repOop = interpreterProxy.stackObjectValue(1); stop = interpreterProxy.stackIntegerValue(2); start = interpreterProxy.stackIntegerValue(3); rcvrOop = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return null; } valuesOop = interpreterProxy.fetchPointerofObject(1, rcvrOop); predicatesOop = interpreterProxy.fetchPointerofObject(0, rcvrOop); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(predicatesOop)) { interpreterProxy.primitiveFail(); return null; } if (!(interpreterProxy.isBytes(valuesOop) && (interpreterProxy.isBytes(repOop)))) { interpreterProxy.primitiveFail(); return null; } values = valuesOop.bytes; predicates = predicatesOop.bytes; replacement = repOop.bytes; valuesSize = SIZEOF(valuesOop); predicatesSize = SIZEOF(predicatesOop); replacementSize = SIZEOF(repOop); if (start > stop) { interpreterProxy.primitiveFail(); return null; } if (start < 1) { interpreterProxy.primitiveFail(); return null; } if (start > valuesSize) { interpreterProxy.primitiveFail(); return null; } if (start > predicatesSize) { interpreterProxy.primitiveFail(); return null; } if (stop > valuesSize) { interpreterProxy.primitiveFail(); return null; } if (stop > predicatesSize) { interpreterProxy.primitiveFail(); return null; } if (repStart < 1) { interpreterProxy.primitiveFail(); return null; } if (repStart > replacementSize) { interpreterProxy.primitiveFail(); return null; } if (((replacementSize - repStart) + 1) < ((stop - start) + 1)) { interpreterProxy.primitiveFail(); return null; } for (i = (start - 1); i <= (stop - 1); i++) { if (predicates[i] === 1) { values[i] = replacement[(repStart + i) - start]; } } interpreterProxy.pop(4); } function primitivePredicateReplaceWords() { var floatReplacement; var floatValues; var fv; var i; var predicates; var predicatesOop; var predicatesSize; var rIsFloat; var rcvrOop; var repOop; var repStart; var replacement; var replacementSize; var start; var stop; var vIsFloat; var values; var valuesOop; var valuesSize; repStart = interpreterProxy.stackIntegerValue(0); repOop = interpreterProxy.stackObjectValue(1); stop = interpreterProxy.stackIntegerValue(2); start = interpreterProxy.stackIntegerValue(3); rcvrOop = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return null; } valuesOop = interpreterProxy.fetchPointerofObject(1, rcvrOop); predicatesOop = interpreterProxy.fetchPointerofObject(0, rcvrOop); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(predicatesOop)) { interpreterProxy.primitiveFail(); return null; } if (!((interpreterProxy.isWords(valuesOop) && (interpreterProxy.isWords(repOop))) || (interpreterProxy.isPointers(valuesOop) && (interpreterProxy.isPointers(repOop))))) { interpreterProxy.primitiveFail(); return null; } predicates = predicatesOop.bytes; valuesSize = SIZEOF(valuesOop); predicatesSize = SIZEOF(predicatesOop); replacementSize = SIZEOF(repOop); if (start > stop) { interpreterProxy.primitiveFail(); return null; } if (start < 1) { interpreterProxy.primitiveFail(); return null; } if (start > valuesSize) { interpreterProxy.primitiveFail(); return null; } if (start > predicatesSize) { interpreterProxy.primitiveFail(); return null; } if (stop > valuesSize) { interpreterProxy.primitiveFail(); return null; } if (stop > predicatesSize) { interpreterProxy.primitiveFail(); return null; } if (repStart < 1) { interpreterProxy.primitiveFail(); return null; } if (repStart > replacementSize) { interpreterProxy.primitiveFail(); return null; } if (((replacementSize - repStart) + 1) < ((stop - start) + 1)) { interpreterProxy.primitiveFail(); return null; } vIsFloat = interpreterProxy.isMemberOf(valuesOop, "KedamaFloatArray"); rIsFloat = interpreterProxy.isMemberOf(repOop, "KedamaFloatArray"); if (vIsFloat && (rIsFloat)) { floatValues = valuesOop.wordsAsFloat32Array(); floatReplacement = repOop.wordsAsFloat32Array(); for (i = (start - 1); i <= (stop - 1); i++) { if (predicates[i] === 1) { floatValues[i] = floatReplacement[(repStart + i) - start]; } } } if (vIsFloat && (!rIsFloat)) { floatValues = valuesOop.wordsAsFloat32Array(); replacement = repOop.words; for (i = (start - 1); i <= (stop - 1); i++) { if (predicates[i] === 1) { floatValues[i] = replacement[(repStart + i) - start]; } } } if (!vIsFloat && (rIsFloat)) { values = valuesOop.words; floatReplacement = repOop.wordsAsFloat32Array(); for (i = (start - 1); i <= (stop - 1); i++) { if (predicates[i] === 1) { fv = (floatReplacement[(repStart + i) - start]>>>0); values[i] = fv; } } } if (!vIsFloat && (!rIsFloat)) { values = valuesOop.words; replacement = repOop.words; for (i = (start - 1); i <= (stop - 1); i++) { if (predicates[i] === 1) { values[i] = replacement[(repStart + i) - start]; } } } interpreterProxy.pop(4); } function primitiveRemArrays() { var argOop; var floatArg; var floatRcvr; var floatResult; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordArg; var wordRcvr; var wordResult; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordRcvr = wordsRcvr[i]; wordArg = wordsArg[i]; /* In this primitive, words are supposed to be unsigned. */ wordResult = MOD(wordRcvr, wordArg); wordsResult[i] = wordResult; } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { wordRcvr = wordsRcvr[i]; floatArg = floatsArg[i]; floatResult = wordRcvr / floatArg; floatResult = Math.floor(floatResult); floatsResult[i] = (wordRcvr - (floatResult * floatArg)); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatRcvr = floatsRcvr[i]; wordArg = wordsArg[i]; floatResult = floatRcvr / wordArg; floatResult = Math.floor(floatResult); floatsResult[i] = (floatRcvr - (floatResult * wordArg)); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatRcvr = floatsRcvr[i]; floatArg = floatsArg[i]; floatResult = floatRcvr / floatArg; floatResult = Math.floor(floatResult); floatsResult[i] = (floatRcvr - (floatResult * floatArg)); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveRemScalar() { var argOop; var floatArg; var floatRcvr; var floatResult; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordRcvr; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (MOD(wordsRcvr[i], intArg)); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { wordRcvr = wordsRcvr[i]; floatResult = wordRcvr / floatArg; floatResult = Math.floor(floatResult); floatsResult[i] = (wordRcvr - (floatResult * floatArg)); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatRcvr = floatsRcvr[i]; floatResult = floatRcvr / intArg; floatResult = Math.floor(floatResult); floatsResult[i] = (floatRcvr - (floatResult * intArg)); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatRcvr = floatsRcvr[i]; floatResult = floatRcvr / floatArg; floatResult = Math.floor(floatResult); floatsResult[i] = (floatRcvr - (floatResult * floatArg)); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveSubArrays() { var argOop; var floatsArg; var floatsRcvr; var floatsResult; var i; var isArgWords; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsArg; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackObjectValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(argOop)); interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(argOop); interpreterProxy.success(length === SIZEOF(rcvrOop)); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgWords = interpreterProxy.isMemberOf(argOop, "WordArray"); isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgWords && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgWords) { wordsRcvr = rcvrOop.words; wordsArg = argOop.words; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] - wordsArg[i]); } } else { wordsRcvr = rcvrOop.words; floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] - floatsArg[i]); } } } else { if (isArgWords) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); wordsArg = argOop.words; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - wordsArg[i]); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatsArg = argOop.wordsAsFloat32Array(); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - floatsArg[i]); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function primitiveSubScalar() { var argOop; var floatArg; var floatsRcvr; var floatsResult; var i; var intArg; var isArgInt; var isRcvrWords; var length; var rcvrOop; var resultOop; var wordsRcvr; var wordsResult; resultOop = interpreterProxy.stackObjectValue(0); argOop = interpreterProxy.stackValue(1); rcvrOop = interpreterProxy.stackObjectValue(2); if (interpreterProxy.failed()) { return null; } interpreterProxy.success(interpreterProxy.isWords(rcvrOop)); interpreterProxy.success(interpreterProxy.isWords(resultOop)); if (interpreterProxy.failed()) { return null; } length = SIZEOF(rcvrOop); interpreterProxy.success(length === SIZEOF(resultOop)); if (interpreterProxy.failed()) { return null; } isArgInt = typeof argOop === "number"; isRcvrWords = interpreterProxy.isMemberOf(rcvrOop, "WordArray"); if (isArgInt && isRcvrWords) { if (!interpreterProxy.isMemberOf(resultOop, "WordArray")) { interpreterProxy.primitiveFail(); return null; } } else { if (!interpreterProxy.isMemberOf(resultOop, "KedamaFloatArray")) { interpreterProxy.primitiveFail(); return null; } } if (isRcvrWords) { if (isArgInt) { wordsRcvr = rcvrOop.words; intArg = argOop; wordsResult = resultOop.words; for (i = 0; i <= (length - 1); i++) { wordsResult[i] = (wordsRcvr[i] - intArg); } } else { wordsRcvr = rcvrOop.words; floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (wordsRcvr[i] - floatArg); } } } else { if (isArgInt) { floatsRcvr = rcvrOop.wordsAsFloat32Array(); intArg = argOop; floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - intArg); } } else { floatsRcvr = rcvrOop.wordsAsFloat32Array(); floatArg = interpreterProxy.floatValueOf(argOop); floatsResult = resultOop.wordsAsFloat32Array(); for (i = 0; i <= (length - 1); i++) { floatsResult[i] = (floatsRcvr[i] - floatArg); } } } interpreterProxy.pop(4); interpreterProxy.push(resultOop); } function radiansToDegrees(radians) { var deg; var degrees; degrees = radians / 0.0174532925199433; deg = 90.0 - degrees; if (!(deg > 0.0)) { deg += 360.0; } return deg; } function randomIntoFloatArray() { var factor; var floatArray; var floatArrayOop; var from; var index; var range; var size; var to; factor = interpreterProxy.stackFloatValue(0); floatArrayOop = interpreterProxy.stackValue(1); to = interpreterProxy.stackIntegerValue(2); from = interpreterProxy.stackIntegerValue(3); range = interpreterProxy.stackIntegerValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(floatArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(floatArrayOop); if (!((size >= to) && ((from >= 1) && (to >= from)))) { interpreterProxy.primitiveFail(); return null; } floatArray = floatArrayOop.wordsAsFloat32Array(); if (interpreterProxy.failed()) { return null; } for (index = from; index <= to; index++) { floatArray[index - 1] = (kedamaRandom2(range) * factor); } interpreterProxy.pop(5); } function randomIntoIntegerArray() { var factor; var from; var index; var integerArray; var integerArrayOop; var range; var size; var to; factor = interpreterProxy.stackFloatValue(0); integerArrayOop = interpreterProxy.stackValue(1); to = interpreterProxy.stackIntegerValue(2); from = interpreterProxy.stackIntegerValue(3); range = interpreterProxy.stackIntegerValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(integerArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(integerArrayOop); if (!((size >= to) && ((from >= 1) && (to >= from)))) { interpreterProxy.primitiveFail(); return null; } integerArray = integerArrayOop.words; if (interpreterProxy.failed()) { return null; } for (index = from; index <= to; index++) { integerArray[index - 1] = ((kedamaRandom2(range) * factor)|0); } interpreterProxy.pop(5); } function randomRange() { var range; var ret; range = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } ret = kedamaRandom2(range); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(2); interpreterProxy.pushInteger(ret); } function scalarGetAngleTo() { var fromX; var fromY; var r; var toX; var toY; var x; var y; fromY = interpreterProxy.stackFloatValue(0); fromX = interpreterProxy.stackFloatValue(1); toY = interpreterProxy.stackFloatValue(2); toX = interpreterProxy.stackFloatValue(3); if (interpreterProxy.failed()) { return null; } x = toX - fromX; y = toY - fromY; r = degreesFromXy(x, y); r += 90.0; if (r > 360.0) { r -= 360.0; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(5); interpreterProxy.pushFloat(r); } function scalarGetDistanceTo() { var fromX; var fromY; var r; var toX; var toY; var x; var y; fromY = interpreterProxy.stackFloatValue(0); fromX = interpreterProxy.stackFloatValue(1); toY = interpreterProxy.stackFloatValue(2); toX = interpreterProxy.stackFloatValue(3); if (interpreterProxy.failed()) { return null; } x = fromX - toX; y = fromY - toY; r = Math.sqrt((x * x) + (y * y)); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(5); interpreterProxy.pushFloat(r); } function scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(index, xArray, headingArray, val, destWidth, leftEdgeMode, rightEdgeMode) { var headingRadians; var newX; newX = val; if (newX < 0.0) { if (leftEdgeMode === 1) { /* wrap */ newX += destWidth; } if (leftEdgeMode === 2) { /* stick */ newX = 0.0; } if (leftEdgeMode === 3) { /* bounce */ newX = 0.0 - newX; headingRadians = headingArray[index]; if (headingRadians < 3.141592653589793) { headingArray[index] = (3.141592653589793 - headingRadians); } else { headingArray[index] = (9.42477796076938 - headingRadians); } } } if (newX >= destWidth) { if (rightEdgeMode === 1) { newX -= destWidth; } if (rightEdgeMode === 2) { newX = destWidth - 1.0e-6; } if (rightEdgeMode === 3) { newX = (destWidth - 1.0e-6) - (newX - destWidth); headingRadians = headingArray[index]; if (headingRadians < 3.141592653589793) { headingArray[index] = (3.141592653589793 - headingRadians); } else { headingArray[index] = (9.42477796076938 - headingRadians); } } } xArray[index] = newX; } function scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(index, yArray, headingArray, val, destHeight, topEdgeMode, bottomEdgeMode) { var newY; newY = val; if (newY < 0.0) { if (topEdgeMode === 1) { /* wrap */ newY += destHeight; } if (topEdgeMode === 2) { /* stick */ newY = 0.0; } if (topEdgeMode === 3) { /* bounce */ newY = 0.0 - newY; headingArray[index] = (6.283185307179586 - headingArray[index]); } } if (newY >= destHeight) { if (bottomEdgeMode === 1) { newY -= destHeight; } if (bottomEdgeMode === 2) { newY = destHeight - 1.0e-6; } if (bottomEdgeMode === 3) { newY = (destHeight - 1.0e-6) - (newY - destHeight); headingArray[index] = (6.283185307179586 - headingArray[index]); } } yArray[index] = newY; } function setHeadingArrayFrom() { var heading; var headingArray; var headingOop; var i; var isValVector; var pArray; var pOop; var resultArray; var resultOop; var size; resultOop = interpreterProxy.stackValue(0); headingOop = interpreterProxy.stackValue(1); pOop = interpreterProxy.stackValue(2); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(pOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(headingOop); if (resultOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(resultOop)) { if (SIZEOF(resultOop) !== size) { interpreterProxy.primitiveFail(); return null; } isValVector = true; } else { interpreterProxy.primitiveFail(); return null; } } pArray = pOop.bytes; headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { resultArray = resultOop.wordsAsFloat32Array(); } else { heading = interpreterProxy.floatValueOf(resultOop); heading = degreesToRadians(heading); } for (i = 0; i <= (size - 1); i++) { if (pArray[i] === 1) { if (isValVector) { heading = resultArray[i]; heading = degreesToRadians(heading); } headingArray[i] = heading; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(3); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function setScalarHeading() { var heading; var headingArray; var headingOop; var index; heading = interpreterProxy.stackFloatValue(0); headingOop = interpreterProxy.stackValue(1); index = interpreterProxy.stackIntegerValue(2); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) < index) { interpreterProxy.primitiveFail(); return null; } headingArray = headingOop.wordsAsFloat32Array(); headingArray[index - 1] = degreesToRadians(heading); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(3); } function shutdownModule() { return true; } function turtleScalarSetX() { var destWidth; var headingArray; var headingOop; var leftEdgeMode; var rightEdgeMode; var size; var val; var xArray; var xIndex; var xOop; rightEdgeMode = interpreterProxy.stackIntegerValue(0); leftEdgeMode = interpreterProxy.stackIntegerValue(1); destWidth = interpreterProxy.stackFloatValue(2); val = interpreterProxy.stackFloatValue(3); headingOop = interpreterProxy.stackValue(4); xIndex = interpreterProxy.stackIntegerValue(5); xOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(xOop); if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } xArray = xOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(xIndex - 1, xArray, headingArray, val, destWidth, leftEdgeMode, rightEdgeMode); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function turtleScalarSetY() { var bottomEdgeMode; var destHeight; var headingArray; var headingOop; var size; var topEdgeMode; var val; var yArray; var yIndex; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); destHeight = interpreterProxy.stackFloatValue(2); val = interpreterProxy.stackFloatValue(3); headingOop = interpreterProxy.stackValue(4); yIndex = interpreterProxy.stackIntegerValue(5); yOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(yOop); if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(yIndex - 1, yArray, headingArray, val, destHeight, topEdgeMode, bottomEdgeMode); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function turtlesSetX() { var destWidth; var headingArray; var headingOop; var i; var isValVector; var isWordVector; var leftEdgeMode; var newX; var pArray; var pOop; var rightEdgeMode; var size; var val; var valArray; var valOop; var wordValArray; var xArray; var xOop; rightEdgeMode = interpreterProxy.stackIntegerValue(0); leftEdgeMode = interpreterProxy.stackIntegerValue(1); destWidth = interpreterProxy.stackFloatValue(2); valOop = interpreterProxy.stackValue(3); headingOop = interpreterProxy.stackValue(4); xOop = interpreterProxy.stackValue(5); pOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(pOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (valOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(valOop)) { isValVector = true; isWordVector = interpreterProxy.isMemberOf(valOop, "WordArray"); } else { interpreterProxy.primitiveFail(); return null; } } size = SIZEOF(xOop); if (SIZEOF(pOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (isValVector) { if (SIZEOF(valOop) !== size) { interpreterProxy.primitiveFail(); return null; } } pArray = pOop.bytes; xArray = xOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { if (isWordVector) { wordValArray = valOop.words; } else { valArray = valOop.wordsAsFloat32Array(); } } else { val = interpreterProxy.floatValueOf(valOop); } for (i = 0; i <= (size - 1); i++) { if (pArray[i] === 1) { if (isValVector) { if (isWordVector) { newX = wordValArray[i]; } else { newX = valArray[i]; } } else { newX = val; } scalarXAtxArrayheadingArrayvaluedestWidthleftEdgeModerightEdgeMode(i, xArray, headingArray, newX, destWidth, leftEdgeMode, rightEdgeMode); } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function turtlesSetY() { var bottomEdgeMode; var destHeight; var headingArray; var headingOop; var i; var isValVector; var isWordVector; var newY; var pArray; var pOop; var size; var topEdgeMode; var val; var valArray; var valOop; var wordValArray; var yArray; var yOop; bottomEdgeMode = interpreterProxy.stackIntegerValue(0); topEdgeMode = interpreterProxy.stackIntegerValue(1); destHeight = interpreterProxy.stackFloatValue(2); valOop = interpreterProxy.stackValue(3); headingOop = interpreterProxy.stackValue(4); yOop = interpreterProxy.stackValue(5); pOop = interpreterProxy.stackValue(6); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isBytes(pOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(headingOop)) { interpreterProxy.primitiveFail(); return null; } if (valOop.isFloat) { isValVector = false; } else { if (interpreterProxy.isWords(valOop)) { isValVector = true; isWordVector = interpreterProxy.isMemberOf(valOop, "WordArray"); } else { interpreterProxy.primitiveFail(); return null; } } size = SIZEOF(yOop); if (SIZEOF(pOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(headingOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (isValVector) { if (SIZEOF(valOop) !== size) { interpreterProxy.primitiveFail(); return null; } } pArray = pOop.bytes; yArray = yOop.wordsAsFloat32Array(); headingArray = headingOop.wordsAsFloat32Array(); if (isValVector) { if (isWordVector) { wordValArray = valOop.words; } else { valArray = valOop.wordsAsFloat32Array(); } } else { val = interpreterProxy.floatValueOf(valOop); } for (i = 0; i <= (size - 1); i++) { if (pArray[i] === 1) { if (isValVector) { if (isWordVector) { newY = wordValArray[i]; } else { newY = valArray[i]; } } else { newY = val; } scalarYAtyArrayheadingArrayvaluedestHeighttopEdgeModebottomEdgeMode(i, yArray, headingArray, newY, destHeight, topEdgeMode, bottomEdgeMode); } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(7); } function vectorGetAngleTo() { var index; var isVector; var pX; var pXOop; var pY; var pYOop; var ppx; var ppy; var r; var result; var resultOop; var size; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; resultOop = interpreterProxy.stackValue(0); yArrayOop = interpreterProxy.stackValue(1); xArrayOop = interpreterProxy.stackValue(2); pYOop = interpreterProxy.stackValue(3); pXOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(resultOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(resultOop); if (size < 0) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(xArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (pXOop.isFloat) { if (pYOop.isFloat) { isVector = false; } else { interpreterProxy.primitiveFail(); return null; } } else { if (pYOop.isFloat) { interpreterProxy.primitiveFail(); return null; } else { isVector = true; } } if (isVector) { if (SIZEOF(pXOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(pYOop) !== size) { interpreterProxy.primitiveFail(); return null; } } result = resultOop.wordsAsFloat32Array(); xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); if (isVector) { pX = pXOop.wordsAsFloat32Array(); pY = pYOop.wordsAsFloat32Array(); } if (!isVector) { ppx = interpreterProxy.floatValueOf(pXOop); ppy = interpreterProxy.floatValueOf(pYOop); } for (index = 0; index <= (size - 1); index++) { if (isVector) { ppx = pX[index]; ppy = pY[index]; } x = ppx - xArray[index]; y = ppy - yArray[index]; r = degreesFromXy(x, y); r += 90.0; if (r > 360.0) { r -= 360.0; } result[index] = r; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); interpreterProxy.push(resultOop); } function vectorGetDistanceTo() { var index; var isVector; var pX; var pXOop; var pY; var pYOop; var ppx; var ppy; var result; var resultOop; var size; var x; var xArray; var xArrayOop; var y; var yArray; var yArrayOop; resultOop = interpreterProxy.stackValue(0); yArrayOop = interpreterProxy.stackValue(1); xArrayOop = interpreterProxy.stackValue(2); pYOop = interpreterProxy.stackValue(3); pXOop = interpreterProxy.stackValue(4); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(resultOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(xArrayOop)) { interpreterProxy.primitiveFail(); return null; } if (!interpreterProxy.isWords(yArrayOop)) { interpreterProxy.primitiveFail(); return null; } size = SIZEOF(resultOop); if (size < 0) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(xArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(yArrayOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (pXOop.isFloat) { if (pYOop.isFloat) { isVector = false; } else { interpreterProxy.primitiveFail(); return null; } } else { if (pYOop.isFloat) { interpreterProxy.primitiveFail(); return null; } else { isVector = true; } } if (isVector) { if (SIZEOF(pXOop) !== size) { interpreterProxy.primitiveFail(); return null; } if (SIZEOF(pYOop) !== size) { interpreterProxy.primitiveFail(); return null; } } result = resultOop.wordsAsFloat32Array(); xArray = xArrayOop.wordsAsFloat32Array(); yArray = yArrayOop.wordsAsFloat32Array(); if (isVector) { pX = pXOop.wordsAsFloat32Array(); pY = pYOop.wordsAsFloat32Array(); } if (!isVector) { ppx = interpreterProxy.floatValueOf(pXOop); ppy = interpreterProxy.floatValueOf(pYOop); } for (index = 0; index <= (size - 1); index++) { if (isVector) { ppx = pX[index]; ppy = pY[index]; } x = ppx - xArray[index]; y = ppy - yArray[index]; result[index] = Math.sqrt((x * x) + (y * y)); } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(6); interpreterProxy.push(resultOop); } function zoomBitmap() { var bit; var dOrigin; var dst; var dstIndex; var dstSize; var dummy; var sHeight; var sOrigin; var sWidth; var src; var srcIndex; var srcOrigin; var srcSize; var sx; var sy; var xFactor; var y; var yFactor; yFactor = interpreterProxy.stackIntegerValue(0); xFactor = interpreterProxy.stackIntegerValue(1); sHeight = interpreterProxy.stackIntegerValue(2); sWidth = interpreterProxy.stackIntegerValue(3); dst = interpreterProxy.stackValue(4); src = interpreterProxy.stackValue(5); if (interpreterProxy.failed()) { return null; } srcSize = SIZEOF(src); dstSize = SIZEOF(dst); if ((sWidth * sHeight) !== srcSize) { interpreterProxy.primitiveFail(); return null; } if (((srcSize * xFactor) * yFactor) !== dstSize) { interpreterProxy.primitiveFail(); return null; } sOrigin = src.words; dOrigin = dst.words; srcIndex = 0; srcOrigin = 0; dstIndex = 0; for (sy = 0; sy <= (sHeight - 1); sy++) { for (y = 0; y <= (yFactor - 1); y++) { for (sx = 0; sx <= (sWidth - 1); sx++) { bit = sOrigin[srcIndex]; ++srcIndex; for (dummy = 0; dummy <= (xFactor - 1); dummy++) { dOrigin[dstIndex] = bit; ++dstIndex; } } srcIndex = srcOrigin; } srcOrigin += sWidth; srcIndex = srcOrigin; } interpreterProxy.pop(6); } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("KedamaPlugin2", { primitiveAddArrays: primitiveAddArrays, getModuleName: getModuleName, primitiveMulArrays: primitiveMulArrays, drawTurtlesInArray: drawTurtlesInArray, primitiveGTScalar: primitiveGTScalar, setScalarHeading: setScalarHeading, primitiveSubScalar: primitiveSubScalar, turtleScalarSetY: turtleScalarSetY, vectorGetAngleTo: vectorGetAngleTo, primitiveRemArrays: primitiveRemArrays, primitiveLTArrays: primitiveLTArrays, primitiveAddScalar: primitiveAddScalar, primPixelsAtXY: primPixelsAtXY, primitiveMulScalar: primitiveMulScalar, primSetPixelsAtXY: primSetPixelsAtXY, setHeadingArrayFrom: setHeadingArrayFrom, primPixelAtXYPut: primPixelAtXYPut, makeMaskLog: makeMaskLog, primitiveLTScalar: primitiveLTScalar, scalarGetAngleTo: scalarGetAngleTo, primitiveOrByteArray: primitiveOrByteArray, primitiveLEArrays: primitiveLEArrays, primitiveRemScalar: primitiveRemScalar, getHeadingArrayInto: getHeadingArrayInto, turtlesSetX: turtlesSetX, primitivePredicateReplaceBytes: primitivePredicateReplaceBytes, primitiveDivArrays: primitiveDivArrays, makeMask: makeMask, primitiveLEScalar: primitiveLEScalar, kedamaSetRandomSeed: kedamaSetRandomSeed, randomIntoIntegerArray: randomIntoIntegerArray, setInterpreter: setInterpreter, primitivePredicateAtAllPutColor: primitivePredicateAtAllPutColor, makeTurtlesMap: makeTurtlesMap, randomIntoFloatArray: randomIntoFloatArray, primUpHill: primUpHill, shutdownModule: shutdownModule, primitiveDivScalar: primitiveDivScalar, primitiveGEArrays: primitiveGEArrays, primitiveNotByteArray: primitiveNotByteArray, randomRange: randomRange, initialiseModule: initialiseModule, getScalarHeading: getScalarHeading, primPixelAtXY: primPixelAtXY, primitivePredicateAtAllPutNumber: primitivePredicateAtAllPutNumber, primitiveEQArrays: primitiveEQArrays, primitiveNEArrays: primitiveNEArrays, primScalarForward: primScalarForward, vectorGetDistanceTo: vectorGetDistanceTo, turtlesSetY: turtlesSetY, turtleScalarSetX: turtleScalarSetX, primTurtlesForward: primTurtlesForward, primitiveGEScalar: primitiveGEScalar, primitiveAndByteArray: primitiveAndByteArray, primitivePredicateReplaceWords: primitivePredicateReplaceWords, primitiveGTArrays: primitiveGTArrays, primitivePredicateAtAllPutObject: primitivePredicateAtAllPutObject, primitiveEQScalar: primitiveEQScalar, primitivePredicateAtAllPutBoolean: primitivePredicateAtAllPutBoolean, zoomBitmap: zoomBitmap, primitiveNEScalar: primitiveNEScalar, scalarGetDistanceTo: scalarGetDistanceTo, primitiveSubArrays: primitiveSubArrays, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:21 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from KlattSynthesizerPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function Klatt() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus /*** Constants ***/ var A1v = 46; var A2f = 34; var A2v = 47; var A3f = 35; var A3v = 48; var A4f = 36; var A4v = 49; var A5f = 37; var A6f = 38; var Anv = 45; var Aspiration = 9; var Atv = 50; var B1 = 13; var B2 = 17; var B2f = 40; var B3 = 19; var B3f = 41; var B4 = 21; var B4f = 42; var B5 = 23; var B5f = 43; var B6 = 25; var B6f = 44; var Bnp = 27; var Bnz = 29; var Btp = 31; var Btz = 33; var Bypass = 39; var Diplophonia = 4; var Epsilon = 0.0001; var F0 = 0; var F1 = 12; var F2 = 16; var F3 = 18; var F4 = 20; var F5 = 22; var F6 = 24; var Flutter = 1; var Fnp = 26; var Fnz = 28; var Friction = 10; var Ftp = 30; var Ftz = 32; var Gain = 51; var Jitter = 2; var PI = 3.141592653589793; var R1c = 12; var R1vp = 3; var R2c = 13; var R2fp = 7; var R2vp = 4; var R3c = 14; var R3fp = 8; var R3vp = 5; var R4c = 15; var R4fp = 9; var R4vp = 6; var R5c = 16; var R5fp = 10; var R6c = 17; var R6fp = 11; var R7c = 18; var R8c = 19; var Ra = 7; var Rk = 8; var Rnpc = 20; var Rnpp = 1; var Rnz = 21; var Ro = 6; var Rout = 24; var Rtpc = 22; var Rtpp = 2; var Rtz = 23; var Shimmer = 3; var Turbulence = 11; var Voicing = 5; /*** Variables ***/ var a1 = 0; var a2 = 0; var b1 = 0; var c1 = 0; var cascade = 0; var frame = null; var glast = 0; var interpreterProxy = null; var moduleName = "Klatt 3 November 2014 (e)"; var nlast = 0; var nmod = 0; var nopen = 0; var nper = 0; var periodCount = 0; var pitch = 0; var resonators = null; var samplesCount = 0; var samplesPerFrame = 0; var samplingRate = 0; var seed = 0; var t0 = 0; var vlast = 0; var x1 = 0; var x2 = 0; /* Add diplophonia (bicyclic voice). Change voicing amplitude. */ function addAmplitudeDiplophonia() { if ((MOD(periodCount, 2)) !== 0) { /* x1 must be <= 0 */ x1 = x1 * (1.0 - frame[Diplophonia]); if (x1 > 0) { x1 = 0; } } } /* Add F0 flutter, as specified in: 'Analysis, synthesis and perception of voice quality variations among female and male talkers' D.H. Klatt and L.C. Klatt JASA 87(2) February 1990. Flutter is added by applying a quasi-random element constructed from three slowly varying sine waves. */ function addFlutter() { var asin; var bsin; var csin; var deltaF0; var timeCount; timeCount = samplesCount / samplingRate; asin = Math.sin(((2.0 * PI) * 12.7) * timeCount); bsin = Math.sin(((2.0 * PI) * 7.1) * timeCount); csin = Math.sin(((2.0 * PI) * 4.7) * timeCount); deltaF0 = (((frame[Flutter] * 2.0) * frame[F0]) / 100.0) * ((asin + bsin) + csin); pitch += deltaF0; } /* Add diplophonia (bicyclic voice). Change F0. */ function addFrequencyDiplophonia() { if ((MOD(periodCount, 2)) === 0) { pitch += (frame[Diplophonia] * frame[F0]) * (1.0 - frame[Ro]); } else { pitch -= (frame[Diplophonia] * frame[F0]) * (1.0 - frame[Ro]); } } /* Add jitter (random F0 perturbation). */ function addJitter() { pitch += (((nextRandom() - 32767) * frame[Jitter]) / 32768.0) * frame[F0]; } /* Add shimmer (random voicing amplitude perturbation). */ function addShimmer() { /* x1 must be <= 0 */ x1 += (((nextRandom() - 32767) * frame[Shimmer]) / 32768.0) * x1; if (x1 > 0) { x1 = 0; } } /* Set up an anti-resonator */ function antiResonatorfrequencybandwidth(index, freq, bw) { var a; var arg; var b; var c; var r; arg = ((0.0 - PI) / samplingRate) * bw; r = Math.exp(arg); c = 0.0 - (r * r); arg = ((PI * 2.0) / samplingRate) * freq; b = (r * Math.cos(arg)) * 2.0; a = (1.0 - b) - c; a = 1.0 / a; b = (0.0 - b) * a; c = (0.0 - c) * a; resonatorAput(index, a); resonatorBput(index, b); resonatorCput(index, c); } function antiResonatorvalue(index, aFloat) { var answer; var p1; answer = ((resonatorA(index) * aFloat) + (resonatorB(index) * ((p1 = resonatorP1(index))))) + (resonatorC(index) * resonatorP2(index)); resonatorP2put(index, p1); resonatorP1put(index, aFloat); return answer; } /* Cascade vocal tract, excited by laryngeal sources. Nasal antiresonator, nasal resonator, tracheal antirresonator, tracheal resonator, then formants F8, F7, F6, F5, F4, F3, F2, F1. */ function cascadeBranch(source) { var out; if (!(cascade > 0)) { return 0.0; } out = antiResonatorvalue(Rnz, source); out = resonatorvalue(Rnpc, out); out = antiResonatorvalue(Rtz, out); /* Do not use unless sample rate >= 16000 */ out = resonatorvalue(Rtpc, out); if (cascade >= 8) { out = resonatorvalue(R8c, out); } if (cascade >= 7) { out = resonatorvalue(R7c, out); } if (cascade >= 6) { out = resonatorvalue(R6c, out); } if (cascade >= 5) { out = resonatorvalue(R5c, out); } if (cascade >= 4) { out = resonatorvalue(R4c, out); } if (cascade >= 3) { out = resonatorvalue(R3c, out); } if (cascade >= 2) { out = resonatorvalue(R2c, out); } if (cascade >= 1) { out = resonatorvalue(R1c, out); } return out; } /* Return the first indexable word of oop which is assumed to be variableWordSubclass */ function checkedFloatPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWords(oop)); if (interpreterProxy.failed()) { return 0; } return oop.wordsAsFloat32Array(); } /* Return the first indexable word of oop which is assumed to be variableWordSubclass */ function checkedShortPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWords(oop)); if (interpreterProxy.failed()) { return 0; } return oop.wordsAsInt16Array(); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function glottalSource() { var x0; if (t0 === 0) { return 0; } if (nper < nopen) { x0 = (a1 * x1) + (a2 * x2); x2 = x1; x1 = x0; } else { x0 = (b1 * x1) - c1; x1 = x0; } if (nper >= t0) { nper = 0; pitchSynchronousReset(); } ++nper; return x0; } function linearFromdB(aNumber) { return Math.pow(2.0,((aNumber - 87.0) / 6.0)) * 32.767; } function loadFrom(klattOop) { var oop; interpreterProxy.success(SIZEOF(klattOop) === 22); if (interpreterProxy.failed()) { return false; } oop = interpreterProxy.fetchPointerofObject(0, klattOop); resonators = checkedFloatPtrOf(oop); pitch = interpreterProxy.fetchFloatofObject(2, klattOop); t0 = interpreterProxy.fetchIntegerofObject(3, klattOop); nper = interpreterProxy.fetchIntegerofObject(4, klattOop); nopen = interpreterProxy.fetchIntegerofObject(5, klattOop); nmod = interpreterProxy.fetchIntegerofObject(6, klattOop); a1 = interpreterProxy.fetchFloatofObject(7, klattOop); a2 = interpreterProxy.fetchFloatofObject(8, klattOop); x1 = interpreterProxy.fetchFloatofObject(9, klattOop); x2 = interpreterProxy.fetchFloatofObject(10, klattOop); b1 = interpreterProxy.fetchFloatofObject(11, klattOop); c1 = interpreterProxy.fetchFloatofObject(12, klattOop); glast = interpreterProxy.fetchFloatofObject(13, klattOop); vlast = interpreterProxy.fetchFloatofObject(14, klattOop); nlast = interpreterProxy.fetchFloatofObject(15, klattOop); periodCount = interpreterProxy.fetchIntegerofObject(16, klattOop); samplesCount = interpreterProxy.fetchIntegerofObject(17, klattOop); seed = interpreterProxy.fetchIntegerofObject(18, klattOop); cascade = interpreterProxy.fetchIntegerofObject(19, klattOop); samplesPerFrame = interpreterProxy.fetchIntegerofObject(20, klattOop); samplingRate = interpreterProxy.fetchIntegerofObject(21, klattOop); return interpreterProxy.failed() === false; } /* Answer a random number between 0 and 65535. */ function nextRandom() { seed = ((seed * 1309) + 13849) & 65535; return seed; } function normalizeGlottalPulse() { var ingore; var s0; var s1; var s2; s0 = 0.0; s1 = x1; s2 = x2; for (ingore = 1; ingore <= nopen; ingore++) { s0 = (a1 * s1) + (a2 * s2); s2 = s1; s1 = s0; } if (s0 !== 0.0) { x1 = (x1 / s0) * 10000.0; } } /* Friction-excited parallel vocal tract formants F6, F5, F4, F3, F2, outputs added with alternating sign. Sound source for other parallel resonators is friction plus first difference of voicing waveform. */ function parallelFrictionBranch(source) { return (((resonatorvalue(R2fp, source) - resonatorvalue(R3fp, source)) + resonatorvalue(R4fp, source)) - resonatorvalue(R5fp, source)) + resonatorvalue(R6fp, source); } /* Voice-excited parallel vocal tract F1, F2, F3, F4, FNP and FTP. */ function parallelVoicedBranch(source) { return ((((resonatorvalue(R1vp, source) + resonatorvalue(R2vp, source)) + resonatorvalue(R3vp, source)) + resonatorvalue(R4vp, source)) + resonatorvalue(Rnpp, source)) + resonatorvalue(Rtpp, source); } function pitchSynchronousReset() { if (frame[F0] > 0) { voicedPitchSynchronousReset(); periodCount = MOD((periodCount + 1), 65535); } else { t0 = 1; nmod = t0; } } function primitiveSynthesizeFrameIntoStartingAt() { var aKlattFrame; var buffer; var bufferOop; var rcvr; var startIndex; aKlattFrame = checkedFloatPtrOf(interpreterProxy.stackValue(2)); buffer = checkedShortPtrOf((bufferOop = interpreterProxy.stackValue(1))); startIndex = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } rcvr = interpreterProxy.stackObjectValue(3); if (!loadFrom(rcvr)) { return null; } interpreterProxy.success((SIZEOF(bufferOop) * 2) >= samplesPerFrame); if (interpreterProxy.failed()) { return null; } synthesizeFrameintostartingAt(aKlattFrame, buffer, startIndex); if (!saveTo(rcvr)) { return null; } interpreterProxy.pop(3); } function quphicosphisinphirphid(u, phi, cosphi, sinphi, rphid) { var expuphi; expuphi = Math.exp(u * phi); return (expuphi * ((((rphid * ((u * u) + 1.0)) + u) * sinphi) - cosphi)) + 1.0; } /* Convert formant frequencies and bandwidth into resonator difference equation coefficients. */ function resonatorfrequencybandwidth(index, freq, bw) { var a; var arg; var b; var c; var r; arg = ((0.0 - PI) / samplingRate) * bw; r = Math.exp(arg); c = 0.0 - (r * r); arg = ((PI * 2.0) / samplingRate) * freq; b = (r * Math.cos(arg)) * 2.0; a = (1.0 - b) - c; resonatorAput(index, a); resonatorBput(index, b); resonatorCput(index, c); } /* Convert formant frequencies and bandwidth into resonator difference equation coefficients. */ function resonatorfrequencybandwidthgain(index, freq, bw, gain) { resonatorfrequencybandwidth(index, freq, bw); resonatorAput(index, resonatorA(index) * gain); } function resonatorvalue(index, aFloat) { var answer; var p1; /* (p1 between: -100000 and: 100000) ifFalse: [self halt]. (answer between: -100000 and: 100000) ifFalse: [self halt]. */ answer = ((resonatorA(index) * aFloat) + (resonatorB(index) * ((p1 = resonatorP1(index))))) + (resonatorC(index) * resonatorP2(index)); resonatorP2put(index, p1); resonatorP1put(index, answer); return answer; } function resonatorA(index) { return resonators[(index * 5) - 5]; } function resonatorAput(index, aFloat) { resonators[(index * 5) - 5] = aFloat; } function resonatorB(index) { return resonators[(index * 5) - 4]; } function resonatorBput(index, aFloat) { resonators[(index * 5) - 4] = aFloat; } function resonatorC(index) { return resonators[(index * 5) - 3]; } function resonatorCput(index, aFloat) { resonators[(index * 5) - 3] = aFloat; } function resonatorP1(index) { return resonators[(index * 5) - 2]; } function resonatorP1put(index, aFloat) { resonators[(index * 5) - 2] = aFloat; } function resonatorP2(index) { return resonators[(index * 5) - 1]; } function resonatorP2put(index, aFloat) { resonators[(index * 5) - 1] = aFloat; } function rorark(roNumber, raNumber, rkNumber) { var cosphi; var d; var gamma; var gammapwr; var phi; var r; var ra; var rho; var rk; var ro; var rphid; var sinphi; var te; var theta; var u; te = ((t0 * roNumber)|0); ro = te / t0; rk = rkNumber; ra = raNumber; if (ra <= 0.0) { d = 1.0; } else { r = (1.0 - ro) / ra; d = 1.0 - (r / (Math.exp(r) - 1.0)); } phi = PI * (rk + 1.0); cosphi = Math.cos(phi); sinphi = Math.sin(phi); rphid = ((ra / ro) * phi) * d; u = zeroQphicosphisinphirphid(phi, cosphi, sinphi, rphid); theta = phi / te; rho = Math.exp(u * theta); a1 = (2.0 * Math.cos(theta)) * rho; a2 = 0.0 - (rho * rho); x2 = 0.0; x1 = rho * Math.sin(theta); gamma = Math.exp(-1 / (ra * t0)); gammapwr = Math.pow(gamma,(t0 - te)); b1 = gamma; c1 = ((1.0 - gamma) * gammapwr) / (1.0 - gammapwr); normalizeGlottalPulse(); } function saveTo(origKlattOop) { var a1Oop; var a2Oop; var b1Oop; var c1Oop; var glastOop; var klattOop; var nlastOop; var pitchOop; var vlastOop; var x1Oop; var x2Oop; interpreterProxy.pushRemappableOop(origKlattOop); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(pitch)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(a1)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(a2)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(x1)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(x2)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(b1)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(c1)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(glast)); interpreterProxy.pushRemappableOop(interpreterProxy.floatObjectOf(vlast)); nlastOop = interpreterProxy.floatObjectOf(nlast); vlastOop = interpreterProxy.popRemappableOop(); glastOop = interpreterProxy.popRemappableOop(); c1Oop = interpreterProxy.popRemappableOop(); b1Oop = interpreterProxy.popRemappableOop(); x2Oop = interpreterProxy.popRemappableOop(); x1Oop = interpreterProxy.popRemappableOop(); a2Oop = interpreterProxy.popRemappableOop(); a1Oop = interpreterProxy.popRemappableOop(); pitchOop = interpreterProxy.popRemappableOop(); klattOop = interpreterProxy.popRemappableOop(); if (interpreterProxy.failed()) { return false; } interpreterProxy.storePointerofObjectwithValue(2, klattOop, pitchOop); interpreterProxy.storeIntegerofObjectwithValue(3, klattOop, t0); interpreterProxy.storeIntegerofObjectwithValue(4, klattOop, nper); interpreterProxy.storeIntegerofObjectwithValue(5, klattOop, nopen); interpreterProxy.storeIntegerofObjectwithValue(6, klattOop, nmod); interpreterProxy.storePointerofObjectwithValue(7, klattOop, a1Oop); interpreterProxy.storePointerofObjectwithValue(8, klattOop, a2Oop); interpreterProxy.storePointerofObjectwithValue(9, klattOop, x1Oop); interpreterProxy.storePointerofObjectwithValue(10, klattOop, x2Oop); interpreterProxy.storePointerofObjectwithValue(11, klattOop, b1Oop); interpreterProxy.storePointerofObjectwithValue(12, klattOop, c1Oop); interpreterProxy.storePointerofObjectwithValue(13, klattOop, glastOop); interpreterProxy.storePointerofObjectwithValue(14, klattOop, vlastOop); interpreterProxy.storePointerofObjectwithValue(15, klattOop, nlastOop); interpreterProxy.storeIntegerofObjectwithValue(16, klattOop, periodCount); interpreterProxy.storeIntegerofObjectwithValue(17, klattOop, samplesCount); interpreterProxy.storeIntegerofObjectwithValue(18, klattOop, seed); return interpreterProxy.failed() === false; } function setCurrentFrame(aKlattFrame) { var ampF1V; var ampF2F; var ampF2V; var ampF3F; var ampF3V; var ampF4F; var ampF4V; var ampF5F; var ampF6F; var ampFNV; var ampFTV; /* Fudge factors... */ frame = aKlattFrame; /* -4.44 dB */ ampFNV = linearFromdB(frame[Anv]) * 0.6; /* -4.44 dB */ ampFTV = linearFromdB(frame[Atv]) * 0.6; /* -7.96 dB */ ampF1V = linearFromdB(frame[A1v]) * 0.4; /* -16.5 dB */ ampF2V = linearFromdB(frame[A2v]) * 0.15; /* -24.4 dB */ ampF3V = linearFromdB(frame[A3v]) * 0.06; /* -28.0 dB */ ampF4V = linearFromdB(frame[A4v]) * 0.04; /* -16.5 dB */ ampF2F = linearFromdB(frame[A2f]) * 0.15; /* -24.4 dB */ ampF3F = linearFromdB(frame[A3f]) * 0.06; /* -28.0 dB */ ampF4F = linearFromdB(frame[A4f]) * 0.04; /* -33.2 dB */ ampF5F = linearFromdB(frame[A5f]) * 0.022; /* -30.5 dB */ /* Set coefficients of variable cascade resonators */ ampF6F = linearFromdB(frame[A6f]) * 0.03; if (cascade >= 8) { if (samplingRate >= 16000) { /* Inside Nyquist rate? */ resonatorfrequencybandwidth(R8c, 7500, 600); } else { cascade = 6; } } if (cascade >= 7) { if (samplingRate >= 16000) { /* Inside Nyquist rate? */ resonatorfrequencybandwidth(R7c, 6500, 500); } else { cascade = 6; } } if (cascade >= 6) { resonatorfrequencybandwidth(R6c, frame[F6], frame[B6]); } if (cascade >= 5) { resonatorfrequencybandwidth(R5c, frame[F5], frame[B5]); } resonatorfrequencybandwidth(R4c, frame[F4], frame[B4]); resonatorfrequencybandwidth(R3c, frame[F3], frame[B3]); resonatorfrequencybandwidth(R2c, frame[F2], frame[B2]); resonatorfrequencybandwidth(R1c, frame[F1], frame[B1]); resonatorfrequencybandwidth(Rnpc, frame[Fnp], frame[Bnp]); resonatorfrequencybandwidth(Rtpc, frame[Ftp], frame[Btp]); antiResonatorfrequencybandwidth(Rnz, frame[Fnz], frame[Bnz]); antiResonatorfrequencybandwidth(Rtz, frame[Ftz], frame[Btz]); resonatorfrequencybandwidthgain(Rnpp, frame[Fnp], frame[Bnp], ampFNV); resonatorfrequencybandwidthgain(Rtpp, frame[Ftp], frame[Btp], ampFTV); resonatorfrequencybandwidthgain(R1vp, frame[F1], frame[B1], ampF1V); resonatorfrequencybandwidthgain(R2vp, frame[F2], frame[B2], ampF2V); resonatorfrequencybandwidthgain(R3vp, frame[F3], frame[B3], ampF3V); resonatorfrequencybandwidthgain(R4vp, frame[F4], frame[B4], ampF4V); resonatorfrequencybandwidthgain(R2fp, frame[F2], frame[B2f], ampF2F); resonatorfrequencybandwidthgain(R3fp, frame[F3], frame[B3f], ampF3F); resonatorfrequencybandwidthgain(R4fp, frame[F4], frame[B4f], ampF4F); resonatorfrequencybandwidthgain(R5fp, frame[F5], frame[B5f], ampF5F); resonatorfrequencybandwidthgain(R6fp, frame[F6], frame[B6f], ampF6F); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function synthesizeFrameintostartingAt(aKlattFrame, buffer, startIndex) { var ampGain; var aspiration; var aspirationNoise; var bypass; var friction; var frictionNoise; var gain; var glotout; var index; var noise; var out; var parGlotout; var parVoicing; var source; var temp; var top; var turbulence; var voice; var voicing; setCurrentFrame(aKlattFrame); if (pitch > 0) { voicing = linearFromdB(frame[Voicing] - 7); parVoicing = linearFromdB(frame[Voicing]); turbulence = linearFromdB(frame[Turbulence]) * 0.1; } else { voicing = (parVoicing = (turbulence = 0.0)); } friction = linearFromdB(frame[Friction]) * 0.25; aspiration = linearFromdB(frame[Aspiration]) * 0.05; /* -26.0 dB */ /* Flod overall gain into output resonator (low-pass filter) */ bypass = linearFromdB(frame[Bypass]) * 0.05; gain = frame[Gain] - 3; if (gain <= 0) { gain = 57; } ampGain = linearFromdB(gain); resonatorfrequencybandwidthgain(Rout, 0, samplingRate, ampGain); noise = nlast; index = startIndex; top = (samplesPerFrame + startIndex) - 1; while (index <= top) { /* Get low-passed random number for aspiration and friction noise */ /* radom number between -8196.0 and 8196.0 */ /* Tilt down noise spectrum by soft low-pass filter having a pole near the origin in the z-plane. */ noise = (nextRandom() - 32768) / 4.0; noise += 0.75 * nlast; /* Amplitude modulate noise (reduce noise amplitude during second half of glottal period) if voicing simultaneously present. */ nlast = noise; if (nper > nmod) { noise = noise * 0.5; } /* Compute voicing waveform. */ frictionNoise = friction * noise; voice = glottalSource(); /* Add turbulence during glottal open phase. Use random rather than noise because noise is low-passed. */ vlast = voice; if (nper < nopen) { voice += (turbulence * (nextRandom() - 32768)) / 4.0; } glotout = voicing * voice; /* Compute aspiration amplitude and add to voicing source. */ parGlotout = parVoicing * voice; aspirationNoise = aspiration * noise; glotout += aspirationNoise; /* Cascade vocal tract, excited by laryngeal sources. Nasal antiresonator, nasal resonator, trachearl antirresonator, tracheal resonator, then formants F8, F7, F6, F5, F4, F3, F2, F1. */ parGlotout += aspirationNoise; /* Voice-excited parallel vocal tract F1, F2, F3, F4, FNP and FTP. */ out = cascadeBranch(glotout); /* Source is voicing plus aspiration. */ source = parGlotout; /* Friction-excited parallel vocal tract formants F6, F5, F4, F3, F2, outputs added with alternating sign. Sound source for other parallel resonators is friction plus first difference of voicing waveform. */ out += parallelVoicedBranch(source); source = (frictionNoise + parGlotout) - glast; glast = parGlotout; /* Apply bypas and output low-pass filter */ out = parallelFrictionBranch(source) - out; out = (bypass * source) - out; out = resonatorvalue(Rout, out); temp = ((out * ampGain)|0); if (temp < -32768) { temp = -32768; } if (temp > 32767) { temp = 32767; } buffer[index - 1] = temp; ++index; ++samplesCount; } } /* Set the pitch. */ function voicedPitchSynchronousReset() { /* Add flutter and jitter (F0 perturbations). */ pitch = frame[F0]; addFlutter(); addJitter(); addFrequencyDiplophonia(); if (pitch < 0) { pitch = 0; } /* Duration of period before amplitude modulation. */ t0 = ((samplingRate / pitch)|0); nmod = t0; if (frame[Voicing] > 0) { nmod = nmod >> 1; } /* Set the LF glottal pulse model parameters. */ nopen = ((t0 * frame[Ro])|0); rorark(frame[Ro], frame[Ra], frame[Rk]); addShimmer(); addAmplitudeDiplophonia(); } function zeroQphicosphisinphirphid(phi, cosphi, sinphi, rphid) { var qa; var qb; var qc; var qzero; var ua; var ub; var uc; qzero = quphicosphisinphirphid(0, phi, cosphi, sinphi, rphid); if (qzero > 0) { ua = 0; ub = 1; qa = qzero; qb = quphicosphisinphirphid(ub, phi, cosphi, sinphi, rphid); while (qb > 0) { ua = ub; qa = qb; ub = ub * 2; qb = quphicosphisinphirphid(ub, phi, cosphi, sinphi, rphid); } } else { ua = -1; ub = 0; qa = quphicosphisinphirphid(ua, phi, cosphi, sinphi, rphid); qb = qzero; while (qa < 0) { ub = ua; qb = qa; ua = ua * 2; qa = quphicosphisinphirphid(ua, phi, cosphi, sinphi, rphid); } } while ((ub - ua) > Epsilon) { uc = (ub + ua) / 2.0; qc = quphicosphisinphirphid(uc, phi, cosphi, sinphi, rphid); if (qc > 0) { ua = uc; qa = qc; } else { ub = uc; qb = qc; } } return (ub + ua) / 2.0; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("Klatt", { setInterpreter: setInterpreter, primitiveSynthesizeFrameIntoStartingAt: primitiveSynthesizeFrameIntoStartingAt, getModuleName: getModuleName, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:21 pm */ /* Automatically generated by JSSmartSyntaxPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from LargeIntegersPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function LargeIntegers() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; /*** Functions ***/ function CLASSOF(obj) { return typeof obj === "number" ? interpreterProxy.classSmallInteger() : obj.sqClass } function BYTESIZEOF(obj) { return obj.bytes ? obj.bytes.length : obj.words ? obj.words.length * 4 : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift /*** Variables ***/ var andOpIndex = 0; var interpreterProxy = null; var moduleName = "LargeIntegers v1.5 (e)"; var orOpIndex = 1; var xorOpIndex = 2; /* Argument has to be aBytesOop! */ /* Tests for any magnitude bits in the interval from start to stopArg. */ function anyBitOfBytesfromto(aBytesOop, start, stopArg) { var lastByteIx; var digit; var magnitude; var leftShift; var rightShift; var firstByteIx; var stop; var mask; var ix; // missing DebugCode; if ((start < 1) || (stopArg < 1)) { return interpreterProxy.primitiveFail(); } magnitude = aBytesOop; stop = Math.min(stopArg, highBitOfBytes(magnitude)); if (start > stop) { return false; } firstByteIx = ((start - 1) >> 3) + 1; lastByteIx = ((stop - 1) >> 3) + 1; rightShift = MOD((start - 1), 8); leftShift = 7 - (MOD((stop - 1), 8)); if (firstByteIx === lastByteIx) { mask = (SHL(255, rightShift)) & (SHR(255, leftShift)); digit = digitOfBytesat(magnitude, firstByteIx); return (digit & mask) !== 0; } if ((SHR(digitOfBytesat(magnitude, firstByteIx), rightShift)) !== 0) { return true; } for (ix = (firstByteIx + 1); ix <= (lastByteIx - 1); ix++) { if (digitOfBytesat(magnitude, ix) !== 0) { return true; } } if (((SHL(digitOfBytesat(magnitude, lastByteIx), leftShift)) & 255) !== 0) { return true; } return false; } /* Attention: this method invalidates all oop's! Only newBytes is valid at return. */ /* Does not normalize. */ function bytesgrowTo(aBytesObject, newLen) { var oldLen; var copyLen; var newBytes; newBytes = interpreterProxy.instantiateClassindexableSize(CLASSOF(aBytesObject), newLen); oldLen = BYTESIZEOF(aBytesObject); if (oldLen < newLen) { copyLen = oldLen; } else { copyLen = newLen; } cDigitCopyFromtolen(aBytesObject.bytes, newBytes.bytes, copyLen); return newBytes; } /* Attention: this method invalidates all oop's! Only newBytes is valid at return. */ function bytesOrIntgrowTo(oop, len) { var sq_class; var val; var newBytes; if (typeof oop === "number") { val = oop; if (val < 0) { sq_class = interpreterProxy.classLargeNegativeInteger(); } else { sq_class = interpreterProxy.classLargePositiveInteger(); } newBytes = interpreterProxy.instantiateClassindexableSize(sq_class, len); cCopyIntValtoBytes(val, newBytes); } else { newBytes = bytesgrowTo(oop, len); } return newBytes; } function cCopyIntValtoBytes(val, bytes) { var pByte; var ix; var ixLimiT; pByte = bytes.bytes; for (ix = 1, ixLimiT = cDigitLengthOfCSI(val); ix <= ixLimiT; ix++) { pByte[ix - 1] = cDigitOfCSIat(val, ix); } } /* pByteRes len = longLen; returns over.. */ function cDigitAddlenwithleninto(pByteShort, shortLen, pByteLong, longLen, pByteRes) { var i; var limit; var accum; accum = 0; limit = shortLen - 1; for (i = 0; i <= limit; i++) { accum = ((accum >>> 8) + pByteShort[i]) + pByteLong[i]; pByteRes[i] = (accum & 255); } limit = longLen - 1; for (i = shortLen; i <= limit; i++) { accum = (accum >>> 8) + pByteLong[i]; pByteRes[i] = (accum & 255); } return accum >>> 8; } /* Precondition: pFirst len = pSecond len. */ function cDigitComparewithlen(pFirst, pSecond, len) { var firstDigit; var secondDigit; var ix; ix = len - 1; while (ix >= 0) { if (((secondDigit = pSecond[ix])) !== ((firstDigit = pFirst[ix]))) { if (secondDigit < firstDigit) { return 1; } else { return -1; } } --ix; } return 0; } function cDigitCopyFromtolen(pFrom, pTo, len) { var limit; var i; limit = len - 1; for (i = 0; i <= limit; i++) { pTo[i] = pFrom[i]; } return 0; } function cDigitDivlenremlenquolen(pDiv, divLen, pRem, remLen, pQuo, quoLen) { var b; var q; var a; var dnh; var lo; var hi; var r3; var mul; var cond; var l; var k; var j; var i; var dl; var ql; var r1r2; var dh; var t; /* Last actual byte of data (ST ix) */ dl = divLen - 1; ql = quoLen; dh = pDiv[dl - 1]; if (dl === 1) { dnh = 0; } else { dnh = pDiv[dl - 2]; } for (k = 1; k <= ql; k++) { /* maintain quo*arg+rem=self */ /* Estimate rem/div by dividing the leading two digits of rem by dh. */ /* The estimate is q = qhi*16r100+qlo, where qhi and qlo are unsigned char. */ /* r1 := rem digitAt: j. */ j = (remLen + 1) - k; if (pRem[j - 1] === dh) { q = 255; } else { /* Compute q = (r1,r2)//dh, t = (r1,r2)\\dh. */ /* r2 := (rem digitAt: j - 2). */ r1r2 = pRem[j - 1]; r1r2 = (r1r2 << 8) + pRem[j - 2]; t = MOD(r1r2, dh); /* Next compute (hi,lo) := q*dnh */ q = DIV(r1r2, dh); mul = q * dnh; hi = mul >>> 8; /* Correct overestimate of q. Max of 2 iterations through loop -- see Knuth vol. 2 */ lo = mul & 255; if (j < 3) { r3 = 0; } else { r3 = pRem[j - 3]; } while (true) { if ((t < hi) || ((t === hi) && (r3 < lo))) { /* i.e. (t,r3) < (hi,lo) */ --q; if (lo < dnh) { --hi; lo = (lo + 256) - dnh; } else { lo -= dnh; } cond = hi >= dh; } else { cond = false; } if (!(cond)) break; hi -= dh; } } l = j - dl; a = 0; for (i = 1; i <= divLen; i++) { hi = pDiv[i - 1] * (q >>> 8); lo = pDiv[i - 1] * (q & 255); b = (pRem[l - 1] - a) - (lo & 255); pRem[l - 1] = (b & 255); /* This is a possible replacement to simulate arithmetic shift (preserving sign of b) */ /* b := b >> 8 bitOr: (0 - (b >> ((interpreterProxy sizeof: b)*8 */ /* CHAR_BIT */ /* -1)) << 8). */ b = b >> 8; a = (hi + (lo >>> 8)) - b; ++l; } if (a > 0) { /* Add div back into rem, decrease q by 1 */ --q; l = j - dl; a = 0; for (i = 1; i <= divLen; i++) { a = ((a >>> 8) + pRem[l - 1]) + pDiv[i - 1]; pRem[l - 1] = (a & 255); ++l; } } pQuo[quoLen - k] = q; } return 0; } /* Answer the index (in bits) of the high order bit of the receiver, or zero if the receiver is zero. This method is allowed (and needed) for LargeNegativeIntegers as well, since Squeak's LargeIntegers are sign/magnitude. */ function cDigitHighBitlen(pByte, len) { var lastDigit; var realLength; realLength = len; while (((lastDigit = pByte[realLength - 1])) === 0) { if (((--realLength)) === 0) { return 0; } } return cHighBit(lastDigit) + (8 * (realLength - 1)); } /* Answer the number of indexable fields of a CSmallInteger. This value is the same as the largest legal subscript. */ function cDigitLengthOfCSI(csi) { if ((csi < 256) && (csi > -256)) { return 1; } if ((csi < 65536) && (csi > -65536)) { return 2; } if ((csi < 16777216) && (csi > -16777216)) { return 3; } return 4; } /* C indexed! */ function cDigitLshiftfromlentolen(shiftCount, pFrom, lenFrom, pTo, lenTo) { var digitShift; var carry; var digit; var i; var bitShift; var rshift; var limit; digitShift = shiftCount >> 3; bitShift = MOD(shiftCount, 8); limit = digitShift - 1; for (i = 0; i <= limit; i++) { pTo[i] = 0; } if (bitShift === 0) { /* Fast version for digit-aligned shifts */ /* C indexed! */ return cDigitReplacefromtowithstartingAt(pTo, digitShift, lenTo - 1, pFrom, 0); } rshift = 8 - bitShift; carry = 0; limit = lenFrom - 1; for (i = 0; i <= limit; i++) { digit = pFrom[i]; pTo[i + digitShift] = ((carry | (SHL(digit, bitShift))) & 255); carry = SHR(digit, rshift); } if (carry !== 0) { pTo[lenTo - 1] = carry; } return 0; } function cDigitMontgomerylentimeslenmodulolenmInvModBinto(pBytesFirst, firstLen, pBytesSecond, secondLen, pBytesThird, thirdLen, mInv, pBytesRes) { var k; var i; var lastByte; var limit3; var limit2; var limit1; var u; var accum; limit1 = firstLen - 1; limit2 = secondLen - 1; limit3 = thirdLen - 1; lastByte = 0; for (i = 0; i <= limit1; i++) { accum = pBytesRes[0] + (pBytesFirst[i] * pBytesSecond[0]); u = (accum * mInv) & 255; accum += u * pBytesThird[0]; for (k = 1; k <= limit2; k++) { accum = (((accum >>> 8) + pBytesRes[k]) + (pBytesFirst[i] * pBytesSecond[k])) + (u * pBytesThird[k]); pBytesRes[k - 1] = (accum & 255); } for (k = secondLen; k <= limit3; k++) { accum = ((accum >>> 8) + pBytesRes[k]) + (u * pBytesThird[k]); pBytesRes[k - 1] = (accum & 255); } accum = (accum >>> 8) + lastByte; pBytesRes[limit3] = (accum & 255); lastByte = accum >>> 8; } for (i = firstLen; i <= limit3; i++) { accum = pBytesRes[0]; u = (accum * mInv) & 255; accum += u * pBytesThird[0]; for (k = 1; k <= limit3; k++) { accum = ((accum >>> 8) + pBytesRes[k]) + (u * pBytesThird[k]); pBytesRes[k - 1] = (accum & 255); } accum = (accum >>> 8) + lastByte; pBytesRes[limit3] = (accum & 255); lastByte = accum >>> 8; } if (!((lastByte === 0) && (cDigitComparewithlen(pBytesThird, pBytesRes, thirdLen) === 1))) { /* self cDigitSub: pBytesThird len: thirdLen with: pBytesRes len: thirdLen into: pBytesRes */ accum = 0; for (i = 0; i <= limit3; i++) { accum = (accum + pBytesRes[i]) - pBytesThird[i]; pBytesRes[i] = (accum & 255); accum = accum >> 8; } } } function cDigitMultiplylenwithleninto(pByteShort, shortLen, pByteLong, longLen, pByteRes) { var ab; var j; var digit; var carry; var i; var limitLong; var k; var limitShort; if ((shortLen === 1) && (pByteShort[0] === 0)) { return 0; } if ((longLen === 1) && (pByteLong[0] === 0)) { return 0; } limitShort = shortLen - 1; limitLong = longLen - 1; for (i = 0; i <= limitShort; i++) { if (((digit = pByteShort[i])) !== 0) { k = i; /* Loop invariant: 0<=carry<=0377, k=i+j-1 (ST) */ /* -> Loop invariant: 0<=carry<=0377, k=i+j (C) (?) */ carry = 0; for (j = 0; j <= limitLong; j++) { ab = pByteLong[j]; ab = ((ab * digit) + carry) + pByteRes[k]; carry = ab >>> 8; pByteRes[k] = (ab & 255); ++k; } pByteRes[k] = carry; } } return 0; } /* Answer the value of an indexable field in the receiver. LargePositiveInteger uses bytes of base two number, and each is a 'digit' base 256. */ /* ST indexed! */ function cDigitOfCSIat(csi, ix) { if (ix < 1) { interpreterProxy.primitiveFail(); } if (ix > 4) { return 0; } if (csi < 0) { return (SHR((0 - csi), ((ix - 1) * 8))) & 255; } else { return (SHR(csi, ((ix - 1) * 8))) & 255; } } /* pByteRes len = longLen. */ function cDigitOpshortlenlongleninto(opIndex, pByteShort, shortLen, pByteLong, longLen, pByteRes) { var i; var limit; limit = shortLen - 1; if (opIndex === andOpIndex) { for (i = 0; i <= limit; i++) { pByteRes[i] = (pByteShort[i] & pByteLong[i]); } limit = longLen - 1; for (i = shortLen; i <= limit; i++) { pByteRes[i] = 0; } return 0; } if (opIndex === orOpIndex) { for (i = 0; i <= limit; i++) { pByteRes[i] = (pByteShort[i] | pByteLong[i]); } limit = longLen - 1; for (i = shortLen; i <= limit; i++) { pByteRes[i] = pByteLong[i]; } return 0; } if (opIndex === xorOpIndex) { for (i = 0; i <= limit; i++) { pByteRes[i] = (pByteShort[i] ^ pByteLong[i]); } limit = longLen - 1; for (i = shortLen; i <= limit; i++) { pByteRes[i] = pByteLong[i]; } return 0; } return interpreterProxy.primitiveFail(); } /* C indexed! */ function cDigitReplacefromtowithstartingAt(pTo, start, stop, pFrom, repStart) { return function() { // inlining self cDigitCopyFrom: pFrom + repStart to: pTo + start len: stop - start + 1 var len = stop - start + 1; for (var i = 0; i < len; i++) { pTo[i + start] = pFrom[i + repStart]; } return 0; }(); } function cDigitRshiftfromlentolen(shiftCount, pFrom, fromLen, pTo, toLen) { var j; var digitShift; var carry; var digit; var bitShift; var leftShift; var limit; var start; digitShift = shiftCount >> 3; bitShift = MOD(shiftCount, 8); if (bitShift === 0) { /* Fast version for byte-aligned shifts */ /* C indexed! */ return cDigitReplacefromtowithstartingAt(pTo, 0, toLen - 1, pFrom, digitShift); } leftShift = 8 - bitShift; carry = SHR(pFrom[digitShift], bitShift); start = digitShift + 1; limit = fromLen - 1; for (j = start; j <= limit; j++) { digit = pFrom[j]; pTo[j - start] = ((carry | (SHL(digit, leftShift))) & 255); carry = SHR(digit, bitShift); } if (carry !== 0) { pTo[toLen - 1] = carry; } return 0; } function cDigitSublenwithleninto(pByteSmall, smallLen, pByteLarge, largeLen, pByteRes) { var i; var z; /* Loop invariant is -1<=z<=0 */ z = 0; for (i = 0; i <= (smallLen - 1); i++) { z = (z + pByteLarge[i]) - pByteSmall[i]; pByteRes[i] = (z & 255); z = z >> 8; } for (i = smallLen; i <= (largeLen - 1); i++) { z += pByteLarge[i]; pByteRes[i] = (z & 255); z = z >> 8; } } /* Answer the index of the high order bit of the argument, or zero if the argument is zero. */ /* For 64 bit uints there could be added a 32-shift. */ function cHighBit(uint) { var shifted; var bitNo; shifted = uint; bitNo = 0; if (!(shifted < (1 << 16))) { shifted = shifted >>> 16; bitNo += 16; } if (!(shifted < (1 << 8))) { shifted = shifted >>> 8; bitNo += 8; } if (!(shifted < (1 << 4))) { shifted = shifted >>> 4; bitNo += 4; } if (!(shifted < (1 << 2))) { shifted = shifted >>> 2; bitNo += 2; } if (!(shifted < (1 << 1))) { shifted = shifted >>> 1; ++bitNo; } return bitNo + shifted; } /* anOop has to be a SmallInteger! */ function createLargeFromSmallInteger(anOop) { var size; var res; var pByte; var ix; var sq_class; var val; val = anOop; if (val < 0) { sq_class = interpreterProxy.classLargeNegativeInteger(); } else { sq_class = interpreterProxy.classLargePositiveInteger(); } size = cDigitLengthOfCSI(val); res = interpreterProxy.instantiateClassindexableSize(sq_class, size); pByte = res.bytes; for (ix = 1; ix <= size; ix++) { pByte[ix - 1] = cDigitOfCSIat(val, ix); } return res; } /* Attention: this method invalidates all oop's! Only newBytes is valid at return. */ /* Does not normalize. */ function digitLshift(aBytesOop, shiftCount) { var newLen; var oldLen; var newBytes; var highBit; oldLen = BYTESIZEOF(aBytesOop); if (((highBit = cDigitHighBitlen(aBytesOop.bytes, oldLen))) === 0) { return 0; } newLen = ((highBit + shiftCount) + 7) >> 3; newBytes = interpreterProxy.instantiateClassindexableSize(CLASSOF(aBytesOop), newLen); cDigitLshiftfromlentolen(shiftCount, aBytesOop.bytes, oldLen, newBytes.bytes, newLen); return newBytes; } /* Attention: this method invalidates all oop's! Only newBytes is valid at return. */ /* Shift right shiftCount bits, 0<=shiftCount. Discard all digits beyond a, and all zeroes at or below a. */ /* Does not normalize. */ function digitRshiftlookfirst(aBytesOop, shiftCount, a) { var newOop; var oldDigitLen; var newByteLen; var newBitLen; var oldBitLen; oldBitLen = cDigitHighBitlen(aBytesOop.bytes, a); oldDigitLen = (oldBitLen + 7) >> 3; newBitLen = oldBitLen - shiftCount; if (newBitLen <= 0) { /* All bits lost */ return interpreterProxy.instantiateClassindexableSize(CLASSOF(aBytesOop), 0); } newByteLen = (newBitLen + 7) >> 3; newOop = interpreterProxy.instantiateClassindexableSize(CLASSOF(aBytesOop), newByteLen); cDigitRshiftfromlentolen(shiftCount, aBytesOop.bytes, oldDigitLen, newOop.bytes, newByteLen); return newOop; } /* Does not need to normalize! */ function digitAddLargewith(firstInteger, secondInteger) { var sum; var shortLen; var over; var shortInt; var resClass; var newSum; var longLen; var firstLen; var secondLen; var longInt; firstLen = BYTESIZEOF(firstInteger); secondLen = BYTESIZEOF(secondInteger); resClass = CLASSOF(firstInteger); if (firstLen <= secondLen) { shortInt = firstInteger; shortLen = firstLen; longInt = secondInteger; longLen = secondLen; } else { shortInt = secondInteger; shortLen = secondLen; longInt = firstInteger; longLen = firstLen; } sum = interpreterProxy.instantiateClassindexableSize(resClass, longLen); over = cDigitAddlenwithleninto(shortInt.bytes, shortLen, longInt.bytes, longLen, sum.bytes); if (over > 0) { /* sum := sum growby: 1. */ newSum = interpreterProxy.instantiateClassindexableSize(resClass, longLen + 1); cDigitCopyFromtolen(sum.bytes, newSum.bytes, longLen); /* C index! */ sum = newSum; sum.bytes[longLen] = over; } return sum; } /* Bit logic here is only implemented for positive integers or Zero; if rec or arg is negative, it fails. */ function digitBitLogicwithopIndex(firstInteger, secondInteger, opIx) { var shortLen; var shortLarge; var firstLarge; var secondLarge; var longLen; var longLarge; var firstLen; var secondLen; var result; if (typeof firstInteger === "number") { if (firstInteger < 0) { return interpreterProxy.primitiveFail(); } firstLarge = createLargeFromSmallInteger(firstInteger); } else { if (CLASSOF(firstInteger) === interpreterProxy.classLargeNegativeInteger()) { return interpreterProxy.primitiveFail(); } firstLarge = firstInteger; } if (typeof secondInteger === "number") { if (secondInteger < 0) { return interpreterProxy.primitiveFail(); } secondLarge = createLargeFromSmallInteger(secondInteger); } else { if (CLASSOF(secondInteger) === interpreterProxy.classLargeNegativeInteger()) { return interpreterProxy.primitiveFail(); } secondLarge = secondInteger; } firstLen = BYTESIZEOF(firstLarge); secondLen = BYTESIZEOF(secondLarge); if (firstLen < secondLen) { shortLen = firstLen; shortLarge = firstLarge; longLen = secondLen; longLarge = secondLarge; } else { shortLen = secondLen; shortLarge = secondLarge; longLen = firstLen; longLarge = firstLarge; } result = interpreterProxy.instantiateClassindexableSize(interpreterProxy.classLargePositiveInteger(), longLen); cDigitOpshortlenlongleninto(opIx, shortLarge.bytes, shortLen, longLarge.bytes, longLen, result.bytes); if (interpreterProxy.failed()) { return 0; } return normalizePositive(result); } /* Compare the magnitude of firstInteger with that of secondInteger. Return a code of 1, 0, -1 for firstInteger >, = , < secondInteger */ function digitCompareLargewith(firstInteger, secondInteger) { var secondLen; var firstLen; firstLen = BYTESIZEOF(firstInteger); secondLen = BYTESIZEOF(secondInteger); if (secondLen !== firstLen) { if (secondLen > firstLen) { return -1; } else { return 1; } } return cDigitComparewithlen(firstInteger.bytes, secondInteger.bytes, firstLen); } /* Does not normalize. */ /* Division by zero has to be checked in caller. */ function digitDivLargewithnegative(firstInteger, secondInteger, neg) { var resultClass; var result; var rem; var div; var quo; var d; var l; var secondLen; var firstLen; firstLen = BYTESIZEOF(firstInteger); secondLen = BYTESIZEOF(secondInteger); if (neg) { resultClass = interpreterProxy.classLargeNegativeInteger(); } else { resultClass = interpreterProxy.classLargePositiveInteger(); } l = (firstLen - secondLen) + 1; if (l <= 0) { result = interpreterProxy.instantiateClassindexableSize(interpreterProxy.classArray(), 2); interpreterProxy.stObjectatput(result,1,0); interpreterProxy.stObjectatput(result,2,firstInteger); return result; } d = 8 - cHighBit(unsafeByteOfat(secondInteger, secondLen)); div = digitLshift(secondInteger, d); div = bytesOrIntgrowTo(div, digitLength(div) + 1); rem = digitLshift(firstInteger, d); if (digitLength(rem) === firstLen) { rem = bytesOrIntgrowTo(rem, firstLen + 1); } quo = interpreterProxy.instantiateClassindexableSize(resultClass, l); cDigitDivlenremlenquolen(div.bytes, digitLength(div), rem.bytes, digitLength(rem), quo.bytes, digitLength(quo)); rem = digitRshiftlookfirst(rem, d, digitLength(div) - 1); result = interpreterProxy.instantiateClassindexableSize(interpreterProxy.classArray(), 2); interpreterProxy.stObjectatput(result,1,quo); interpreterProxy.stObjectatput(result,2,rem); return result; } function digitLength(oop) { if (typeof oop === "number") { return cDigitLengthOfCSI(oop); } else { return BYTESIZEOF(oop); } } function digitMontgomerytimesmodulomInvModB(firstLarge, secondLarge, thirdLarge, mInv) { var prod; var thirdLen; var firstLen; var secondLen; firstLen = BYTESIZEOF(firstLarge); secondLen = BYTESIZEOF(secondLarge); thirdLen = BYTESIZEOF(thirdLarge); if (!(firstLen <= thirdLen)) { return interpreterProxy.primitiveFail(); } if (!(secondLen <= thirdLen)) { return interpreterProxy.primitiveFail(); } if (!((mInv >= 0) && (mInv <= 255))) { return interpreterProxy.primitiveFail(); } prod = interpreterProxy.instantiateClassindexableSize(interpreterProxy.classLargePositiveInteger(), thirdLen); cDigitMontgomerylentimeslenmodulolenmInvModBinto(firstLarge.bytes, firstLen, secondLarge.bytes, secondLen, thirdLarge.bytes, thirdLen, mInv, prod.bytes); return normalizePositive(prod); } /* Normalizes. */ function digitMultiplyLargewithnegative(firstInteger, secondInteger, neg) { var longInt; var resultClass; var shortLen; var shortInt; var longLen; var prod; var secondLen; var firstLen; firstLen = BYTESIZEOF(firstInteger); secondLen = BYTESIZEOF(secondInteger); if (firstLen <= secondLen) { shortInt = firstInteger; shortLen = firstLen; longInt = secondInteger; longLen = secondLen; } else { shortInt = secondInteger; shortLen = secondLen; longInt = firstInteger; longLen = firstLen; } if (neg) { resultClass = interpreterProxy.classLargeNegativeInteger(); } else { resultClass = interpreterProxy.classLargePositiveInteger(); } prod = interpreterProxy.instantiateClassindexableSize(resultClass, longLen + shortLen); cDigitMultiplylenwithleninto(shortInt.bytes, shortLen, longInt.bytes, longLen, prod.bytes); return normalize(prod); } /* Argument has to be aLargeInteger! */ function digitOfBytesat(aBytesOop, ix) { if (ix > BYTESIZEOF(aBytesOop)) { return 0; } else { return unsafeByteOfat(aBytesOop, ix); } } /* Normalizes. */ function digitSubLargewith(firstInteger, secondInteger) { var smallerLen; var larger; var res; var smaller; var resLen; var largerLen; var firstNeg; var firstLen; var secondLen; var neg; firstNeg = CLASSOF(firstInteger) === interpreterProxy.classLargeNegativeInteger(); firstLen = BYTESIZEOF(firstInteger); secondLen = BYTESIZEOF(secondInteger); if (firstLen === secondLen) { while ((firstLen > 1) && (digitOfBytesat(firstInteger, firstLen) === digitOfBytesat(secondInteger, firstLen))) { --firstLen; } secondLen = firstLen; } if ((firstLen < secondLen) || ((firstLen === secondLen) && (digitOfBytesat(firstInteger, firstLen) < digitOfBytesat(secondInteger, firstLen)))) { larger = secondInteger; largerLen = secondLen; smaller = firstInteger; smallerLen = firstLen; neg = firstNeg === false; } else { larger = firstInteger; largerLen = firstLen; smaller = secondInteger; smallerLen = secondLen; neg = firstNeg; } resLen = largerLen; res = interpreterProxy.instantiateClassindexableSize((neg ? interpreterProxy.classLargeNegativeInteger() : interpreterProxy.classLargePositiveInteger()), resLen); cDigitSublenwithleninto(smaller.bytes, smallerLen, larger.bytes, largerLen, res.bytes); return (neg ? normalizeNegative(res) : normalizePositive(res)); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function highBitOfBytes(aBytesOop) { return cDigitHighBitlen(aBytesOop.bytes, BYTESIZEOF(aBytesOop)); } function isNormalized(anInteger) { var ix; var len; var sLen; var minVal; var maxVal; if (typeof anInteger === "number") { return true; } len = digitLength(anInteger); if (len === 0) { return false; } if (unsafeByteOfat(anInteger, len) === 0) { return false; } /* maximal digitLength of aSmallInteger */ sLen = 4; if (len > sLen) { return true; } if (len < sLen) { return false; } if (CLASSOF(anInteger) === interpreterProxy.classLargePositiveInteger()) { /* SmallInteger maxVal */ /* all bytes of maxVal but the highest one are just FF's */ maxVal = 1073741823; return unsafeByteOfat(anInteger, sLen) > cDigitOfCSIat(maxVal, sLen); } else { /* SmallInteger minVal */ /* all bytes of minVal but the highest one are just 00's */ minVal = -1073741824; if (unsafeByteOfat(anInteger, sLen) < cDigitOfCSIat(minVal, sLen)) { return false; } else { /* if just one digit differs, then anInteger < minval (the corresponding digit byte is greater!) and therefore a LargeNegativeInteger */ for (ix = 1; ix <= sLen; ix++) { if (unsafeByteOfat(anInteger, ix) !== cDigitOfCSIat(minVal, ix)) { return true; } } } } return false; } /* Check for leading zeroes and return shortened copy if so. */ function normalize(aLargeInteger) { // missing DebugCode; if (CLASSOF(aLargeInteger) === interpreterProxy.classLargePositiveInteger()) { return normalizePositive(aLargeInteger); } else { return normalizeNegative(aLargeInteger); } } /* Check for leading zeroes and return shortened copy if so. */ /* First establish len = significant length. */ function normalizeNegative(aLargeNegativeInteger) { var i; var len; var sLen; var minVal; var oldLen; var val; len = (oldLen = digitLength(aLargeNegativeInteger)); while ((len !== 0) && (unsafeByteOfat(aLargeNegativeInteger, len) === 0)) { --len; } if (len === 0) { return 0; } /* SmallInteger minVal digitLength */ sLen = 4; if (len <= sLen) { /* SmallInteger minVal */ minVal = -1073741824; if ((len < sLen) || (digitOfBytesat(aLargeNegativeInteger, sLen) < cDigitOfCSIat(minVal, sLen))) { /* If high digit less, then can be small */ val = 0; for (i = len; i >= 1; i += -1) { val = (val * 256) - unsafeByteOfat(aLargeNegativeInteger, i); } return val; } for (i = 1; i <= sLen; i++) { /* If all digits same, then = minVal (sr: minVal digits 1 to 3 are 0) */ if (digitOfBytesat(aLargeNegativeInteger, i) !== cDigitOfCSIat(minVal, i)) { /* Not so; return self shortened */ if (len < oldLen) { /* ^ self growto: len */ return bytesgrowTo(aLargeNegativeInteger, len); } else { return aLargeNegativeInteger; } } } return minVal; } if (len < oldLen) { /* ^ self growto: len */ return bytesgrowTo(aLargeNegativeInteger, len); } else { return aLargeNegativeInteger; } } /* Check for leading zeroes and return shortened copy if so. */ /* First establish len = significant length. */ function normalizePositive(aLargePositiveInteger) { var i; var len; var sLen; var val; var oldLen; len = (oldLen = digitLength(aLargePositiveInteger)); while ((len !== 0) && (unsafeByteOfat(aLargePositiveInteger, len) === 0)) { --len; } if (len === 0) { return 0; } /* SmallInteger maxVal digitLength. */ sLen = 4; if ((len <= sLen) && (digitOfBytesat(aLargePositiveInteger, sLen) <= cDigitOfCSIat(1073741823, sLen))) { /* If so, return its SmallInt value */ val = 0; for (i = len; i >= 1; i += -1) { val = (val * 256) + unsafeByteOfat(aLargePositiveInteger, i); } return val; } if (len < oldLen) { /* ^ self growto: len */ return bytesgrowTo(aLargePositiveInteger, len); } else { return aLargePositiveInteger; } } function primAnyBitFromTo() { var integer; var large; var from; var to; var _return_value; from = interpreterProxy.stackIntegerValue(1); to = interpreterProxy.stackIntegerValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); integer = interpreterProxy.stackValue(2); if (interpreterProxy.failed()) { return null; } if (typeof integer === "number") { /* convert it to a not normalized LargeInteger */ large = createLargeFromSmallInteger(integer); } else { large = integer; } _return_value = (anyBitOfBytesfromto(large, from, to)? interpreterProxy.trueObject() : interpreterProxy.falseObject()); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } /* Converts a SmallInteger into a - non normalized! - LargeInteger; aLargeInteger will be returned unchanged. */ /* Do not check for forced fail, because we need this conversion to test the plugin in ST during forced fail, too. */ function primAsLargeInteger() { var anInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); anInteger = interpreterProxy.stackValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof anInteger === "number") { _return_value = createLargeFromSmallInteger(anInteger); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } else { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, anInteger); return null; } } /* If calling this primitive fails, then C module does not exist. Do not check for forced fail, because we want to know if module exists during forced fail, too. */ function primCheckIfCModuleExists() { var _return_value; _return_value = (interpreterProxy.trueObject() ); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(1, _return_value); return null; } function _primDigitBitShift() { var rShift; var aLarge; var anInteger; var shiftCount; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); anInteger = interpreterProxy.stackValue(1); shiftCount = interpreterProxy.stackIntegerValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof anInteger === "number") { /* convert it to a not normalized LargeInteger */ aLarge = createLargeFromSmallInteger(anInteger); } else { aLarge = anInteger; } if (shiftCount >= 0) { _return_value = digitLshift(aLarge, shiftCount); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } else { rShift = 0 - shiftCount; _return_value = normalize(digitRshiftlookfirst(aLarge, rShift, BYTESIZEOF(aLarge))); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } } function primDigitAdd() { var firstLarge; var firstInteger; var secondLarge; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondInteger); } else { secondLarge = secondInteger; } _return_value = digitAddLargewith(firstLarge, secondLarge); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } function primDigitAddWith() { var firstLarge; var secondLarge; var firstInteger; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondInteger); } else { secondLarge = secondInteger; } _return_value = digitAddLargewith(firstLarge, secondLarge); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } /* Bit logic here is only implemented for positive integers or Zero; if rec or arg is negative, it fails. */ function primDigitBitAnd() { var firstInteger; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } _return_value = digitBitLogicwithopIndex(firstInteger, secondInteger, andOpIndex); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } /* Bit logic here is only implemented for positive integers or Zero; if any arg is negative, it fails. */ function primDigitBitLogicWithOp() { var firstInteger; var secondInteger; var opIndex; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); firstInteger = interpreterProxy.stackValue(2); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); secondInteger = interpreterProxy.stackValue(1); opIndex = interpreterProxy.stackIntegerValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } _return_value = digitBitLogicwithopIndex(firstInteger, secondInteger, opIndex); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(4, _return_value); return null; } /* Bit logic here is only implemented for positive integers or Zero; if rec or arg is negative, it fails. */ function primDigitBitOr() { var firstInteger; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } _return_value = digitBitLogicwithopIndex(firstInteger, secondInteger, orOpIndex); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } function primDigitBitShift() { var aLarge; var rShift; var anInteger; var shiftCount; var _return_value; shiftCount = interpreterProxy.stackIntegerValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); anInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (typeof anInteger === "number") { /* convert it to a not normalized LargeInteger */ aLarge = createLargeFromSmallInteger(anInteger); } else { aLarge = anInteger; } if (shiftCount >= 0) { _return_value = digitLshift(aLarge, shiftCount); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } else { rShift = 0 - shiftCount; _return_value = normalize(digitRshiftlookfirst(aLarge, rShift, BYTESIZEOF(aLarge))); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } } function primDigitBitShiftMagnitude() { var aLarge; var rShift; var anInteger; var shiftCount; var _return_value; shiftCount = interpreterProxy.stackIntegerValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); anInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (typeof anInteger === "number") { /* convert it to a not normalized LargeInteger */ aLarge = createLargeFromSmallInteger(anInteger); } else { aLarge = anInteger; } if (shiftCount >= 0) { _return_value = digitLshift(aLarge, shiftCount); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } else { rShift = 0 - shiftCount; _return_value = normalize(digitRshiftlookfirst(aLarge, rShift, BYTESIZEOF(aLarge))); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } } /* Bit logic here is only implemented for positive integers or Zero; if rec or arg is negative, it fails. */ function primDigitBitXor() { var firstInteger; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } _return_value = digitBitLogicwithopIndex(firstInteger, secondInteger, xorOpIndex); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } function primDigitCompare() { var firstVal; var firstInteger; var secondVal; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* first */ if (typeof secondInteger === "number") { /* second */ if (((firstVal = firstInteger)) > ((secondVal = secondInteger))) { _return_value = 1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } else { if (firstVal < secondVal) { _return_value = -1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } else { _return_value = 0; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } } } else { /* SECOND */ _return_value = -1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } } else { /* FIRST */ if (typeof secondInteger === "number") { /* second */ _return_value = 1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } else { /* SECOND */ _return_value = digitCompareLargewith(firstInteger, secondInteger); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } } } function primDigitCompareWith() { var firstVal; var secondVal; var firstInteger; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* first */ if (typeof secondInteger === "number") { /* second */ if (((firstVal = firstInteger)) > ((secondVal = secondInteger))) { _return_value = 1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } else { if (firstVal < secondVal) { _return_value = -1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } else { _return_value = 0; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } } } else { /* SECOND */ _return_value = -1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } } else { /* FIRST */ if (typeof secondInteger === "number") { /* second */ _return_value = 1; if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } else { /* SECOND */ _return_value = digitCompareLargewith(firstInteger, secondInteger); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } } } /* Answer the result of dividing firstInteger by secondInteger. Fail if parameters are not integers, not normalized or secondInteger is zero. */ function primDigitDivNegative() { var firstAsLargeInteger; var firstInteger; var secondAsLargeInteger; var secondInteger; var neg; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); secondInteger = interpreterProxy.stackValue(1); neg = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); firstInteger = interpreterProxy.stackValue(2); if (interpreterProxy.failed()) { return null; } if (!isNormalized(firstInteger)) { // missing DebugCode; interpreterProxy.primitiveFail(); return null; } if (!isNormalized(secondInteger)) { // missing DebugCode; interpreterProxy.primitiveFail(); return null; } if (typeof firstInteger === "number") { /* convert to LargeInteger */ firstAsLargeInteger = createLargeFromSmallInteger(firstInteger); } else { firstAsLargeInteger = firstInteger; } if (typeof secondInteger === "number") { /* check for zerodivide and convert to LargeInteger */ if (secondInteger === 0) { interpreterProxy.primitiveFail(); return null; } secondAsLargeInteger = createLargeFromSmallInteger(secondInteger); } else { secondAsLargeInteger = secondInteger; } _return_value = digitDivLargewithnegative(firstAsLargeInteger, secondAsLargeInteger, neg); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } /* Answer the result of dividing firstInteger by secondInteger. Fail if parameters are not integers or secondInteger is zero. */ function primDigitDivWithNegative() { var firstAsLargeInteger; var secondAsLargeInteger; var firstInteger; var secondInteger; var neg; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); firstInteger = interpreterProxy.stackValue(2); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); secondInteger = interpreterProxy.stackValue(1); neg = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert to LargeInteger */ firstAsLargeInteger = createLargeFromSmallInteger(firstInteger); } else { firstAsLargeInteger = firstInteger; } if (typeof secondInteger === "number") { /* check for zerodivide and convert to LargeInteger */ if (secondInteger === 0) { interpreterProxy.primitiveFail(); return null; } secondAsLargeInteger = createLargeFromSmallInteger(secondInteger); } else { secondAsLargeInteger = secondInteger; } _return_value = digitDivLargewithnegative(firstAsLargeInteger, secondAsLargeInteger, neg); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(4, _return_value); return null; } function primDigitMultiplyNegative() { var firstLarge; var firstInteger; var secondLarge; var secondInteger; var neg; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); secondInteger = interpreterProxy.stackValue(1); neg = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); firstInteger = interpreterProxy.stackValue(2); if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondInteger); } else { secondLarge = secondInteger; } _return_value = digitMultiplyLargewithnegative(firstLarge, secondLarge, neg); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } function primDigitMultiplyWithNegative() { var firstLarge; var secondLarge; var firstInteger; var secondInteger; var neg; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); firstInteger = interpreterProxy.stackValue(2); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); secondInteger = interpreterProxy.stackValue(1); neg = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondInteger); } else { secondLarge = secondInteger; } _return_value = digitMultiplyLargewithnegative(firstLarge, secondLarge, neg); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(4, _return_value); return null; } function primDigitSubtract() { var firstLarge; var firstInteger; var secondLarge; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondInteger); } else { secondLarge = secondInteger; } _return_value = digitSubLargewith(firstLarge, secondLarge); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } function primDigitSubtractWith() { var firstLarge; var secondLarge; var firstInteger; var secondInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); firstInteger = interpreterProxy.stackValue(1); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); secondInteger = interpreterProxy.stackValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondInteger); } else { secondLarge = secondInteger; } _return_value = digitSubLargewith(firstLarge, secondLarge); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(3, _return_value); return null; } /* If calling this primitive fails, then C module does not exist. */ function primGetModuleName() { var strPtr; var strLen; var i; var strOop; // missing DebugCode; strLen = getModuleName().length; strOop = interpreterProxy.instantiateClassindexableSize(interpreterProxy.classString(), strLen); strPtr = strOop.bytes; for (i = 0; i <= (strLen - 1); i++) { strPtr[i] = getModuleName()[i]; } if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(1, strOop); return null; } function primMontgomeryTimesModulo() { var firstLarge; var secondLarge; var firstInteger; var thirdLarge; var secondOperandInteger; var thirdModuloInteger; var smallInverseInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(2))); secondOperandInteger = interpreterProxy.stackValue(2); interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(1))); thirdModuloInteger = interpreterProxy.stackValue(1); smallInverseInteger = interpreterProxy.stackIntegerValue(0); // missing DebugCode; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(3))); firstInteger = interpreterProxy.stackValue(3); if (interpreterProxy.failed()) { return null; } if (typeof firstInteger === "number") { /* convert it to a not normalized LargeInteger */ firstLarge = createLargeFromSmallInteger(firstInteger); } else { firstLarge = firstInteger; } if (typeof secondOperandInteger === "number") { /* convert it to a not normalized LargeInteger */ secondLarge = createLargeFromSmallInteger(secondOperandInteger); } else { secondLarge = secondOperandInteger; } if (typeof thirdModuloInteger === "number") { /* convert it to a not normalized LargeInteger */ thirdLarge = createLargeFromSmallInteger(thirdModuloInteger); } else { thirdLarge = thirdModuloInteger; } _return_value = digitMontgomerytimesmodulomInvModB(firstLarge, secondLarge, thirdLarge, smallInverseInteger); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(4, _return_value); return null; } /* Parameter specification #(Integer) doesn't convert! */ function primNormalize() { var anInteger; var _return_value; interpreterProxy.success(interpreterProxy.isKindOfInteger(interpreterProxy.stackValue(0))); anInteger = interpreterProxy.stackValue(0); // missing DebugCode; if (interpreterProxy.failed()) { return null; } if (typeof anInteger === "number") { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, anInteger); return null; } _return_value = normalize(anInteger); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(2, _return_value); return null; } function primNormalizeNegative() { var rcvr; var _return_value; // missing DebugCode; interpreterProxy.success(interpreterProxy.stackValue(0).sqClass === interpreterProxy.classLargeNegativeInteger()); rcvr = interpreterProxy.stackValue(0); if (interpreterProxy.failed()) { return null; } _return_value = normalizeNegative(rcvr); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(1, _return_value); return null; } function primNormalizePositive() { var rcvr; var _return_value; // missing DebugCode; interpreterProxy.success(interpreterProxy.stackValue(0).sqClass === interpreterProxy.classLargePositiveInteger()); rcvr = interpreterProxy.stackValue(0); if (interpreterProxy.failed()) { return null; } _return_value = normalizePositive(rcvr); if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(1, _return_value); return null; } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } /* Argument bytesOop must not be aSmallInteger! */ function unsafeByteOfat(bytesOop, ix) { return ((bytesOop.bytes))[ix - 1]; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("LargeIntegers", { primDigitAddWith: primDigitAddWith, primDigitBitShiftMagnitude: primDigitBitShiftMagnitude, primGetModuleName: primGetModuleName, primDigitBitLogicWithOp: primDigitBitLogicWithOp, primCheckIfCModuleExists: primCheckIfCModuleExists, primDigitCompare: primDigitCompare, primDigitMultiplyNegative: primDigitMultiplyNegative, primDigitBitShift: primDigitBitShift, primNormalizePositive: primNormalizePositive, primDigitSubtractWith: primDigitSubtractWith, _primDigitBitShift: _primDigitBitShift, primDigitMultiplyWithNegative: primDigitMultiplyWithNegative, primDigitSubtract: primDigitSubtract, primDigitDivNegative: primDigitDivNegative, primNormalizeNegative: primNormalizeNegative, primDigitBitOr: primDigitBitOr, primMontgomeryTimesModulo: primMontgomeryTimesModulo, primDigitBitAnd: primDigitBitAnd, primDigitDivWithNegative: primDigitDivWithNegative, setInterpreter: setInterpreter, primNormalize: primNormalize, primDigitBitXor: primDigitBitXor, primDigitCompareWith: primDigitCompareWith, primDigitAdd: primDigitAdd, getModuleName: getModuleName, primAsLargeInteger: primAsLargeInteger, primAnyBitFromTo: primAnyBitFromTo, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:21 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from Matrix2x3Plugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function Matrix2x3Plugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; /*** Functions ***/ function CLASSOF(obj) { return typeof obj === "number" ? interpreterProxy.classSmallInteger() : obj.sqClass } function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } /*** Variables ***/ var interpreterProxy = null; var m23ArgX = 0; var m23ArgY = 0; var m23ResultX = 0; var m23ResultY = 0; var moduleName = "Matrix2x3Plugin 3 November 2014 (e)"; /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Load the argument matrix */ function loadArgumentMatrix(matrix) { if (interpreterProxy.failed()) { return null; } if (!(interpreterProxy.isWords(matrix) && (SIZEOF(matrix) === 6))) { interpreterProxy.primitiveFail(); return null; } return matrix.wordsAsFloat32Array(); } /* Load the argument point into m23ArgX and m23ArgY */ function loadArgumentPoint(point) { var isInt; var oop; if (interpreterProxy.failed()) { return null; } if (CLASSOF(point) !== interpreterProxy.classPoint()) { return interpreterProxy.primitiveFail(); } oop = interpreterProxy.fetchPointerofObject(0, point); isInt = typeof oop === "number"; if (!(isInt || (oop.isFloat))) { return interpreterProxy.primitiveFail(); } if (isInt) { m23ArgX = oop; } else { m23ArgX = interpreterProxy.floatValueOf(oop); } oop = interpreterProxy.fetchPointerofObject(1, point); isInt = typeof oop === "number"; if (!(isInt || (oop.isFloat))) { return interpreterProxy.primitiveFail(); } if (isInt) { m23ArgY = oop; } else { m23ArgY = interpreterProxy.floatValueOf(oop); } } /* Multiply matrix m1 with m2 and store the result into m3. */ function matrix2x3ComposeMatrixwithinto(m1, m2, m3) { var a11; var a12; var a13; var a21; var a22; var a23; a11 = (m1[0] * m2[0]) + (m1[1] * m2[3]); a12 = (m1[0] * m2[1]) + (m1[1] * m2[4]); a13 = ((m1[0] * m2[2]) + (m1[1] * m2[5])) + m1[2]; a21 = (m1[3] * m2[0]) + (m1[4] * m2[3]); a22 = (m1[3] * m2[1]) + (m1[4] * m2[4]); a23 = ((m1[3] * m2[2]) + (m1[4] * m2[5])) + m1[5]; m3[0] = a11; m3[1] = a12; m3[2] = a13; m3[3] = a21; m3[4] = a22; m3[5] = a23; } /* Invert the pre-loaded argument point by the given matrix */ function matrix2x3InvertPoint(m) { var det; var detX; var detY; var x; var y; x = m23ArgX - m[2]; y = m23ArgY - m[5]; det = (m[0] * m[4]) - (m[1] * m[3]); if (det === 0.0) { return interpreterProxy.primitiveFail(); } det = 1.0 / det; detX = (x * m[4]) - (m[1] * y); detY = (m[0] * y) - (x * m[3]); m23ResultX = detX * det; m23ResultY = detY * det; } /* Transform the pre-loaded argument point by the given matrix */ function matrix2x3TransformPoint(m) { m23ResultX = ((m23ArgX * m[0]) + (m23ArgY * m[1])) + m[2]; m23ResultY = ((m23ArgX * m[3]) + (m23ArgY * m[4])) + m[5]; } function okayIntValue(value) { return (value >= -1073741824) && (m23ResultX <= 1073741823); } function primitiveComposeMatrix(argCount) { var m1; var m2; var m3; var result; m3 = loadArgumentMatrix((result = interpreterProxy.stackObjectValue(0))); m2 = loadArgumentMatrix(interpreterProxy.stackObjectValue(1)); m1 = loadArgumentMatrix(interpreterProxy.stackObjectValue(2)); if (interpreterProxy.failed()) { return null; } matrix2x3ComposeMatrixwithinto(m1, m2, m3); interpreterProxy.popthenPush(argCount + 1, result); } function primitiveInvertPoint(argCount) { var matrix; loadArgumentPoint(interpreterProxy.stackObjectValue(0)); matrix = loadArgumentMatrix(interpreterProxy.stackObjectValue(1)); if (interpreterProxy.failed()) { return null; } matrix2x3InvertPoint(matrix); if (!interpreterProxy.failed()) { roundAndStoreResultPoint(argCount); } } function primitiveInvertRectInto(argCount) { var cornerX; var cornerY; var dstOop; var matrix; var maxX; var maxY; var minX; var minY; var originX; var originY; var srcOop; dstOop = interpreterProxy.stackObjectValue(0); srcOop = interpreterProxy.stackObjectValue(1); matrix = loadArgumentMatrix(interpreterProxy.stackObjectValue(2)); if (interpreterProxy.failed()) { return null; } if (CLASSOF(srcOop) !== CLASSOF(dstOop)) { return interpreterProxy.primitiveFail(); } if (!interpreterProxy.isPointers(srcOop)) { return interpreterProxy.primitiveFail(); } if (SIZEOF(srcOop) !== 2) { return interpreterProxy.primitiveFail(); } loadArgumentPoint(interpreterProxy.fetchPointerofObject(0, srcOop)); if (interpreterProxy.failed()) { return null; } originX = m23ArgX; originY = m23ArgY; matrix2x3InvertPoint(matrix); minX = (maxX = m23ResultX); /* Load bottom-right point */ minY = (maxY = m23ResultY); loadArgumentPoint(interpreterProxy.fetchPointerofObject(1, srcOop)); if (interpreterProxy.failed()) { return null; } cornerX = m23ArgX; cornerY = m23ArgY; matrix2x3InvertPoint(matrix); minX = Math.min(minX, m23ResultX); maxX = Math.max(maxX, m23ResultX); minY = Math.min(minY, m23ResultY); /* Load top-right point */ maxY = Math.max(maxY, m23ResultY); m23ArgX = cornerX; m23ArgY = originY; matrix2x3InvertPoint(matrix); minX = Math.min(minX, m23ResultX); maxX = Math.max(maxX, m23ResultX); minY = Math.min(minY, m23ResultY); /* Load bottom-left point */ maxY = Math.max(maxY, m23ResultY); m23ArgX = originX; m23ArgY = cornerY; matrix2x3InvertPoint(matrix); minX = Math.min(minX, m23ResultX); maxX = Math.max(maxX, m23ResultX); minY = Math.min(minY, m23ResultY); maxY = Math.max(maxY, m23ResultY); if (!interpreterProxy.failed()) { dstOop = roundAndStoreResultRectx0y0x1y1(dstOop, minX, minY, maxX, maxY); } if (!interpreterProxy.failed()) { interpreterProxy.popthenPush(argCount + 1, dstOop); } } function primitiveIsIdentity(argCount) { var matrix; matrix = loadArgumentMatrix(interpreterProxy.stackObjectValue(0)); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(1); interpreterProxy.pushBool((((((matrix[0] === 1.0) && (matrix[1] === 0.0)) && (matrix[2] === 0.0)) && (matrix[3] === 0.0)) && (matrix[4] === 1.0)) && (matrix[5] === 0.0)); } function primitiveIsPureTranslation(argCount) { var matrix; matrix = loadArgumentMatrix(interpreterProxy.stackObjectValue(0)); if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(1); interpreterProxy.pushBool((((matrix[0] === 1.0) && (matrix[1] === 0.0)) && (matrix[3] === 0.0)) && (matrix[4] === 1.0)); } function primitiveTransformPoint(argCount) { var matrix; loadArgumentPoint(interpreterProxy.stackObjectValue(0)); matrix = loadArgumentMatrix(interpreterProxy.stackObjectValue(1)); if (interpreterProxy.failed()) { return null; } matrix2x3TransformPoint(matrix); roundAndStoreResultPoint(argCount); } function primitiveTransformRectInto(argCount) { var cornerX; var cornerY; var dstOop; var matrix; var maxX; var maxY; var minX; var minY; var originX; var originY; var srcOop; dstOop = interpreterProxy.stackObjectValue(0); srcOop = interpreterProxy.stackObjectValue(1); matrix = loadArgumentMatrix(interpreterProxy.stackObjectValue(2)); if (interpreterProxy.failed()) { return null; } if (CLASSOF(srcOop) !== CLASSOF(dstOop)) { return interpreterProxy.primitiveFail(); } if (!interpreterProxy.isPointers(srcOop)) { return interpreterProxy.primitiveFail(); } if (SIZEOF(srcOop) !== 2) { return interpreterProxy.primitiveFail(); } loadArgumentPoint(interpreterProxy.fetchPointerofObject(0, srcOop)); if (interpreterProxy.failed()) { return null; } originX = m23ArgX; originY = m23ArgY; matrix2x3TransformPoint(matrix); minX = (maxX = m23ResultX); /* Load bottom-right point */ minY = (maxY = m23ResultY); loadArgumentPoint(interpreterProxy.fetchPointerofObject(1, srcOop)); if (interpreterProxy.failed()) { return null; } cornerX = m23ArgX; cornerY = m23ArgY; matrix2x3TransformPoint(matrix); minX = Math.min(minX, m23ResultX); maxX = Math.max(maxX, m23ResultX); minY = Math.min(minY, m23ResultY); /* Load top-right point */ maxY = Math.max(maxY, m23ResultY); m23ArgX = cornerX; m23ArgY = originY; matrix2x3TransformPoint(matrix); minX = Math.min(minX, m23ResultX); maxX = Math.max(maxX, m23ResultX); minY = Math.min(minY, m23ResultY); /* Load bottom-left point */ maxY = Math.max(maxY, m23ResultY); m23ArgX = originX; m23ArgY = cornerY; matrix2x3TransformPoint(matrix); minX = Math.min(minX, m23ResultX); maxX = Math.max(maxX, m23ResultX); minY = Math.min(minY, m23ResultY); maxY = Math.max(maxY, m23ResultY); dstOop = roundAndStoreResultRectx0y0x1y1(dstOop, minX, minY, maxX, maxY); if (!interpreterProxy.failed()) { interpreterProxy.popthenPush(argCount + 1, dstOop); } } /* Store the result of a previous operation. Fail if we cannot represent the result as SmallInteger */ function roundAndStoreResultPoint(argCount) { m23ResultX += 0.5; m23ResultY += 0.5; if (!okayIntValue(m23ResultX)) { return interpreterProxy.primitiveFail(); } if (!okayIntValue(m23ResultY)) { return interpreterProxy.primitiveFail(); } interpreterProxy.popthenPush(argCount + 1, interpreterProxy.makePointwithxValueyValue((m23ResultX|0), (m23ResultY|0))); } /* Check, round and store the result of a rectangle operation */ function roundAndStoreResultRectx0y0x1y1(dstOop, x0, y0, x1, y1) { var cornerOop; var maxX; var maxY; var minX; var minY; var originOop; var rectOop; minX = x0 + 0.5; if (!okayIntValue(minX)) { return interpreterProxy.primitiveFail(); } maxX = x1 + 0.5; if (!okayIntValue(maxX)) { return interpreterProxy.primitiveFail(); } minY = y0 + 0.5; if (!okayIntValue(minY)) { return interpreterProxy.primitiveFail(); } maxY = y1 + 0.5; if (!okayIntValue(maxY)) { return interpreterProxy.primitiveFail(); } interpreterProxy.pushRemappableOop(dstOop); originOop = interpreterProxy.makePointwithxValueyValue((minX|0), (minY|0)); interpreterProxy.pushRemappableOop(originOop); cornerOop = interpreterProxy.makePointwithxValueyValue((maxX|0), (maxY|0)); originOop = interpreterProxy.popRemappableOop(); rectOop = interpreterProxy.popRemappableOop(); interpreterProxy.storePointerofObjectwithValue(0, rectOop, originOop); interpreterProxy.storePointerofObjectwithValue(1, rectOop, cornerOop); return rectOop; } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("Matrix2x3Plugin", { primitiveInvertPoint: primitiveInvertPoint, primitiveInvertRectInto: primitiveInvertRectInto, primitiveIsIdentity: primitiveIsIdentity, primitiveComposeMatrix: primitiveComposeMatrix, setInterpreter: setInterpreter, primitiveTransformRectInto: primitiveTransformRectInto, primitiveIsPureTranslation: primitiveIsPureTranslation, getModuleName: getModuleName, primitiveTransformPoint: primitiveTransformPoint, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:23 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from MiscPrimitivePlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function MiscPrimitivePlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift /*** Variables ***/ var interpreterProxy = null; var moduleName = "MiscPrimitivePlugin 3 November 2014 (e)"; /* Copy the integer anInt into byteArray ba at index i, and return the next index */ function encodeBytesOfinat(anInt, ba, i) { var j; for (j = 0; j <= 3; j++) { ba[(i + j) - 1] = ((SHR(anInt, ((3 - j) * 8))) & 255); } return i + 4; } /* Encode the integer anInt in byteArray ba at index i, and return the next index. The encoding is as follows... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes */ function encodeIntinat(anInt, ba, i) { if (anInt <= 223) { ba[i - 1] = anInt; return i + 1; } if (anInt <= 7935) { ba[i - 1] = ((anInt >> 8) + 224); ba[i] = (MOD(anInt, 256)); return i + 2; } ba[i - 1] = 255; return encodeBytesOfinat(anInt, ba, i + 1); } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Return 1, 2 or 3, if string1 is <, =, or > string2, with the collating order of characters given by the order array. */ function primitiveCompareString(argCount) { var string1; var string2; var order; var c1; var c2; var i; var len1; var len2; interpreterProxy.stackValue(3); string1 = interpreterProxy.stackBytes(2); string2 = interpreterProxy.stackBytes(1); order = interpreterProxy.stackBytes(0); if (interpreterProxy.failed()) { return null; } len1 = string1.length; len2 = string2.length; for (i = 1; i <= Math.min(len1, len2); i++) { c1 = order[string1[i - 1]]; c2 = order[string2[i - 1]]; if (c1 !== c2) { if (c1 < c2) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 1); return null; } else { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 3); return null; } } } if (len1 === len2) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 2); return null; } if (len1 < len2) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 1); return null; } else { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 3); return null; } } /* Store a run-coded compression of the receiver into the byteArray ba, and return the last index stored into. ba is assumed to be large enough. The encoding is as follows... S {N D}*. S is the size of the original bitmap, followed by run-coded pairs. N is a run-length * 4 + data code. D, the data, depends on the data code... 0 skip N words, D is absent 1 N words with all 4 bytes = D (1 byte) 2 N words all = D (4 bytes) 3 N words follow in D (4N bytes) S and N are encoded as follows... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes */ function primitiveCompressToByteArray(argCount) { var bm; var ba; var eqBytes; var i; var j; var k; var lowByte; var m; var size; var word; interpreterProxy.stackValue(2); bm = interpreterProxy.stackInt32Array(1); ba = interpreterProxy.stackBytes(0); if (interpreterProxy.failed()) { return null; } size = bm.length; i = encodeIntinat(size, ba, 1); k = 1; while (k <= size) { word = bm[k - 1]; lowByte = word & 255; eqBytes = (((word >>> 8) & 255) === lowByte) && ((((word >>> 16) & 255) === lowByte) && (((word >>> 24) & 255) === lowByte)); j = k; while ((j < size) && (word === bm[j])) { ++j; } if (j > k) { /* We have two or more = words, ending at j */ if (eqBytes) { /* Actually words of = bytes */ i = encodeIntinat((((j - k) + 1) * 4) + 1, ba, i); ba[i - 1] = lowByte; ++i; } else { i = encodeIntinat((((j - k) + 1) * 4) + 2, ba, i); i = encodeBytesOfinat(word, ba, i); } k = j + 1; } else { /* Check for word of 4 = bytes */ if (eqBytes) { /* Note 1 word of 4 = bytes */ i = encodeIntinat((1 * 4) + 1, ba, i); ba[i - 1] = lowByte; ++i; ++k; } else { /* Finally, check for junk */ while ((j < size) && (bm[j - 1] !== bm[j])) { ++j; } if (j === size) { ++j; } i = encodeIntinat(((j - k) * 4) + 3, ba, i); for (m = k; m <= (j - 1); m++) { i = encodeBytesOfinat(bm[m - 1], ba, i); } k = j; } } } if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, i - 1); return null; } /* Copy the contents of the given array of signed 8-bit samples into the given array of 16-bit signed samples. */ function primitiveConvert8BitSigned(argCount) { var aByteArray; var aSoundBuffer; var i; var n; var s; interpreterProxy.stackValue(2); aByteArray = interpreterProxy.stackBytes(1); aSoundBuffer = interpreterProxy.stackUint16Array(0); if (interpreterProxy.failed()) { return null; } n = aByteArray.length; for (i = 1; i <= n; i++) { s = aByteArray[i - 1]; if (s > 127) { aSoundBuffer[i - 1] = ((s - 256) << 8); } else { aSoundBuffer[i - 1] = (s << 8); } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(argCount); } /* Decompress the body of a byteArray encoded by compressToByteArray (qv)... The format is simply a sequence of run-coded pairs, {N D}*. N is a run-length * 4 + data code. D, the data, depends on the data code... 0 skip N words, D is absent (could be used to skip from one raster line to the next) 1 N words with all 4 bytes = D (1 byte) 2 N words all = D (4 bytes) 3 N words follow in D (4N bytes) S and N are encoded as follows (see decodeIntFrom:)... 0-223 0-223 224-254 (0-30)*256 + next byte (0-7935) 255 next 4 bytes */ /* NOTE: If fed with garbage, this routine could read past the end of ba, but it should fail before writing past the ned of bm. */ function primitiveDecompressFromByteArray(argCount) { var bm; var ba; var index; var anInt; var code; var data; var end; var i; var j; var k; var m; var n; var pastEnd; interpreterProxy.stackValue(3); bm = interpreterProxy.stackInt32Array(2); ba = interpreterProxy.stackBytes(1); index = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } /* byteArray read index */ i = index; end = ba.length; /* bitmap write index */ k = 1; pastEnd = bm.length + 1; while (i <= end) { /* Decode next run start N */ anInt = ba[i - 1]; ++i; if (!(anInt <= 223)) { if (anInt <= 254) { anInt = ((anInt - 224) * 256) + ba[i - 1]; ++i; } else { anInt = 0; for (j = 1; j <= 4; j++) { anInt = (anInt << 8) + ba[i - 1]; ++i; } } } n = anInt >>> 2; if ((k + n) > pastEnd) { interpreterProxy.primitiveFail(); return null; } code = anInt & 3; if (code === 1) { /* n consecutive words of 4 bytes = the following byte */ data = ba[i - 1]; ++i; data = data | (data << 8); data = data | (data << 16); for (j = 1; j <= n; j++) { bm[k - 1] = data; ++k; } } if (code === 2) { /* n consecutive words = 4 following bytes */ data = 0; for (j = 1; j <= 4; j++) { data = (data << 8) | ba[i - 1]; ++i; } for (j = 1; j <= n; j++) { bm[k - 1] = data; ++k; } } if (code === 3) { /* n consecutive words from the data... */ for (m = 1; m <= n; m++) { data = 0; for (j = 1; j <= 4; j++) { data = (data << 8) | ba[i - 1]; ++i; } bm[k - 1] = data; ++k; } } } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(argCount); } function primitiveFindFirstInString(argCount) { var aString; var inclusionMap; var start; var i; var stringSize; interpreterProxy.stackValue(3); aString = interpreterProxy.stackBytes(2); inclusionMap = interpreterProxy.stackBytes(1); start = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } if (inclusionMap.length !== 256) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 0); return null; } i = start; stringSize = aString.length; while ((i <= stringSize) && (inclusionMap[aString[i - 1]] === 0)) { ++i; } if (i > stringSize) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 0); return null; } if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, i); return null; } /* Answer the index in the string body at which the substring key first occurs, at or beyond start. The match is determined using matchTable, which can be used to effect, eg, case-insensitive matches. If no match is found, zero will be returned. The algorithm below is not optimum -- it is intended to be translated to C which will go so fast that it wont matter. */ function primitiveFindSubstring(argCount) { var key; var body; var start; var matchTable; var index; var startIndex; interpreterProxy.stackValue(4); key = interpreterProxy.stackBytes(3); body = interpreterProxy.stackBytes(2); start = interpreterProxy.stackIntegerValue(1); matchTable = interpreterProxy.stackBytes(0); if (interpreterProxy.failed()) { return null; } if (key.length === 0) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 0); return null; } for (startIndex = start; startIndex <= ((body.length - key.length) + 1); startIndex++) { index = 1; while (matchTable[body[((startIndex + index) - 1) - 1]] === matchTable[key[index - 1]]) { if (index === key.length) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, startIndex); return null; } ++index; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 0); return null; } function primitiveIndexOfAsciiInString(argCount) { var anInteger; var aString; var start; var pos; var stringSize; interpreterProxy.stackValue(3); anInteger = interpreterProxy.stackIntegerValue(2); aString = interpreterProxy.stackBytes(1); start = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } stringSize = aString.length; for (pos = start; pos <= stringSize; pos++) { if (aString[pos - 1] === anInteger) { if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, pos); return null; } } if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, 0); return null; } /* Answer the hash of a byte-indexed collection, using speciesHash as the initial value. See SmallInteger>>hashMultiply. The primitive should be renamed at a suitable point in the future */ function primitiveStringHash(argCount) { var aByteArray; var speciesHash; var byteArraySize; var hash; var low; var pos; interpreterProxy.stackValue(2); aByteArray = interpreterProxy.stackBytes(1); speciesHash = interpreterProxy.stackIntegerValue(0); if (interpreterProxy.failed()) { return null; } byteArraySize = aByteArray.length; hash = speciesHash & 268435455; for (pos = 1; pos <= byteArraySize; pos++) { /* Begin hashMultiply */ hash += aByteArray[pos - 1]; low = hash & 16383; hash = ((9741 * low) + ((((9741 * (hash >>> 14)) + (101 * low)) & 16383) * 16384)) & 268435455; } if (interpreterProxy.failed()) { return null; } interpreterProxy.popthenPush(argCount + 1, hash); return null; } /* translate the characters in the string by the given table, in place */ function primitiveTranslateStringWithTable(argCount) { var aString; var start; var stop; var table; var i; interpreterProxy.stackValue(4); aString = interpreterProxy.stackBytes(3); start = interpreterProxy.stackIntegerValue(2); stop = interpreterProxy.stackIntegerValue(1); table = interpreterProxy.stackBytes(0); if (interpreterProxy.failed()) { return null; } for (i = start; i <= stop; i++) { aString[i - 1] = table[aString[i - 1]]; } if (interpreterProxy.failed()) { return null; } interpreterProxy.pop(argCount); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("MiscPrimitivePlugin", { primitiveConvert8BitSigned: primitiveConvert8BitSigned, primitiveCompareString: primitiveCompareString, primitiveTranslateStringWithTable: primitiveTranslateStringWithTable, primitiveStringHash: primitiveStringHash, primitiveCompressToByteArray: primitiveCompressToByteArray, primitiveFindSubstring: primitiveFindSubstring, primitiveIndexOfAsciiInString: primitiveIndexOfAsciiInString, setInterpreter: setInterpreter, primitiveDecompressFromByteArray: primitiveDecompressFromByteArray, getModuleName: getModuleName, primitiveFindFirstInString: primitiveFindFirstInString, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ function MIDIPlugin() { const MIDI = midiParameterConstants(); return { debug: false, vmProxy: null, vm: null, prims: null, timeOffset: 0, midi: null, // WebMIDI access or false if not supported midiPromise: null, ports: new Map(), // indexed by Squeak port number getModuleName() { return 'MIDIPlugin (SqueakJS)'; }, setInterpreter(vmProxy) { this.vmProxy = vmProxy; this.vm = vmProxy.vm; this.prims = vmProxy.vm.primHandler; return true; }, initialiseModule() { this.debug = this.vm.options.debugMIDI; if (!navigator.requestMIDIAccess) { console.log('MIDIPlugin: WebMIDI not supported'); this.vmProxy.success(false); return; } if (!this.midiPromise) { this.midiPromise = navigator.requestMIDIAccess({ software: true, // because why not sysex: false, // if you change this, tweak the running status handling }) .then(access => { this.midi = access; this.initMIDI(access); }) .catch(err => { console.error('MIDIPlugin: ' + err); this.midi = false; }); } if (performance.timeOrigin) this.timeOffset = performance.timeOrigin - this.vm.startupTime; }, initMIDI(access) { const allPorts = [...access.inputs.values(), ...access.outputs.values()]; for (const port of allPorts) this.portChanged(port); access.onstatechange = (event) => { const port = event.port; let { name, manufacturer, state } = port; if (manufacturer && !name.includes(manufacturer)) name += ` (${manufacturer})`; const sqPort = this.portChanged(port); const isNew = !port.sqPort; if (isNew) port.sqPort = sqPort; if (isNew || state === 'disconnected' || this.debug) { console.log(`MIDIPlugin: ${name} ${state} (port ${sqPort.handle} ${port.type} ${port.connection})`); } }; console.log(`MIDIPlugin: WebMIDI initialized (ports: ${this.ports.size})`); for (const [portNumber, port] of this.ports) { const dir = port.dir === 3 ? 'in+out' : port.dir === 2 ? 'out' : 'in'; const names = []; if (port.input) names.push(port.input.name); if (port.output) names.push(port.output.name); console.log(`MIDIPlugin: port ${portNumber} ${dir} (${names.join(', ')})`); } }, portChanged(port) { // Squeak likes combined input/output ports so we create sqPorts with input+output here let { name, manufacturer } = port; // strip input / output designation name = name.replace(/(\b(in|out)(put)?\b)/i, '').replace(/(\(\)|\[\])/, '').replace(/ /, " ").trim(); if (manufacturer && !name.includes(manufacturer)) name += ` (${manufacturer})`; // find existing port or create new one let sqPort; for (const existingPort of this.ports.values()) { if (existingPort.name === name) { sqPort = existingPort; break; } } if (!sqPort) { const handle = this.ports.size; sqPort = { handle, name, dir: 0, input: null, output: null, runningStatus: 0, receivedMessages: [], }; this.ports.set(handle, sqPort); } // dir: 1=input, 2=output, 3=input+output if (port.state === "connected") { sqPort[port.type] = port; sqPort.dir |= port.type === 'input' ? 1 : 2; } else { sqPort[port.type] = null; sqPort.dir &= port.type === 'input' ? -2 : -3; } return sqPort; }, primitiveMIDIGetPortCount(argCount) { // we rely on this primitive to be called first // so the other primitives can be synchronous const returnCount = () => this.vm.popNandPush(argCount + 1, this.ports.size); if (this.midi === null) { const unfreeze = this.vm.freeze(); this.midiPromise .then(returnCount) .catch(err => { console.error('MIDIPlugin: ' + err); returnCount(); }) .finally(unfreeze); } else { returnCount(); } return true; }, primitiveMIDIGetPortName(argCount) { if (!this.midi) return false; const portNumber = this.prims.stackInteger(0); const port = this.ports.get(portNumber); if (!port) return false; let name = port.name; if (port.dir === 0) name += ' [disconnected]'; return this.prims.popNandPushIfOK(argCount + 1, this.prims.makeStString(name)); }, primitiveMIDIGetPortDirectionality(argCount) { if (!this.midi) return false; const portNumber = this.prims.stackInteger(0); const port = this.ports.get(portNumber); if (!port) return false; return this.prims.popNandPushIfOK(argCount + 1, port.dir); }, primitiveMIDIGetClock(argCount) { if (!this.midi) return false; const clock = this.prims.millisecondClockValue(); return this.prims.popNandPushIfOK(argCount + 1, clock); }, primitiveMIDIParameterGetOrSet(argCount) { if (!this.midi) return false; const parameter = this.prims.stackInteger(argCount - 1); // const newValue = argCount > 1 ? this.prims.stackInteger(0) : null; let value; // mostly untested, because I found no Squeak app that actually uses these switch (parameter) { case MIDI.Installed: value = 1; break case MIDI.Version: value = 1; break; case MIDI.HasBuffer: case MIDI.HasDurs: case MIDI.CanSetClock: case MIDI.CanUseSemaphore: case MIDI.EchoOn: case MIDI.UseControllerCache: case MIDI.EventsAvailable: case MIDI.FlushDriver: value = 0; break; case MIDI.ClockTicksPerSec: value = 1000; break; case MIDI.HasInputClock: value = 1; break; default: return false; } return this.prims.popNandPushIfOK(argCount + 1, value); }, primitiveMIDIOpenPort(argCount) { const portNumber = this.prims.stackInteger(2); // const readSemaIndex = this.prims.stackInteger(1); // ignored // const interfaceClockRate = this.prims.stackInteger(0); // ignored let port; const checkPort = () => { port = this.ports.get(portNumber); if (!port) console.error(`MIDIPlugin: invalid port ${portNumber}`); else if (!port.dir) { console.error(`MIDIPlugin: port ${portNumber} ${port.name} is disconnected`); port = null; } }; const openPort = unfreeze => { const promises = []; // wait for MIDI initialization first if (port.input) if (port.input.connection === "closed") promises.push(port.input.open()); else console.warn(`MIDIPlugin: input port ${portNumber} is ${port.input.connection}`); if (port.output) if (port.output.connection === "closed") promises.push(port.output.open()); else console.warn(`MIDIPlugin: output port ${portNumber} is ${port.output.connection}`); port.runningStatus = 0; port.receivedMessages = []; Promise.all(promises) .then(() => { if (port.input) port.input.onmidimessage = event => { const time = Math.round(event.timeStamp + this.timeOffset); const bytes = new Uint8Array(event.data); port.receivedMessages.push({time, bytes}); if (this.debug) console.log('MIDIPlugin: received', time, [...bytes]); }; }) .catch(err => console.error('MIDIPlugin: ' + err)) .finally(unfreeze); }; // if already initialized, report failure immediately if (this.midi) { checkPort(); if (!port) return false; } // otherwise, we wait for initialization const unfreeze = this.vm.freeze(); this.midiPromise .then(() => { if (!port) checkPort(); if (port) openPort(unfreeze); else unfreeze(); }); return this.prims.popNIfOK(argCount); }, primitiveMIDIClosePort(argCount) { // ok to close even if not initialized if (this.midi) { const portNumber = this.prims.stackInteger(0); const port = this.ports.get(portNumber); if (!port) return false; const promises = []; if (port.input && port.input.connection === 'open') { promises.push(port.input.close()); port.input.onmidimessage = null; port.receivedMessages.length = 0; } if (port.output && port.output.connection === 'open') { promises.push(port.output.close()); } if (promises.length) { const unfreeze = this.vm.freeze(); Promise.all(promises) .catch(err => console.error('MIDIPlugin: ' + err)) .finally(unfreeze); } } return this.prims.popNIfOK(argCount); }, primitiveMIDIWrite(argCount) { if (!this.midi) return false; const portNumber = this.prims.stackInteger(2); let data = this.prims.stackNonInteger(1).bytes; const timestamp = this.prims.stackInteger(0); const port = this.ports.get(portNumber); if (!port || !port.output || !data) return false; if (port.output.connection !== 'open') { console.error('MIDIPlugin: primitiveMIDIWrite error (port not open)'); return this.prims.popNandPushIfOK(argCount + 1, 0); } // this could be simple if it were not for the running status // WebMIDI insists the first byte is a status byte // so we need to keep track of it, and prepend it if necessary if (data[0] < 0x80) { if (port.runningStatus === 0) { console.error('MIDIPlugin: no running status byte'); return false; } const newData = new Uint8Array(data.length + 1); newData[0] = port.runningStatus; newData.set(data, 1); data = newData; } try { if (this.debug) console.log('MIDIPlugin: send', [...data], timestamp); // send or schedule data if (timestamp === 0) port.output.send(data); else port.output.send(data, timestamp); // find last status byte in data, but ignore real-time messages (0xF8-0xFF) // system common messages (0xF0-0xF7) reset the running status for (let i = data.length - 1; i >= 0; i--) { if (data[i] >= 0x80 && data[i] <= 0xF7) { port.runningStatus = data[i] < 0xF0 ? data[i] : 0; break; } } } catch (err) { console.error('MIDIPlugin: ' + err); return false; } return this.prims.popNandPushIfOK(argCount + 1, data.length); }, primitiveMIDIRead(argCount) { if (!this.midi) return false; const portNumber = this.prims.stackInteger(1); const data = this.prims.stackNonInteger(0).bytes; const port = this.ports.get(portNumber); if (!port || !port.input || port.input.connection !== 'open') return false; let received = 0; const event = port.receivedMessages.shift(); if (event) { let { time, bytes } = event; data[0] = (time >> 24) & 0xFF; data[1] = (time >> 16) & 0xFF; data[2] = (time >> 8) & 0xFF; data[3] = time & 0xFF; data.set(bytes, 4); received = bytes.length + 4; if (this.debug) console.log('MIDIPlugin: read', received, [...data.subarray(0, received)]); } return this.prims.popNandPushIfOK(argCount + 1, received); }, }; } function midiParameterConstants() { // MIDI parameter key constants // see primitiveMIDIParameterGetOrSet() for SqueakJS values return { Installed: 1, // Read-only. Return 1 if a MIDI driver is installed, 0 if not. // On OMS-based MIDI drivers, this returns 1 only if the OMS // system is properly installed and configured. Version: 2, // Read-only. Return the integer version number of this MIDI driver. // The version numbering sequence is relative to a particular driver. // That is, version 3 of the Macintosh MIDI driver is not necessarily // related to version 3 of the Win95 MIDI driver. HasBuffer: 3, // Read-only. Return 1 if this MIDI driver has a time-stamped output // buffer, 0 otherwise. Such a buffer allows the client to schedule // MIDI output packets to be sent later. This can allow more precise // timing, since the driver uses timer interrupts to send the data // at the right time even if the processor is in the midst of a // long-running Squeak primitive or is running some other application // or system task. HasDurs: 4, // Read-only. Return 1 if this MIDI driver supports an extended // primitive for note-playing that includes the note duration and // schedules both the note-on and the note-off messages in the // driver. Otherwise, return 0. CanSetClock: 5, // Read-only. Return 1 if this MIDI driver's clock can be set // via an extended primitive, 0 if not. CanUseSemaphore: 6, // Read-only. Return 1 if this MIDI driver can signal a semaphore // when MIDI input arrives. Otherwise, return 0. If this driver // supports controller caching and it is enabled, then incoming // controller messages will not signal the semaphore. EchoOn: 7, // Read-write. If this flag is set to a non-zero value, and if // the driver supports echoing, then incoming MIDI events will // be echoed immediately. If this driver does not support echoing, // then queries of this parameter will always return 0 and // attempts to change its value will do nothing. UseControllerCache: 8, // Read-write. If this flag is set to a non-zero value, and if // the driver supports a controller cache, then the driver will // maintain a cache of the latest value seen for each MIDI controller, // and control update messages will be filtered out of the incoming // MIDI stream. An extended MIDI primitive allows the client to // poll the driver for the current value of each controller. If // this driver does not support a controller cache, then queries // of this parameter will always return 0 and attempts to change // its value will do nothing. EventsAvailable: 9, // Read-only. Return the number of MIDI packets in the input queue. FlushDriver: 10, // Write-only. Setting this parameter to any value forces the driver // to flush its I/0 buffer, discarding all unprocessed data. Reading // this parameter returns 0. Setting this parameter will do nothing // if the driver does not support buffer flushing. ClockTicksPerSec: 11, // Read-only. Return the MIDI clock rate in ticks per second. HasInputClock: 12, // Read-only. Return 1 if this MIDI driver timestamps incoming // MIDI data with the current value of the MIDI clock, 0 otherwise. // If the driver does not support such timestamping, then the // client must read input data frequently and provide its own // timestamping. }; } function registerMIDIPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('MIDIPlugin', MIDIPlugin()); } else self.setTimeout(registerMIDIPlugin, 100); } registerMIDIPlugin(); /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:23 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from ScratchPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function ScratchPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus /*** Variables ***/ var interpreterProxy = null; var moduleName = "ScratchPlugin 3 November 2014 (e)"; function bitmapatputHsv(bitmap, i, hue, saturation, brightness) { var hF; var hI; var outPix; var p; var q; var t; var v; /* integer part of hue (0..5) */ hI = DIV(hue, 60); /* fractional part ofhue */ hF = MOD(hue, 60); p = (1000 - saturation) * brightness; q = (1000 - (DIV((saturation * hF), 60))) * brightness; t = (1000 - (DIV((saturation * (60 - hF)), 60))) * brightness; v = DIV((brightness * 1000), 3922); p = DIV(p, 3922); q = DIV(q, 3922); t = DIV(t, 3922); if (0 === hI) { outPix = ((v << 16) + (t << 8)) + p; } if (1 === hI) { outPix = ((q << 16) + (v << 8)) + p; } if (2 === hI) { outPix = ((p << 16) + (v << 8)) + t; } if (3 === hI) { outPix = ((p << 16) + (q << 8)) + v; } if (4 === hI) { outPix = ((t << 16) + (p << 8)) + v; } if (5 === hI) { outPix = ((v << 16) + (p << 8)) + q; } if (outPix === 0) { outPix = 1; } bitmap[i] = outPix; return 0; } /* Return an unsigned int pointer to the first indexable word of oop, which must be a words object. */ function checkedFloatPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWordsOrBytes(oop)); if (interpreterProxy.failed()) { return 0; } return oop.wordsAsFloat64Array(); } /* Return an unsigned int pointer to the first indexable word of oop, which must be a words object. */ function checkedUnsignedIntPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWords(oop)); if (interpreterProxy.failed()) { return 0; } return oop.words; } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Answer the hue, an angle between 0 and 360. */ function hueFromRGBminmax(r, g, b, min, max) { var result; var span; span = max - min; if (span === 0) { return 0; } if (r === max) { result = DIV((60 * (g - b)), span); } else { if (g === max) { result = 120 + (DIV((60 * (b - r)), span)); } else { result = 240 + (DIV((60 * (r - g)), span)); } } if (result < 0) { return result + 360; } return result; } /* Answer the interpolated pixel value between the given two pixel values. If either pixel is zero (transparent) answer the other pixel. If both pixels are transparent, answer transparent. The fraction is between 0 and 1023, out of a total range of 1024. */ function interpolateandfrac(pix1, pix2, frac2) { var b; var frac1; var g; var r; var result; if (pix1 === 0) { return pix2; } if (pix2 === 0) { return pix1; } frac1 = 1024 - frac2; r = ((frac1 * ((pix1 >>> 16) & 255)) + (frac2 * ((pix2 >>> 16) & 255))) >> 10; g = ((frac1 * ((pix1 >>> 8) & 255)) + (frac2 * ((pix2 >>> 8) & 255))) >> 10; b = ((frac1 * (pix1 & 255)) + (frac2 * (pix2 & 255))) >> 10; result = ((r << 16) + (g << 8)) + b; if (result === 0) { result = 1; } return result; } /* Answer the interpolated pixel value from the given bitmap at the given point. The x and y coordinates are fixed-point integers with 10 bits of fraction (i.e. they were multiplied by 1024, then truncated). If the given point is right on an edge, answer the nearest edge pixel value. If it is entirely outside of the image, answer 0 (transparent). */ function interpolatedFromxywidthheight(bitmap, xFixed, yFixed, w, h) { var bottomPix; var index; var topPix; var x; var xFrac; var y; var yFrac; x = xFixed >>> 10; if ((x < -1) || (x >= w)) { return 0; } y = yFixed >>> 10; if ((y < -1) || (y >= h)) { return 0; } xFrac = xFixed & 1023; if (x === -1) { x = 0; xFrac = 0; } if (x === (w - 1)) { xFrac = 0; } yFrac = yFixed & 1023; if (y === -1) { y = 0; yFrac = 0; } if (y === (h - 1)) { yFrac = 0; } /* for squeak: + 1 */ index = (y * w) + x; topPix = bitmap[index] & 16777215; if (xFrac > 0) { topPix = interpolateandfrac(topPix, bitmap[index + 1] & 16777215, xFrac); } if (yFrac === 0) { return topPix; } /* for squeak: + 1 */ index = ((y + 1) * w) + x; bottomPix = bitmap[index] & 16777215; if (xFrac > 0) { bottomPix = interpolateandfrac(bottomPix, bitmap[index + 1] & 16777215, xFrac); } return interpolateandfrac(topPix, bottomPix, yFrac); } function primitiveBlur() { var bTotal; var dX; var dY; var gTotal; var height; var in_; var inOop; var n; var out; var outOop; var outPix; var pix; var rTotal; var sz; var width; var x; var y; inOop = interpreterProxy.stackValue(2); outOop = interpreterProxy.stackValue(1); width = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); out = checkedUnsignedIntPtrOf(outOop); sz = SIZEOF(inOop); interpreterProxy.success(SIZEOF(outOop) === sz); if (interpreterProxy.failed()) { return null; } height = DIV(sz, width); for (y = 1; y <= (height - 2); y++) { for (x = 1; x <= (width - 2); x++) { n = (rTotal = (gTotal = (bTotal = 0))); for (dY = -1; dY <= 1; dY++) { for (dX = -1; dX <= 1; dX++) { /* add 1 when testing in Squeak */ pix = in_[((y + dY) * width) + (x + dX)] & 16777215; if (pix !== 0) { /* skip transparent pixels */ rTotal += (pix >>> 16) & 255; gTotal += (pix >>> 8) & 255; bTotal += pix & 255; ++n; } } } if (n === 0) { outPix = 0; } else { outPix = (((DIV(rTotal, n)) << 16) + ((DIV(gTotal, n)) << 8)) + (DIV(bTotal, n)); } out[(y * width) + x] = outPix; } } interpreterProxy.pop(3); return 0; } function primitiveBrightnessShift() { var b; var brightness; var g; var hue; var i; var in_; var inOop; var max; var min; var out; var outOop; var pix; var r; var saturation; var shift; var sz; inOop = interpreterProxy.stackValue(2); outOop = interpreterProxy.stackValue(1); shift = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); sz = SIZEOF(inOop); out = checkedUnsignedIntPtrOf(outOop); interpreterProxy.success(SIZEOF(outOop) === sz); if (interpreterProxy.failed()) { return null; } for (i = 0; i <= (sz - 1); i++) { pix = in_[i] & 16777215; if (pix !== 0) { /* skip pixel values of 0 (transparent) */ r = (pix >>> 16) & 255; g = (pix >>> 8) & 255; /* find min and max color components */ b = pix & 255; max = (min = r); if (g > max) { max = g; } if (b > max) { max = b; } if (g < min) { min = g; } if (b < min) { min = b; } /* find current saturation and brightness with range 0 to 1000 */ hue = hueFromRGBminmax(r, g, b, min, max); if (max === 0) { saturation = 0; } else { saturation = DIV(((max - min) * 1000), max); } /* compute new brigthness */ brightness = DIV((max * 1000), 255); brightness += shift * 10; if (brightness > 1000) { brightness = 1000; } if (brightness < 0) { brightness = 0; } bitmapatputHsv(out, i, hue, saturation, brightness); } } interpreterProxy.pop(3); return 0; } function primitiveCondenseSound() { var count; var dst; var dstOop; var factor; var i; var j; var max; var src; var srcOop; var sz; var v; var _src = 0; var _dst = 0; srcOop = interpreterProxy.stackValue(2); dstOop = interpreterProxy.stackValue(1); factor = interpreterProxy.stackIntegerValue(0); interpreterProxy.success(interpreterProxy.isWords(srcOop)); interpreterProxy.success(interpreterProxy.isWords(dstOop)); count = DIV((2 * SIZEOF(srcOop)), factor); sz = 2 * SIZEOF(dstOop); interpreterProxy.success(sz >= count); if (interpreterProxy.failed()) { return null; } src = srcOop.wordsAsInt16Array(); dst = dstOop.wordsAsInt16Array(); for (i = 1; i <= count; i++) { max = 0; for (j = 1; j <= factor; j++) { v = src[_src++]; if (v < 0) { v = 0 - v; } if (v > max) { max = v; } } dst[_dst++] = max; } interpreterProxy.pop(3); return 0; } function primitiveDoubleSize() { var baseIndex; var dstX; var dstY; var i; var in_; var inH; var inOop; var inW; var out; var outH; var outOop; var outW; var pix; var x; var y; inOop = interpreterProxy.stackValue(7); inW = interpreterProxy.stackIntegerValue(6); inH = interpreterProxy.stackIntegerValue(5); outOop = interpreterProxy.stackValue(4); outW = interpreterProxy.stackIntegerValue(3); outH = interpreterProxy.stackIntegerValue(2); dstX = interpreterProxy.stackIntegerValue(1); dstY = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); out = checkedUnsignedIntPtrOf(outOop); interpreterProxy.success((dstX + (2 * inW)) < outW); interpreterProxy.success((dstY + (2 * inH)) < outH); if (interpreterProxy.failed()) { return null; } for (y = 0; y <= (inH - 1); y++) { baseIndex = ((dstY + (2 * y)) * outW) + dstX; for (x = 0; x <= (inW - 1); x++) { pix = in_[x + (y * inW)]; i = baseIndex + (2 * x); out[i] = pix; out[i + 1] = pix; out[i + outW] = pix; out[(i + outW) + 1] = pix; } } interpreterProxy.pop(8); return 0; } function primitiveExtractChannel() { var dst; var dstOop; var i; var rightFlag; var src; var srcOop; var sz; var _src = 0; var _dst = 0; srcOop = interpreterProxy.stackValue(2); dstOop = interpreterProxy.stackValue(1); rightFlag = interpreterProxy.booleanValueOf(interpreterProxy.stackValue(0)); interpreterProxy.success(interpreterProxy.isWords(srcOop)); interpreterProxy.success(interpreterProxy.isWords(dstOop)); sz = SIZEOF(srcOop); interpreterProxy.success(SIZEOF(dstOop) >= (sz >> 1)); if (interpreterProxy.failed()) { return null; } src = srcOop.wordsAsInt16Array(); dst = dstOop.wordsAsInt16Array(); if (rightFlag) { _src++; } for (i = 1; i <= sz; i++) { dst[_dst++] = src[_src]; _src += 2; } interpreterProxy.pop(3); return 0; } function primitiveFisheye() { var ang; var centerX; var centerY; var dx; var dy; var height; var in_; var inOop; var out; var outOop; var pix; var power; var r; var scaledPower; var srcX; var srcY; var sz; var width; var x; var y; inOop = interpreterProxy.stackValue(3); outOop = interpreterProxy.stackValue(2); width = interpreterProxy.stackIntegerValue(1); power = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); out = checkedUnsignedIntPtrOf(outOop); sz = SIZEOF(inOop); interpreterProxy.success(SIZEOF(outOop) === sz); if (interpreterProxy.failed()) { return null; } height = DIV(sz, width); centerX = width >> 1; centerY = height >> 1; height = DIV(sz, width); centerX = width >> 1; centerY = height >> 1; scaledPower = power / 100.0; for (x = 0; x <= (width - 1); x++) { for (y = 0; y <= (height - 1); y++) { dx = (x - centerX) / centerX; dy = (y - centerY) / centerY; r = Math.pow(Math.sqrt((dx * dx) + (dy * dy)),scaledPower); if (r <= 1.0) { ang = Math.atan2(dy,dx); srcX = ((1024 * (centerX + ((r * Math.cos(ang)) * centerX)))|0); srcY = ((1024 * (centerY + ((r * Math.sin(ang)) * centerY)))|0); } else { srcX = 1024 * x; srcY = 1024 * y; } pix = interpolatedFromxywidthheight(in_, srcX, srcY, width, height); out[(y * width) + x] = pix; } } interpreterProxy.pop(4); return 0; } function primitiveHalfSizeAverage() { var b; var dstH; var dstIndex; var dstW; var dstX; var dstY; var g; var in_; var inH; var inW; var out; var outH; var outW; var pixel; var r; var srcIndex; var srcX; var srcY; var x; var y; in_ = checkedUnsignedIntPtrOf(interpreterProxy.stackValue(11)); inW = interpreterProxy.stackIntegerValue(10); inH = interpreterProxy.stackIntegerValue(9); out = checkedUnsignedIntPtrOf(interpreterProxy.stackValue(8)); outW = interpreterProxy.stackIntegerValue(7); outH = interpreterProxy.stackIntegerValue(6); srcX = interpreterProxy.stackIntegerValue(5); srcY = interpreterProxy.stackIntegerValue(4); dstX = interpreterProxy.stackIntegerValue(3); dstY = interpreterProxy.stackIntegerValue(2); dstW = interpreterProxy.stackIntegerValue(1); dstH = interpreterProxy.stackIntegerValue(0); interpreterProxy.success((srcX >= 0) && (srcY >= 0)); interpreterProxy.success((srcX + (2 * dstW)) <= inW); interpreterProxy.success((srcY + (2 * dstH)) <= inH); interpreterProxy.success((dstX >= 0) && (dstY >= 0)); interpreterProxy.success((dstX + dstW) <= outW); interpreterProxy.success((dstY + dstH) <= outH); if (interpreterProxy.failed()) { return null; } for (y = 0; y <= (dstH - 1); y++) { srcIndex = (inW * (srcY + (2 * y))) + srcX; dstIndex = (outW * (dstY + y)) + dstX; for (x = 0; x <= (dstW - 1); x++) { pixel = in_[srcIndex]; r = pixel & 16711680; g = pixel & 65280; b = pixel & 255; pixel = in_[srcIndex + 1]; r += pixel & 16711680; g += pixel & 65280; b += pixel & 255; pixel = in_[srcIndex + inW]; r += pixel & 16711680; g += pixel & 65280; b += pixel & 255; pixel = in_[(srcIndex + inW) + 1]; r += pixel & 16711680; g += pixel & 65280; /* store combined RGB into target bitmap */ b += pixel & 255; out[dstIndex] = (((r >>> 2) & 16711680) | (((g >>> 2) & 65280) | (b >>> 2))); srcIndex += 2; ++dstIndex; } } interpreterProxy.pop(12); return 0; } function primitiveHalfSizeDiagonal() { var b; var dstH; var dstIndex; var dstW; var dstX; var dstY; var g; var in_; var inH; var inW; var out; var outH; var outW; var p1; var p2; var r; var srcIndex; var srcX; var srcY; var x; var y; in_ = checkedUnsignedIntPtrOf(interpreterProxy.stackValue(11)); inW = interpreterProxy.stackIntegerValue(10); inH = interpreterProxy.stackIntegerValue(9); out = checkedUnsignedIntPtrOf(interpreterProxy.stackValue(8)); outW = interpreterProxy.stackIntegerValue(7); outH = interpreterProxy.stackIntegerValue(6); srcX = interpreterProxy.stackIntegerValue(5); srcY = interpreterProxy.stackIntegerValue(4); dstX = interpreterProxy.stackIntegerValue(3); dstY = interpreterProxy.stackIntegerValue(2); dstW = interpreterProxy.stackIntegerValue(1); dstH = interpreterProxy.stackIntegerValue(0); interpreterProxy.success((srcX >= 0) && (srcY >= 0)); interpreterProxy.success((srcX + (2 * dstW)) <= inW); interpreterProxy.success((srcY + (2 * dstH)) <= inH); interpreterProxy.success((dstX >= 0) && (dstY >= 0)); interpreterProxy.success((dstX + dstW) <= outW); interpreterProxy.success((dstY + dstH) <= outH); if (interpreterProxy.failed()) { return null; } for (y = 0; y <= (dstH - 1); y++) { srcIndex = (inW * (srcY + (2 * y))) + srcX; dstIndex = (outW * (dstY + y)) + dstX; for (x = 0; x <= (dstW - 1); x++) { p1 = in_[srcIndex]; p2 = in_[(srcIndex + inW) + 1]; r = (((p1 & 16711680) + (p2 & 16711680)) >>> 1) & 16711680; g = (((p1 & 65280) + (p2 & 65280)) >>> 1) & 65280; /* store combined RGB into target bitmap */ b = ((p1 & 255) + (p2 & 255)) >>> 1; out[dstIndex] = (r | (g | b)); srcIndex += 2; ++dstIndex; } } interpreterProxy.pop(12); return 0; } function primitiveHueShift() { var b; var brightness; var g; var hue; var i; var in_; var inOop; var max; var min; var out; var outOop; var pix; var r; var saturation; var shift; var sz; inOop = interpreterProxy.stackValue(2); outOop = interpreterProxy.stackValue(1); shift = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); sz = SIZEOF(inOop); out = checkedUnsignedIntPtrOf(outOop); interpreterProxy.success(SIZEOF(outOop) === sz); if (interpreterProxy.failed()) { return null; } for (i = 0; i <= (sz - 1); i++) { pix = in_[i] & 16777215; if (pix !== 0) { /* skip pixel values of 0 (transparent) */ r = (pix >>> 16) & 255; g = (pix >>> 8) & 255; /* find min and max color components */ b = pix & 255; max = (min = r); if (g > max) { max = g; } if (b > max) { max = b; } if (g < min) { min = g; } if (b < min) { min = b; } brightness = DIV((max * 1000), 255); if (max === 0) { saturation = 0; } else { saturation = DIV(((max - min) * 1000), max); } if (brightness < 110) { /* force black to a very dark, saturated gray */ brightness = 110; saturation = 1000; } if (saturation < 90) { saturation = 90; } if ((brightness === 110) || (saturation === 90)) { /* tint all blacks and grays the same */ hue = 0; } else { hue = hueFromRGBminmax(r, g, b, min, max); } /* compute new hue */ hue = MOD(((hue + shift) + 360000000), 360); bitmapatputHsv(out, i, hue, saturation, brightness); } } interpreterProxy.pop(3); return 0; } function primitiveInterpolate() { var in_; var inOop; var result; var sz; var width; var xFixed; var yFixed; inOop = interpreterProxy.stackValue(3); width = interpreterProxy.stackIntegerValue(2); xFixed = interpreterProxy.stackIntegerValue(1); yFixed = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); sz = SIZEOF(inOop); if (interpreterProxy.failed()) { return null; } result = interpolatedFromxywidthheight(in_, xFixed, yFixed, width, DIV(sz, width)); interpreterProxy.pop(5); interpreterProxy.pushInteger(result); return 0; } function primitiveSaturationShift() { var b; var brightness; var g; var hue; var i; var in_; var inOop; var max; var min; var out; var outOop; var pix; var r; var saturation; var shift; var sz; inOop = interpreterProxy.stackValue(2); outOop = interpreterProxy.stackValue(1); shift = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); sz = SIZEOF(inOop); out = checkedUnsignedIntPtrOf(outOop); interpreterProxy.success(SIZEOF(outOop) === sz); if (interpreterProxy.failed()) { return null; } for (i = 0; i <= (sz - 1); i++) { pix = in_[i] & 16777215; if (!(pix < 2)) { /* skip pixel values of 0 (transparent) and 1 (black) */ r = (pix >>> 16) & 255; g = (pix >>> 8) & 255; /* find min and max color components */ b = pix & 255; max = (min = r); if (g > max) { max = g; } if (b > max) { max = b; } if (g < min) { min = g; } if (b < min) { min = b; } brightness = DIV((max * 1000), 255); if (max === 0) { saturation = 0; } else { saturation = DIV(((max - min) * 1000), max); } if (saturation > 0) { /* do nothing if pixel is unsaturated (gray) */ /* compute new saturation */ hue = hueFromRGBminmax(r, g, b, min, max); saturation += shift * 10; if (saturation > 1000) { saturation = 1000; } if (saturation < 0) { saturation = 0; } bitmapatputHsv(out, i, hue, saturation, brightness); } } } interpreterProxy.pop(3); return 0; } /* Scale using bilinear interpolation. */ function primitiveScale() { var in_; var inH; var inOop; var inW; var inX; var inY; var out; var outH; var outOop; var outPix; var outW; var outX; var outY; var p1; var p2; var p3; var p4; var t; var tWeight; var w1; var w2; var w3; var w4; var xIncr; var yIncr; inOop = interpreterProxy.stackValue(5); inW = interpreterProxy.stackIntegerValue(4); inH = interpreterProxy.stackIntegerValue(3); outOop = interpreterProxy.stackValue(2); outW = interpreterProxy.stackIntegerValue(1); outH = interpreterProxy.stackIntegerValue(0); interpreterProxy.success(SIZEOF(inOop) === (inW * inH)); interpreterProxy.success(SIZEOF(outOop) === (outW * outH)); in_ = checkedUnsignedIntPtrOf(inOop); out = checkedUnsignedIntPtrOf(outOop); if (interpreterProxy.failed()) { return null; } /* source x and y, scaled by 1024 */ inX = (inY = 0); /* source x increment, scaled by 1024 */ xIncr = DIV((inW * 1024), outW); /* source y increment, scaled by 1024 */ yIncr = DIV((inH * 1024), outH); for (outY = 0; outY <= (outH - 1); outY++) { inX = 0; for (outX = 0; outX <= (outW - 1); outX++) { /* compute weights, scaled by 2^20 */ w1 = (1024 - (inX & 1023)) * (1024 - (inY & 1023)); w2 = (inX & 1023) * (1024 - (inY & 1023)); w3 = (1024 - (inX & 1023)) * (inY & 1023); /* get source pixels */ w4 = (inX & 1023) * (inY & 1023); t = ((inY >>> 10) * inW) + (inX >>> 10); p1 = in_[t]; if ((inX >>> 10) < (inW - 1)) { p2 = in_[t + 1]; } else { p2 = p1; } if ((inY >>> 10) < (inH - 1)) { t += inW; } p3 = in_[t]; if ((inX >>> 10) < (inW - 1)) { p4 = in_[t + 1]; } else { p4 = p3; } tWeight = 0; if (p1 === 0) { p1 = p2; tWeight += w1; } if (p2 === 0) { p2 = p1; tWeight += w2; } if (p3 === 0) { p3 = p4; tWeight += w3; } if (p4 === 0) { p4 = p3; tWeight += w4; } if (p1 === 0) { p1 = p3; p2 = p4; } if (p3 === 0) { p3 = p1; p4 = p2; } outPix = 0; if (tWeight < 500000) { /* compute an (opaque) output pixel if less than 50% transparent */ t = (((w1 * ((p1 >>> 16) & 255)) + (w2 * ((p2 >>> 16) & 255))) + (w3 * ((p3 >>> 16) & 255))) + (w4 * ((p4 >>> 16) & 255)); outPix = ((t >>> 20) & 255) << 16; t = (((w1 * ((p1 >>> 8) & 255)) + (w2 * ((p2 >>> 8) & 255))) + (w3 * ((p3 >>> 8) & 255))) + (w4 * ((p4 >>> 8) & 255)); outPix = outPix | (((t >>> 20) & 255) << 8); t = (((w1 * (p1 & 255)) + (w2 * (p2 & 255))) + (w3 * (p3 & 255))) + (w4 * (p4 & 255)); outPix = outPix | ((t >>> 20) & 255); if (outPix === 0) { outPix = 1; } } out[(outY * outW) + outX] = outPix; inX += xIncr; } inY += yIncr; } interpreterProxy.pop(6); return 0; } function primitiveWaterRipples1() { var aArOop; var aArray; var allPix; var bArOop; var bArray; var blops; var d; var dist; var dx; var dx2; var dy; var dy2; var f; var g; var h; var height; var i; var in_; var inOop; var j; var newLoc; var out; var outOop; var pix; var power; var q; var ripply; var t; var t1; var temp; var val; var val2; var width; var x; var y; inOop = interpreterProxy.stackValue(5); outOop = interpreterProxy.stackValue(4); width = interpreterProxy.stackIntegerValue(3); in_ = checkedUnsignedIntPtrOf(inOop); out = checkedUnsignedIntPtrOf(outOop); allPix = SIZEOF(inOop); ripply = interpreterProxy.stackIntegerValue(2); aArOop = interpreterProxy.stackValue(1); bArOop = interpreterProxy.stackValue(0); aArray = checkedFloatPtrOf(aArOop); bArray = checkedFloatPtrOf(bArOop); interpreterProxy.success(SIZEOF(outOop) === allPix); if (interpreterProxy.failed()) { return null; } height = DIV(allPix, width); t1 = Math.random(); blops = (MOD(t1, ripply)) - 1; for (t = 0; t <= ((blops / 2) - 1); t++) { t1 = Math.random(); x = MOD(t1, width); t1 = Math.random(); y = MOD(t1, height); t1 = Math.random(); power = MOD(t1, 8); for (g = -4; g <= 4; g++) { for (h = -4; h <= 4; h++) { dist = ((g * g) + (h * h)); if ((dist < 25) && (dist > 0)) { dx = ((x + g)|0); dy = ((y + h)|0); if ((dx > 0) && ((dy > 0) && ((dy < height) && (dx < width)))) { aArray[(dy * width) + dx] = (power * (1.0 - (dist / 25.0))); } } } } } for (f = 1; f <= (width - 2); f++) { for (d = 1; d <= (height - 2); d++) { val = (d * width) + f; aArray[val] = (((((((((bArray[val + 1] + bArray[val - 1]) + bArray[val + width]) + bArray[val - width]) + (bArray[(val - 1) - width] / 2)) + (bArray[(val - 1) + width] / 2)) + (bArray[(val + 1) - width] / 2)) + (bArray[(val + 1) + width] / 2)) / 4) - aArray[val]); aArray[val] = (aArray[val] * 0.9); } } for (q = 0; q <= (width * height); q++) { temp = bArray[q]; bArray[q] = aArray[q]; aArray[q] = temp; } for (j = 0; j <= (height - 1); j++) { for (i = 0; i <= (width - 1); i++) { if ((i > 1) && ((i < (width - 1)) && ((j > 1) && (j < (height - 1))))) { val2 = (j * width) + i; dx2 = (((aArray[val2] - aArray[val2 - 1]) + (aArray[val2 + 1] - aArray[val2])) * 64); dy2 = (((aArray[val2] - aArray[val2 - width]) + (aArray[val2 + width] - aArray[val2])) / 64); if (dx2 < 2) { dx2 = -2; } if (dx2 > 2) { dx2 = 2; } if (dy2 < 2) { dy2 = -2; } if (dy2 > 2) { dy2 = 2; } newLoc = ((((j + dy2) * width) + (i + dx2))|0); if ((newLoc < (width * height)) && (newLoc >= 0)) { pix = in_[newLoc]; } else { pix = in_[i + (j * width)]; } } else { pix = in_[i + (j * width)]; } out[i + (j * width)] = pix; } } interpreterProxy.pop(6); return 0; } function primitiveWhirl() { var ang; var centerX; var centerY; var cosa; var d; var degrees; var dx; var dy; var factor; var height; var in_; var inOop; var out; var outOop; var pix; var radius; var radiusSquared; var scaleX; var scaleY; var sina; var sz; var whirlRadians; var width; var x; var y; inOop = interpreterProxy.stackValue(3); outOop = interpreterProxy.stackValue(2); width = interpreterProxy.stackIntegerValue(1); degrees = interpreterProxy.stackIntegerValue(0); in_ = checkedUnsignedIntPtrOf(inOop); out = checkedUnsignedIntPtrOf(outOop); sz = SIZEOF(inOop); interpreterProxy.success(SIZEOF(outOop) === sz); if (interpreterProxy.failed()) { return null; } height = DIV(sz, width); centerX = width >> 1; centerY = height >> 1; if (centerX < centerY) { radius = centerX; scaleX = centerY / centerX; scaleY = 1.0; } else { radius = centerY; scaleX = 1.0; if (centerY < centerX) { scaleY = centerX / centerY; } else { scaleY = 1.0; } } whirlRadians = (-3.141592653589793 * degrees) / 180.0; radiusSquared = (radius * radius); for (x = 0; x <= (width - 1); x++) { for (y = 0; y <= (height - 1); y++) { dx = scaleX * (x - centerX); dy = scaleY * (y - centerY); d = (dx * dx) + (dy * dy); if (d < radiusSquared) { /* inside the whirl circle */ factor = 1.0 - (Math.sqrt(d) / radius); ang = whirlRadians * (factor * factor); sina = Math.sin(ang); cosa = Math.cos(ang); pix = interpolatedFromxywidthheight(in_, ((1024.0 * ((((cosa * dx) - (sina * dy)) / scaleX) + centerX))|0), ((1024.0 * ((((sina * dx) + (cosa * dy)) / scaleY) + centerY))|0), width, height); out[(width * y) + x] = pix; } } } interpreterProxy.pop(4); return 0; } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("ScratchPlugin", { primitiveCondenseSound: primitiveCondenseSound, getModuleName: getModuleName, primitiveFisheye: primitiveFisheye, primitiveWaterRipples1: primitiveWaterRipples1, primitiveHalfSizeDiagonal: primitiveHalfSizeDiagonal, primitiveScale: primitiveScale, primitiveDoubleSize: primitiveDoubleSize, setInterpreter: setInterpreter, primitiveWhirl: primitiveWhirl, primitiveBlur: primitiveBlur, primitiveBrightnessShift: primitiveBrightnessShift, primitiveHalfSizeAverage: primitiveHalfSizeAverage, primitiveSaturationShift: primitiveSaturationShift, primitiveHueShift: primitiveHueShift, primitiveInterpolate: primitiveInterpolate, primitiveExtractChannel: primitiveExtractChannel, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* * This Socket plugin only fulfills http:/https:/ws:/wss: requests by intercepting them * and sending as either XMLHttpRequest or Fetch or WebSocket. * To make connections to servers without CORS, it uses a CORS proxy. * * When a WebSocket connection is created in the Smalltalk image a low level socket is * assumed to be provided by this plugin. Since low level sockets are not supported * in the browser a WebSocket is used here. This does however require the WebSocket * protocol (applied by the Smalltalk image) to be 'reversed' or 'faked' here in the * plugin. * The WebSocket handshake protocol is faked within the plugin and a regular WebSocket * connection is set up with the other party resulting in a real handshake. * When a (WebSocket) message is sent from the Smalltalk runtime it will be packed * inside a frame (fragment). This Socket plugin will extract the message from the * frame and send it using the WebSocket object (which will put it into a frame * again). A bit of unnecessary byte and bit fiddling unfortunately. * See the following site for an explanation of the WebSocket protocol: * https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers * * DNS requests are done through DNS over HTTPS (DoH). Quad9 (IP 9.9.9.9) is used * as server because it seems to take privacy of users serious. Other servers can * be found at https://en.wikipedia.org/wiki/Public_recursive_name_server */ function SocketPlugin() { return { getModuleName: function() { return 'SocketPlugin (http-only)'; }, interpreterProxy: null, primHandler: null, handleCounter: 0, needProxy: new Set(), // DNS Lookup // Cache elements: key is name, value is { address: 1.2.3.4, validUntil: Date.now() + 30000 } status: 0, // Resolver_Uninitialized, lookupCache: { localhost: { address: [ 127, 0, 0, 1], validUntil: Number.MAX_SAFE_INTEGER } }, lastLookup: null, lookupSemaIdx: 0, // Constants TCP_Socket_Type: 0, Resolver_Uninitialized: 0, Resolver_Ready: 1, Resolver_Busy: 2, Resolver_Error: 3, Socket_InvalidSocket: -1, Socket_Unconnected: 0, Socket_WaitingForConnection: 1, Socket_Connected: 2, Socket_OtherEndClosed: 3, Socket_ThisEndClosed: 4, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; this.primHandler = this.interpreterProxy.vm.primHandler; return true; }, _signalSemaphore: function(semaIndex) { if (semaIndex <= 0) return; this.primHandler.signalSemaphoreWithIndex(semaIndex); }, _signalLookupSemaphore: function() { this._signalSemaphore(this.lookupSemaIdx); }, _getAddressFromLookupCache: function(name, skipExpirationCheck) { if (name) { // Check for valid dotted decimal name first var dottedDecimalsMatch = name.match(/^\d+\.\d+\.\d+\.\d+$/); if (dottedDecimalsMatch) { var result = name.split(".").map(function(d) { return +d; }); if (result.every(function(d) { return d <= 255; })) { return new Uint8Array(result); } } // Lookup in cache var cacheEntry = this.lookupCache[name]; if (cacheEntry && (skipExpirationCheck || cacheEntry.validUntil >= Date.now())) { return new Uint8Array(cacheEntry.address); } } return null; }, _addAddressFromResponseToLookupCache: function(response) { // Check for valid response if (!response || response.Status !== 0 || !response.Question || !response.Answer) { return; } // Clean up all response elements by removing trailing dots in names var removeTrailingDot = function(element, field) { if (element[field] && element[field].replace) { element[field] = element[field].replace(/\.$/, ""); } }; var originalQuestion = response.Question[0]; removeTrailingDot(originalQuestion, "name"); response.Answer.forEach(function(answer) { removeTrailingDot(answer, "name"); removeTrailingDot(answer, "data"); }); // Get address by traversing alias chain var lookup = originalQuestion.name; var address = null; var ttl = 24 * 60 * 60; // One day as safe default var hasAddress = response.Answer.some(function(answer) { if (answer.name === lookup) { // Time To Live can be set on alias and address, keep shortest period if (answer.TTL) { ttl = Math.min(ttl, answer.TTL); } if (answer.type === 1) { // Retrieve IP address as array with 4 numeric values address = answer.data.split(".").map(function(numberString) { return +numberString; }); return true; } else if (answer.type === 5) { // Lookup name points to alias, follow alias from here on lookup = answer.data; } } return false; }); // Store address found if (hasAddress) { this.lookupCache[originalQuestion.name] = { address: address, validUntil: Date.now() + (ttl * 1000) }; } }, _compareAddresses: function(address1, address2) { return address1.every(function(addressPart, index) { return address2[index] === addressPart; }); }, _reverseLookupNameForAddress: function(address) { // Currently public API's for IP to hostname are not standardized yet (like DoH). // Assume most lookup's will be for reversing earlier name to address lookups. // Therefor use the lookup cache and otherwise create a dotted decimals name. var thisHandle = this; var result = null; Object.keys(this.lookupCache).some(function(name) { if (thisHandle._compareAddresses(address, thisHandle.lookupCache[name].address)) { result = name; return true; } return false; }); return result || address.join("."); }, // A socket handle emulates socket behavior _newSocketHandle: function(sendBufSize, connSemaIdx, readSemaIdx, writeSemaIdx) { var plugin = this; return { hostAddress: null, host: null, port: null, connSemaIndex: connSemaIdx, readSemaIndex: readSemaIdx, writeSemaIndex: writeSemaIdx, webSocket: null, sendBuffer: null, sendTimeout: null, response: null, responseReadUntil: 0, responseReceived: false, status: plugin.Socket_Unconnected, _signalConnSemaphore: function() { plugin._signalSemaphore(this.connSemaIndex); }, _signalReadSemaphore: function() { plugin._signalSemaphore(this.readSemaIndex); }, _signalWriteSemaphore: function() { plugin._signalSemaphore(this.writeSemaIndex); }, _otherEndClosed: function() { this.status = plugin.Socket_OtherEndClosed; this.webSocket = null; this._signalConnSemaphore(); }, _hostAndPort: function() { return this.host + ':' + this.port; }, _requestNeedsProxy: function() { return plugin.needProxy.has(this._hostAndPort()); }, _getURL: function(targetURL, isRetry) { var url = ''; if (isRetry || this._requestNeedsProxy()) { var proxy = typeof SqueakJS === "object" && SqueakJS.options.proxy; url = proxy || Squeak.defaultCORSProxy; } if (this.port !== 443) { url += 'http://' + this._hostAndPort() + targetURL; } else { url += 'https://' + this.host + targetURL; } return url; }, _performRequest: function() { // Assume a send is requested through WebSocket if connection is present if (this.webSocket) { this._performWebSocketSend(); return; } var request = new TextDecoder("utf-8").decode(this.sendBuffer); // Remove request from send buffer var endOfRequestIndex = this.sendBuffer.findIndex(function(element, index, array) { // Check for presence of "\r\n\r\n" denoting the end of the request (do simplistic but fast check) return array[index] === "\r" && array[index + 2] === "\r" && array[index + 1] === "\n" && array[index + 3] === "\n"; }); if (endOfRequestIndex >= 0) { this.sendBuffer = this.sendBuffer.subarray(endOfRequestIndex + 4); } else { this.sendBuffer = null; } // Extract header fields var headerLines = request.split('\r\n\r\n')[0].split('\n'); // Split header lines and parse first line var firstHeaderLineItems = headerLines[0].split(' '); var httpMethod = firstHeaderLineItems[0]; if (httpMethod !== 'GET' && httpMethod !== 'PUT' && httpMethod !== 'POST') { this._otherEndClosed(); return -1; } var targetURL = firstHeaderLineItems[1]; // Extract possible data to send var seenUpgrade = false; var seenWebSocket = false; var data = null; for (var i = 1; i < headerLines.length; i++) { var line = headerLines[i]; if (line.match(/Content-Length:/i)) { var contentLength = parseInt(line.substr(16)); var end = this.sendBuffer.byteLength; data = this.sendBuffer.subarray(end - contentLength, end); } else if (line.match(/Host:/i)) { var hostAndPort = line.substr(6).trim(); var host = hostAndPort.split(':')[0]; var port = parseInt(hostAndPort.split(':')[1]) || this.port; if (this.host !== host) { console.warn('Host for ' + this.hostAddress + ' was ' + this.host + ' but from HTTP request now ' + host); this.host = host; } if (this.port !== port) { console.warn('Port for ' + this.hostAddress + ' was ' + this.port + ' but from HTTP request now ' + port); this.port = port; } } if (line.match(/Connection: Upgrade/i)) { seenUpgrade = true; } else if (line.match(/Upgrade: WebSocket/i)) { seenWebSocket = true; } } if (httpMethod === "GET" && seenUpgrade && seenWebSocket) { this._performWebSocketRequest(targetURL, httpMethod, data, headerLines); } else if (self.fetch) { this._performFetchAPIRequest(targetURL, httpMethod, data, headerLines); } else { this._performXMLHTTPRequest(targetURL, httpMethod, data, headerLines); } }, _performFetchAPIRequest: function(targetURL, httpMethod, data, requestLines) { var thisHandle = this; var headers = {}; for (var i = 1; i < requestLines.length; i++) { var lineItems = requestLines[i].split(':'); if (lineItems.length === 2) { headers[lineItems[0]] = lineItems[1].trim(); } } if (typeof SqueakJS === "object" && SqueakJS.options.ajax) { headers["X-Requested-With"] = "XMLHttpRequest"; } var init = { method: httpMethod, headers: headers, body: data, mode: 'cors' }; fetch(this._getURL(targetURL), init) .then(thisHandle._handleFetchAPIResponse.bind(thisHandle)) .catch(function (e) { var url = thisHandle._getURL(targetURL, true); console.warn('Retrying with CORS proxy: ' + url); fetch(url, init) .then(function(res) { console.log('Success: ' + url); thisHandle._handleFetchAPIResponse(res); plugin.needProxy.add(thisHandle._hostAndPort()); }) .catch(function (e) { // KLUDGE! This is just a workaround for a broken // proxy server - we should remove it when // crossorigin.me is fixed console.warn('Fetch API failed, retrying with XMLHttpRequest'); thisHandle._performXMLHTTPRequest(targetURL, httpMethod, data, requestLines); }); }); }, _handleFetchAPIResponse: function(res) { if (this.response === null) { var header = ['HTTP/1.0 ', res.status, ' ', res.statusText, '\r\n']; res.headers.forEach(function(value, key, array) { header = header.concat([key, ': ', value, '\r\n']); }); header.push('\r\n'); this.response = [new TextEncoder('utf-8').encode(header.join(''))]; } this._readIncremental(res.body.getReader()); }, _readIncremental: function(reader) { var thisHandle = this; return reader.read().then(function (result) { if (result.done) { thisHandle.responseReceived = true; return; } thisHandle.response.push(result.value); thisHandle._signalReadSemaphore(); return thisHandle._readIncremental(reader); }); }, _performXMLHTTPRequest: function(targetURL, httpMethod, data, requestLines){ var thisHandle = this; var contentType; for (var i = 1; i < requestLines.length; i++) { var line = requestLines[i]; if (line.match(/Content-Type:/i)) { contentType = encodeURIComponent(line.substr(14)); break; } } var httpRequest = new XMLHttpRequest(); httpRequest.open(httpMethod, this._getURL(targetURL)); if (contentType !== undefined) { httpRequest.setRequestHeader('Content-type', contentType); } if (typeof SqueakJS === "object" && SqueakJS.options.ajax) { httpRequest.setRequestHeader("X-Requested-With", "XMLHttpRequest"); } httpRequest.responseType = "arraybuffer"; httpRequest.onload = function (oEvent) { thisHandle._handleXMLHTTPResponse(this); }; httpRequest.onerror = function(e) { var url = thisHandle._getURL(targetURL, true); console.warn('Retrying with CORS proxy: ' + url); var retry = new XMLHttpRequest(); retry.open(httpMethod, url); retry.responseType = httpRequest.responseType; if (typeof SqueakJS === "object" && SqueakJS.options.ajaxx) { retry.setRequestHeader("X-Requested-With", "XMLHttpRequest"); } retry.onload = function(oEvent) { console.log('Success: ' + url); thisHandle._handleXMLHTTPResponse(this); plugin.needProxy.add(thisHandle._hostAndPort()); }; retry.onerror = function() { thisHandle._otherEndClosed(); console.error("Failed to download:\n" + url); }; retry.send(data); }; httpRequest.send(data); }, _handleXMLHTTPResponse: function(response) { this.responseReceived = true; var content = response.response; if (!content) { this._otherEndClosed(); return; } // Recreate header var header = new TextEncoder('utf-8').encode( 'HTTP/1.0 ' + response.status + ' ' + response.statusText + '\r\n' + response.getAllResponseHeaders() + '\r\n'); // Concat header and response var res = new Uint8Array(header.byteLength + content.byteLength); res.set(header, 0); res.set(new Uint8Array(content), header.byteLength); this.response = [res]; this._signalReadSemaphore(); }, _performWebSocketRequest: function(targetURL, httpMethod, data, requestLines){ var url = this._getURL(targetURL); // Extract WebSocket key and subprotocol var webSocketSubProtocol; var webSocketKey; for (var i = 1; i < requestLines.length; i++) { var requestLine = requestLines[i].split(":"); if (requestLine[0] === "Sec-WebSocket-Protocol") { webSocketSubProtocol = requestLine[1].trim(); if (webSocketKey) { break; // Only break if both webSocketSubProtocol and webSocketKey are found } } else if (requestLine[0] === "Sec-WebSocket-Key") { webSocketKey = requestLine[1].trim(); if (webSocketSubProtocol) { break; // Only break if both webSocketSubProtocol and webSocketKey are found } } } // Keep track of WebSocket for future send and receive operations this.webSocket = new WebSocket(url.replace(/^http/, "ws"), webSocketSubProtocol); var thisHandle = this; this.webSocket.onopen = function() { if (thisHandle.status !== plugin.Socket_Connected) { thisHandle.status = plugin.Socket_Connected; thisHandle._signalConnSemaphore(); thisHandle._signalWriteSemaphore(); // Immediately ready to write } // Send the (fake) handshake back to the caller var acceptKey = new Uint8Array(sha1.array(webSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); var acceptKeyString = Squeak.bytesAsString(acceptKey); thisHandle._performWebSocketReceive( "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + btoa(acceptKeyString) + "\r\n\r\n", true ); }; this.webSocket.onmessage = function(event) { thisHandle._performWebSocketReceive(event.data); }; this.webSocket.onerror = function(e) { thisHandle._otherEndClosed(); console.error("Error in WebSocket:", e); }; this.webSocket.onclose = function() { thisHandle._otherEndClosed(); }; }, _performWebSocketReceive: function(message, skipFramePacking) { // Process received message var dataIsBinary = !message.substr; if (!dataIsBinary) { message = new TextEncoder("utf-8").encode(message); } if (!skipFramePacking) { // Create WebSocket frame from message for Smalltalk runtime var frameLength = 1 + 1 + message.length + 4; // 1 byte for initial header bits & opcode, 1 byte for length and 4 bytes for mask var payloadLengthByte; if (message.byteLength < 126) { payloadLengthByte = message.length; } else if (message.byteLength < 0xffff) { frameLength += 2; // 2 additional bytes for payload length payloadLengthByte = 126; } else { frameLength += 8; // 8 additional bytes for payload length payloadLengthByte = 127; } var frame = new Uint8Array(frameLength); frame[0] = dataIsBinary ? 0x82 : 0x81; // Final bit 0x80 set and opcode 0x01 for text and 0x02 for binary frame[1] = 0x80 | payloadLengthByte; // Mask bit 0x80 and payload length byte var nextByteIndex; if (payloadLengthByte === 126) { frame[2] = message.length >>> 8; frame[3] = message.length & 0xff; nextByteIndex = 4; } else if (payloadLengthByte === 127) { frame[2] = message.length >>> 56; frame[3] = (message.length >>> 48) & 0xff; frame[4] = (message.length >>> 40) & 0xff; frame[5] = (message.length >>> 32) & 0xff; frame[6] = (message.length >>> 24) & 0xff; frame[7] = (message.length >>> 16) & 0xff; frame[8] = (message.length >>> 8) & 0xff; frame[9] = message.length & 0xff; nextByteIndex = 10; } else { nextByteIndex = 2; } // Add 'empty' mask (requiring no transformation) // Otherwise a (random) mask and the following line should be added: // var payload = message.map(function(b, index) { return b ^ maskKey[index & 0x03]; }); var maskKey = new Uint8Array(4); frame.set(maskKey, nextByteIndex); nextByteIndex += 4; var payload = message; frame.set(payload, nextByteIndex); // Make sure the frame is set as the response message = frame; } // Store received message in response buffer if (!this.response || !this.response.length) { this.response = [ message ]; } else { this.response.push(message); } this.responseReceived = true; this._signalReadSemaphore(); }, _performWebSocketSend: function() { // Decode sendBuffer which is a WebSocket frame (from Smalltalk runtime) // Read frame header fields var firstByte = this.sendBuffer[0]; var opcode = firstByte & 0x0f; var dataIsBinary; if (opcode === 0x00) { // Continuation frame console.error("No support for WebSocket frame continuation yet!"); return true; } else if (opcode === 0x01) { // Text frame dataIsBinary = false; } else if (opcode === 0x02) { // Binary frame dataIsBinary = true; } else if (opcode === 0x08) { // Close connection this.webSocket.close(); this.webSocket = null; return; } else if (opcode === 0x09 || opcode === 0x0a) { // Ping/pong frame (ignoring it, is handled by WebSocket implementation itself) return; } else { console.error("Unsupported WebSocket frame opcode " + opcode); return; } var secondByte = this.sendBuffer[1]; var maskBit = secondByte >>> 7; var payloadLength = secondByte & 0x7f; var nextByteIndex; if (payloadLength === 126) { payloadLength = (this.sendBuffer[2] << 8) | this.sendBuffer[3]; nextByteIndex = 4; } else if (payloadLength === 127) { payloadLength = (this.sendBuffer[2] << 56) | (this.sendBuffer[3] << 48) | (this.sendBuffer[4] << 40) | (this.sendBuffer[5] << 32) | (this.sendBuffer[6] << 24) | (this.sendBuffer[7] << 16) | (this.sendBuffer[8] << 8) | this.sendBuffer[9] ; nextByteIndex = 10; } else { nextByteIndex = 2; } var maskKey; if (maskBit) { maskKey = this.sendBuffer.subarray(nextByteIndex, nextByteIndex + 4); nextByteIndex += 4; } // Read (remaining) payload var payloadData = this.sendBuffer.subarray(nextByteIndex, nextByteIndex + payloadLength); nextByteIndex += payloadLength; // Unmask the payload if (maskBit) { payloadData = payloadData.map(function(b, index) { return b ^ maskKey[index & 0x03]; }); } // Extract data from payload var data; if (dataIsBinary) { data = payloadData; } else { data = Squeak.bytesAsString(payloadData); } // Remove frame from send buffer this.sendBuffer = this.sendBuffer.subarray(nextByteIndex); this.webSocket.send(data); // Send remaining frames if (this.sendBuffer.byteLength > 0) { this._performWebSocketSend(); } }, connect: function(hostAddress, port) { this.hostAddress = hostAddress; this.host = plugin._reverseLookupNameForAddress(hostAddress); this.port = port; this.status = plugin.Socket_Connected; this._signalConnSemaphore(); this._signalWriteSemaphore(); // Immediately ready to write }, close: function() { if (this.status == plugin.Socket_Connected || this.status == plugin.Socket_OtherEndClosed || this.status == plugin.Socket_WaitingForConnection) { if (this.webSocket) { this.webSocket.close(); this.webSocket = null; } this.status = plugin.Socket_Unconnected; this._signalConnSemaphore(); } }, destroy: function() { this.status = plugin.Socket_InvalidSocket; }, dataAvailable: function() { if (this.status == plugin.Socket_InvalidSocket) return false; if (this.status == plugin.Socket_Connected) { if (this.webSocket) { return this.response && this.response.length > 0; } else { if (this.response && this.response.length > 0) { this._signalReadSemaphore(); return true; } if (this.responseSentCompletly) { // Signal older Socket implementations that they reached the end this.status = plugin.Socket_OtherEndClosed; this._signalConnSemaphore(); } } } return false; }, recv: function(count) { if (this.response === null) return []; var data = this.response[0] || new Uint8Array(0); if (data.length > count) { var rest = data.subarray(count); if (rest) { this.response[0] = rest; } else { this.response.shift(); } data = data.subarray(0, count); } else { this.response.shift(); } if (this.responseReceived && this.response.length === 0 && !this.webSocket) { this.responseSentCompletly = true; } return data; }, send: function(data, start, end) { if (this.sendTimeout !== null) { self.clearTimeout(this.sendTimeout); } this.lastSend = Date.now(); var newBytes = data.bytes.subarray(start, end); if (this.sendBuffer === null) { // Make copy of buffer otherwise the stream buffer will overwrite it on next call (inside Smalltalk image) this.sendBuffer = newBytes.slice(); } else { var newLength = this.sendBuffer.byteLength + newBytes.byteLength; var newBuffer = new Uint8Array(newLength); newBuffer.set(this.sendBuffer, 0); newBuffer.set(newBytes, this.sendBuffer.byteLength); this.sendBuffer = newBuffer; } // Give image some time to send more data before performing requests this.sendTimeout = self.setTimeout(this._performRequest.bind(this), 50); return newBytes.byteLength; } }; }, primitiveHasSocketAccess: function(argCount) { this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.trueObject()); return true; }, primitiveInitializeNetwork: function(argCount) { if (argCount !== 1) return false; this.lookupSemaIdx = this.interpreterProxy.stackIntegerValue(0); this.status = this.Resolver_Ready; this.interpreterProxy.pop(argCount); // Answer self return true; }, primitiveResolverNameLookupResult: function(argCount) { if (argCount !== 0) return false; // Validate that lastLookup is in fact a name (and not an address) if (!this.lastLookup || !this.lastLookup.substr) { this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; } // Retrieve result from cache var address = this._getAddressFromLookupCache(this.lastLookup, true); this.interpreterProxy.popthenPush(argCount + 1, address ? this.primHandler.makeStByteArray(address) : this.interpreterProxy.nilObject() ); return true; }, primitiveResolverStartNameLookup: function(argCount) { if (argCount !== 1) return false; // Start new lookup, ignoring if one is in progress var lookup = this.lastLookup = this.interpreterProxy.stackValue(0).bytesAsString(); // Perform lookup in local cache var result = this._getAddressFromLookupCache(lookup, false); if (result) { this.status = this.Resolver_Ready; this._signalLookupSemaphore(); } else { // Perform DNS request var dnsQueryURL = "https://9.9.9.9:5053/dns-query?name=" + encodeURIComponent(this.lastLookup) + "&type=A"; var queryStarted = false; if (self.fetch) { var thisHandle = this; var init = { method: "GET", mode: "cors", credentials: "omit", cache: "no-store", // do not use the browser cache for DNS requests (a separate cache is kept) referrer: "no-referrer", referrerPolicy: "no-referrer", }; self.fetch(dnsQueryURL, init) .then(function(response) { return response.json(); }) .then(function(response) { thisHandle._addAddressFromResponseToLookupCache(response); }) .catch(function(error) { console.error("Name lookup failed", error); }) .then(function() { // If no other lookup is started, signal the receiver (ie resolver) is ready if (lookup === thisHandle.lastLookup) { thisHandle.status = thisHandle.Resolver_Ready; thisHandle._signalLookupSemaphore(); } }) ; queryStarted = true; } else { var thisHandle = this; var lookupReady = function() { // If no other lookup is started, signal the receiver (ie resolver) is ready if (lookup === thisHandle.lastLookup) { thisHandle.status = thisHandle.Resolver_Ready; thisHandle._signalLookupSemaphore(); } }; var httpRequest = new XMLHttpRequest(); httpRequest.open("GET", dnsQueryURL, true); httpRequest.timeout = 2000; // milliseconds httpRequest.responseType = "json"; httpRequest.onload = function(oEvent) { thisHandle._addAddressFromResponseToLookupCache(this.response); lookupReady(); }; httpRequest.onerror = function() { console.error("Name lookup failed", httpRequest.statusText); lookupReady(); }; httpRequest.send(); queryStarted = true; } // Mark the receiver (ie resolver) is busy if (queryStarted) { this.status = this.Resolver_Busy; this._signalLookupSemaphore(); } } this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; }, primitiveResolverAddressLookupResult: function(argCount) { if (argCount !== 0) return false; // Validate that lastLookup is in fact an address (and not a name) if (!this.lastLookup || !this.lastLookup.every) { this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; } // Retrieve result from cache var name = this._reverseLookupNameForAddress(this.lastLookup); var result = this.primHandler.makeStString(name); this.interpreterProxy.popthenPush(argCount + 1, result); return true; }, primitiveResolverStartAddressLookup: function(argCount) { if (argCount !== 1) return false; // Start new lookup, ignoring if one is in progress this.lastLookup = this.interpreterProxy.stackBytes(0); this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); // Immediately signal the lookup is ready (since all lookups are done internally) this.status = this.Resolver_Ready; this._signalLookupSemaphore(); return true; }, primitiveResolverStatus: function(argCount) { if (argCount !== 0) return false; this.interpreterProxy.popthenPush(argCount + 1, this.status); return true; }, primitiveResolverAbortLookup: function(argCount) { if (argCount !== 0) return false; // Unable to abort send request (although future browsers might support AbortController), // just cancel the handling of the request by resetting the lastLookup value this.lastLookup = null; this.status = this.Resolver_Ready; this._signalLookupSemaphore(); this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; }, primitiveSocketRemoteAddress: function(argCount) { if (argCount !== 1) return false; var handle = this.interpreterProxy.stackObjectValue(0).handle; if (handle === undefined) return false; this.interpreterProxy.popthenPush(argCount + 1, handle.hostAddress ? this.primHandler.makeStByteArray(handle.hostAddress) : this.interpreterProxy.nilObject() ); return true; }, primitiveSocketRemotePort: function(argCount) { if (argCount !== 1) return false; var handle = this.interpreterProxy.stackObjectValue(0).handle; if (handle === undefined) return false; this.interpreterProxy.popthenPush(argCount + 1, handle.port); return true; }, primitiveSocketConnectionStatus: function(argCount) { if (argCount !== 1) return false; var handle = this.interpreterProxy.stackObjectValue(0).handle; if (handle === undefined) return false; var status = handle.status; if (status === undefined) status = this.Socket_InvalidSocket; this.interpreterProxy.popthenPush(argCount + 1, status); return true; }, primitiveSocketConnectToPort: function(argCount) { if (argCount !== 3) return false; var handle = this.interpreterProxy.stackObjectValue(2).handle; if (handle === undefined) return false; var hostAddress = this.interpreterProxy.stackBytes(1); var port = this.interpreterProxy.stackIntegerValue(0); handle.connect(hostAddress, port); this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; }, primitiveSocketCloseConnection: function(argCount) { if (argCount !== 1) return false; var handle = this.interpreterProxy.stackObjectValue(0).handle; if (handle === undefined) return false; handle.close(); this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; }, primitiveSocketCreate3Semaphores: function(argCount) { if (argCount !== 7) return false; var writeSemaIndex = this.interpreterProxy.stackIntegerValue(0); var readSemaIndex = this.interpreterProxy.stackIntegerValue(1); var semaIndex = this.interpreterProxy.stackIntegerValue(2); var sendBufSize = this.interpreterProxy.stackIntegerValue(3); var socketType = this.interpreterProxy.stackIntegerValue(5); if (socketType !== this.TCP_Socket_Type) return false; var name = '{SqueakJS Socket #' + (++this.handleCounter) + '}'; var sqHandle = this.primHandler.makeStString(name); sqHandle.handle = this._newSocketHandle(sendBufSize, semaIndex, readSemaIndex, writeSemaIndex); this.interpreterProxy.popthenPush(argCount + 1, sqHandle); return true; }, primitiveSocketDestroy: function(argCount) { if (argCount !== 1) return false; var handle = this.interpreterProxy.stackObjectValue(0).handle; if (handle === undefined) return false; handle.destroy(); this.interpreterProxy.popthenPush(argCount + 1, handle.status); return true; }, primitiveSocketReceiveDataAvailable: function(argCount) { if (argCount !== 1) return false; var handle = this.interpreterProxy.stackObjectValue(0).handle; if (handle === undefined) return false; var ret = this.interpreterProxy.falseObject(); if (handle.dataAvailable()) { ret = this.interpreterProxy.trueObject(); } this.interpreterProxy.popthenPush(argCount + 1, ret); return true; }, primitiveSocketReceiveDataBufCount: function(argCount) { if (argCount !== 4) return false; var handle = this.interpreterProxy.stackObjectValue(3).handle; if (handle === undefined) return false; var target = this.interpreterProxy.stackObjectValue(2); var start = this.interpreterProxy.stackIntegerValue(1) - 1; var count = this.interpreterProxy.stackIntegerValue(0); if ((start + count) > target.bytes.length) return false; var bytes = handle.recv(count); target.bytes.set(bytes, start); this.interpreterProxy.popthenPush(argCount + 1, bytes.length); return true; }, primitiveSocketSendDataBufCount: function(argCount) { if (argCount !== 4) return false; var handle = this.interpreterProxy.stackObjectValue(3).handle; if (handle === undefined) return false; var data = this.interpreterProxy.stackObjectValue(2); var start = this.interpreterProxy.stackIntegerValue(1) - 1; if (start < 0 ) return false; var count = this.interpreterProxy.stackIntegerValue(0); var end = start + count; if (end > data.length) return false; var res = handle.send(data, start, end); this.interpreterProxy.popthenPush(argCount + 1, res); return true; }, primitiveSocketSendDone: function(argCount) { if (argCount !== 1) return false; this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.trueObject()); return true; }, primitiveSocketListenWithOrWithoutBacklog: function(argCount) { if (argCount < 2) return false; this.interpreterProxy.popthenPush(argCount + 1, this.interpreterProxy.nilObject()); return true; }, }; } function registerSocketPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('SocketPlugin', SocketPlugin()); } else self.setTimeout(registerSocketPlugin, 100); } registerSocketPlugin(); function SpeechPlugin() { return { getModuleName: function() { return 'SpeechPlugin'; }, interpreterProxy: null, primHandler: null, voiceInput: null, semaphoreIndex: null, shouldListen: false, recognition: null, synth: self.speechSynthesis, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; this.primHandler = this.interpreterProxy.vm.primHandler; return true; }, /* * Speech Synthesis Primitive */ primitiveSpeak: function(argCount) { var text, voice; if (argCount === 1) { text = this.interpreterProxy.stackValue(0).bytesAsString(); } else if (argCount === 2) { text = this.interpreterProxy.stackValue(1).bytesAsString(); var voiceName = this.interpreterProxy.stackValue(0).bytesAsString(); voice = this.synth.getVoices().filter(function(voice) { return voice.name === voiceName; }); } else { return false; } var msg = new SpeechSynthesisUtterance(text); if (voice && voice.length > 0) { msg.voice = voice[0]; } this.synth.speak(msg); this.interpreterProxy.pop(argCount); return true; }, /* * Speech Recognition Primitives */ primitiveRegisterSemaphore: function(argCount) { if (argCount !== 1) return false; this.semaphoreIndex = this.interpreterProxy.stackIntegerValue(0); this.interpreterProxy.pop(argCount); return true; }, primitiveUnregisterSemaphore: function(argCount) { if (argCount !== 0) return false; this.semaphoreIndex = null; return true; }, primitiveGetLastRecognitionResult: function(argCount) { if (argCount !== 0) return false; if (this.semaphoreIndex === null) return false; if (this.voiceInput === null) return false; var stString = this.primHandler.makeStArray(this.voiceInput); this.voiceInput = []; this.interpreterProxy.popthenPush(1, stString); return true; }, primitiveStartListening: function(argCount) { if (argCount !== 0) return false; this.recognition = new webkitSpeechRecognition(); this.recognition.lang = 'en-US'; // en-GB this.recognition.interimResults = true; // this.recognition.maxAlternatives = 1; this.recognition.onresult = this._onRecognitionResult.bind(this); this.recognition.onerror = function(event) { console.log(event); }; /* * Imitate `this.recognition.continuous = true` because it is currently * only supported by Google Chrome. */ this.recognition.onend = function(event) { if (this.shouldListen) { this.recognition.start(); } }.bind(this); this.shouldListen = true; this.recognition.start(); return true; }, _onRecognitionResult: function(event) { this.voiceInput = []; for (var i = event.resultIndex; i < event.results.length; i++) { var result = event.results[i]; var sqResult = [result.isFinal, []]; for (var j = 0; j < result.length; j++) { var transcript = result[j].transcript; sqResult[1].push([transcript, result[j].confidence]); } if (sqResult[1].length > 0) { this.voiceInput.push(sqResult); } } if (this.semaphoreIndex !== null && this.voiceInput.length > 0) { this.primHandler.signalSemaphoreWithIndex(this.semaphoreIndex); } }, primitiveStopListening: function(argCount) { if (argCount !== 0) return false; this.voiceInput = []; this.recognition.stop(); this.shouldListen = false; return true; }, }; } function registerSpeechPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { if (('webkitSpeechRecognition' in self)) { Squeak.registerExternalModule('SpeechPlugin', SpeechPlugin()); } else { console.warn('SpeechPlugin: Web Speech API is not supported by this browser.'); } } else self.setTimeout(registerSpeechPlugin, 100); } registerSpeechPlugin(); /* * This is a fake SSL plugin, actual authentication and crypto * is handled by browser and SocketPlugin */ function SqueakSSL() { return { getModuleName: function() { return 'SqueakSSL (fake)'; }, interpreterProxy: null, primHandler: null, handleCounter: 0, // Return codes from the core SSL functions SQSSL_OK: 0, SQSSL_NEED_MORE_DATA: -1, SQSSL_INVALID_STATE: -2, SQSSL_BUFFER_TOO_SMALL: -3, SQSSL_INPUT_TOO_LARGE: -4, SQSSL_GENERIC_ERROR: -5, SQSSL_OUT_OF_MEMORY: -6, // SqueakSSL getInt/setInt property IDs SQSSL_PROP_VERSION: 0, SQSSL_PROP_LOGLEVEL: 1, SQSSL_PROP_SSLSTATE: 2, SQSSL_PROP_CERTSTATE: 3, // SqueakSSL getString/setString property IDs SQSSL_PROP_PEERNAME: 0, SQSSL_PROP_CERTNAME: 1, SQSSL_PROP_SERVERNAME: 2, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; this.primHandler = this.interpreterProxy.vm.primHandler; return true; }, primitiveCreate: function(argCount) { var name = '{SqueakJS SSL #' + (++this.handleCounter) + '}'; var sqHandle = this.primHandler.makeStString(name); sqHandle.handle = true; this.interpreterProxy.popthenPush(argCount + 1, sqHandle); return true; }, primitiveConnect: function(argCount) { if (argCount !== 5) return false; this.interpreterProxy.popthenPush(argCount + 1, 0); return true; }, primitiveDestroy: function(argCount) { if (argCount !== 1) return false; this.interpreterProxy.popthenPush(argCount + 1, 1); // Non-zero if successful return true; }, primitiveGetIntProperty: function(argCount) { if (argCount !== 2) return false; var handle = this.interpreterProxy.stackObjectValue(1).handle; if (handle === undefined) return false; var propID = this.interpreterProxy.stackIntegerValue(0); var res; if (propID === this.SQSSL_PROP_CERTSTATE) { res = this.SQSSL_OK; // Always valid } else { res = 0; } this.interpreterProxy.popthenPush(argCount + 1, res); return true; }, primitiveSetStringProperty: function(argCount) { if (argCount !== 3) return false; var handle = this.interpreterProxy.stackObjectValue(2).handle; if (handle === undefined) return false; // We don't actually care // var propID = this.interpreterProxy.stackIntegerValue(1); // var value = this.interpreterProxy.stackObjectValue(0); this.interpreterProxy.pop(argCount); return true; }, primitiveGetStringProperty: function(argCount) { if (argCount !== 2) return false; var handle = this.interpreterProxy.stackObjectValue(1).handle; if (handle === undefined) return false; var propID = this.interpreterProxy.stackIntegerValue(0); var res; if (propID === this.SQSSL_PROP_PEERNAME) { res = this.primHandler.makeStString('*'); // Match all } else { res = this.interpreterProxy.nilObject(); } this.interpreterProxy.popthenPush(argCount + 1, res); return true; }, primitiveEncrypt: function(argCount) { if (argCount !== 5) return false; var handle = this.interpreterProxy.stackObjectValue(4).handle; if (handle === undefined) return false; var srcBuf = this.interpreterProxy.stackObjectValue(3); this.interpreterProxy.stackIntegerValue(2) - 1; var length = this.interpreterProxy.stackIntegerValue(1); var dstBuf = this.interpreterProxy.stackObjectValue(0); dstBuf.bytes = srcBuf.bytes; // Just copy all there is this.interpreterProxy.popthenPush(argCount + 1, length); return true; }, primitiveDecrypt: function(argCount) { if (argCount !== 5) return false; var handle = this.interpreterProxy.stackObjectValue(4).handle; if (handle === undefined) return false; var srcBuf = this.interpreterProxy.stackObjectValue(3); this.interpreterProxy.stackIntegerValue(2) - 1; var length = this.interpreterProxy.stackIntegerValue(1); var dstBuf = this.interpreterProxy.stackObjectValue(0); dstBuf.bytes = srcBuf.bytes; // Just copy all there is this.interpreterProxy.popthenPush(argCount + 1, length); return true; } }; } function registerSqueakSSL() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('SqueakSSL', SqueakSSL()); } else self.setTimeout(registerSqueakSSL, 100); } registerSqueakSSL(); /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:26 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from SoundGenerationPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function SoundGenerationPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus var LoopIndexFractionMask = 511; var LoopIndexScaleFactor = 512; var ScaleFactor = 32768; var ScaledIndexOverflow = 536870912; /*** Variables ***/ var interpreterProxy = null; var moduleName = "SoundGenerationPlugin 3 November 2014 (e)"; /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } function primitiveApplyReverb() { var rcvr; var aSoundBuffer; var startIndex; var n; var delayedLeft; var delayedRight; var i; var j; var out; var sliceIndex; var tapGain; var tapIndex; var bufferIndex; var bufferSize; var leftBuffer; var rightBuffer; var tapCount; var tapDelays; var tapGains; rcvr = interpreterProxy.stackValue(3); aSoundBuffer = interpreterProxy.stackInt16Array(2); startIndex = interpreterProxy.stackIntegerValue(1); n = interpreterProxy.stackIntegerValue(0); tapDelays = interpreterProxy.fetchInt32ArrayofObject(7, rcvr); tapGains = interpreterProxy.fetchInt32ArrayofObject(8, rcvr); tapCount = interpreterProxy.fetchIntegerofObject(9, rcvr); bufferSize = interpreterProxy.fetchIntegerofObject(10, rcvr); bufferIndex = interpreterProxy.fetchIntegerofObject(11, rcvr); leftBuffer = interpreterProxy.fetchInt16ArrayofObject(12, rcvr); rightBuffer = interpreterProxy.fetchInt16ArrayofObject(13, rcvr); if (interpreterProxy.failed()) { return null; } for (sliceIndex = startIndex; sliceIndex <= ((startIndex + n) - 1); sliceIndex++) { delayedLeft = (delayedRight = 0); for (tapIndex = 1; tapIndex <= tapCount; tapIndex++) { i = bufferIndex - tapDelays[tapIndex - 1]; if (i < 1) { i += bufferSize; } tapGain = tapGains[tapIndex - 1]; delayedLeft += tapGain * leftBuffer[i - 1]; delayedRight += tapGain * rightBuffer[i - 1]; } j = (2 * sliceIndex) - 1; out = aSoundBuffer[j - 1] + (delayedLeft >> 15); if (out > 32767) { out = 32767; } if (out < -32767) { out = -32767; } aSoundBuffer[j - 1] = out; leftBuffer[bufferIndex - 1] = out; ++j; out = aSoundBuffer[j - 1] + (delayedRight >> 15); if (out > 32767) { out = 32767; } if (out < -32767) { out = -32767; } aSoundBuffer[j - 1] = out; rightBuffer[bufferIndex - 1] = out; bufferIndex = (MOD(bufferIndex, bufferSize)) + 1; } if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(11, rcvr, bufferIndex); interpreterProxy.pop(3); } /* Play samples from a wave table by stepping a fixed amount through the table on every sample. The table index and increment are scaled to allow fractional increments for greater pitch accuracy. */ /* (FMSound pitch: 440.0 dur: 1.0 loudness: 0.5) play */ function primitiveMixFMSound() { var rcvr; var n; var aSoundBuffer; var startIndex; var leftVol; var rightVol; var doingFM; var i; var lastIndex; var offset; var s; var sample; var sliceIndex; var count; var normalizedModulation; var scaledIndex; var scaledIndexIncr; var scaledOffsetIndex; var scaledOffsetIndexIncr; var scaledVol; var scaledVolIncr; var scaledVolLimit; var scaledWaveTableSize; var waveTable; rcvr = interpreterProxy.stackValue(5); n = interpreterProxy.stackIntegerValue(4); aSoundBuffer = interpreterProxy.stackInt16Array(3); startIndex = interpreterProxy.stackIntegerValue(2); leftVol = interpreterProxy.stackIntegerValue(1); rightVol = interpreterProxy.stackIntegerValue(0); scaledVol = interpreterProxy.fetchIntegerofObject(3, rcvr); scaledVolIncr = interpreterProxy.fetchIntegerofObject(4, rcvr); scaledVolLimit = interpreterProxy.fetchIntegerofObject(5, rcvr); count = interpreterProxy.fetchIntegerofObject(7, rcvr); waveTable = interpreterProxy.fetchInt16ArrayofObject(8, rcvr); scaledWaveTableSize = interpreterProxy.fetchIntegerofObject(9, rcvr); scaledIndex = interpreterProxy.fetchIntegerofObject(10, rcvr); scaledIndexIncr = interpreterProxy.fetchIntegerofObject(11, rcvr); normalizedModulation = interpreterProxy.fetchIntegerofObject(14, rcvr); scaledOffsetIndex = interpreterProxy.fetchIntegerofObject(15, rcvr); scaledOffsetIndexIncr = interpreterProxy.fetchIntegerofObject(16, rcvr); if (interpreterProxy.failed()) { return null; } doingFM = (normalizedModulation !== 0) && (scaledOffsetIndexIncr !== 0); lastIndex = (startIndex + n) - 1; for (sliceIndex = startIndex; sliceIndex <= lastIndex; sliceIndex++) { sample = (scaledVol * waveTable[scaledIndex >> 15]) >> 15; if (doingFM) { offset = normalizedModulation * waveTable[scaledOffsetIndex >> 15]; scaledOffsetIndex = MOD((scaledOffsetIndex + scaledOffsetIndexIncr), scaledWaveTableSize); if (scaledOffsetIndex < 0) { scaledOffsetIndex += scaledWaveTableSize; } scaledIndex = MOD(((scaledIndex + scaledIndexIncr) + offset), scaledWaveTableSize); if (scaledIndex < 0) { scaledIndex += scaledWaveTableSize; } } else { scaledIndex = MOD((scaledIndex + scaledIndexIncr), scaledWaveTableSize); } if (leftVol > 0) { i = (2 * sliceIndex) - 1; s = aSoundBuffer[i - 1] + ((sample * leftVol) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } if (rightVol > 0) { i = 2 * sliceIndex; s = aSoundBuffer[i - 1] + ((sample * rightVol) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } if (scaledVolIncr !== 0) { scaledVol += scaledVolIncr; if (((scaledVolIncr > 0) && (scaledVol >= scaledVolLimit)) || ((scaledVolIncr < 0) && (scaledVol <= scaledVolLimit))) { /* reached the limit; stop incrementing */ scaledVol = scaledVolLimit; scaledVolIncr = 0; } } } count -= n; if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(3, rcvr, scaledVol); interpreterProxy.storeIntegerofObjectwithValue(4, rcvr, scaledVolIncr); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, count); interpreterProxy.storeIntegerofObjectwithValue(10, rcvr, scaledIndex); interpreterProxy.storeIntegerofObjectwithValue(15, rcvr, scaledOffsetIndex); interpreterProxy.pop(5); } /* Play samples from a wave table by stepping a fixed amount through the table on every sample. The table index and increment are scaled to allow fractional increments for greater pitch accuracy. If a loop length is specified, then the index is looped back when the loopEnd index is reached until count drops below releaseCount. This allows a short sampled sound to be sustained indefinitely. */ /* (LoopedSampledSound pitch: 440.0 dur: 5.0 loudness: 0.5) play */ function primitiveMixLoopedSampledSound() { var rcvr; var n; var aSoundBuffer; var startIndex; var leftVol; var rightVol; var compositeLeftVol; var compositeRightVol; var i; var isInStereo; var lastIndex; var leftVal; var m; var nextSampleIndex; var rightVal; var s; var sampleIndex; var sliceIndex; var count; var lastSample; var leftSamples; var loopEnd; var releaseCount; var rightSamples; var scaledIndex; var scaledIndexIncr; var scaledLoopLength; var scaledVol; var scaledVolIncr; var scaledVolLimit; rcvr = interpreterProxy.stackValue(5); n = interpreterProxy.stackIntegerValue(4); aSoundBuffer = interpreterProxy.stackInt16Array(3); startIndex = interpreterProxy.stackIntegerValue(2); leftVol = interpreterProxy.stackIntegerValue(1); rightVol = interpreterProxy.stackIntegerValue(0); scaledVol = interpreterProxy.fetchIntegerofObject(3, rcvr); scaledVolIncr = interpreterProxy.fetchIntegerofObject(4, rcvr); scaledVolLimit = interpreterProxy.fetchIntegerofObject(5, rcvr); count = interpreterProxy.fetchIntegerofObject(7, rcvr); releaseCount = interpreterProxy.fetchIntegerofObject(8, rcvr); leftSamples = interpreterProxy.fetchInt16ArrayofObject(10, rcvr); rightSamples = interpreterProxy.fetchInt16ArrayofObject(11, rcvr); lastSample = interpreterProxy.fetchIntegerofObject(16, rcvr); loopEnd = interpreterProxy.fetchIntegerofObject(17, rcvr); scaledLoopLength = interpreterProxy.fetchIntegerofObject(18, rcvr); scaledIndex = interpreterProxy.fetchIntegerofObject(19, rcvr); scaledIndexIncr = interpreterProxy.fetchIntegerofObject(20, rcvr); if (interpreterProxy.failed()) { return null; } isInStereo = leftSamples !== rightSamples; compositeLeftVol = (leftVol * scaledVol) >> 15; compositeRightVol = (rightVol * scaledVol) >> 15; i = (2 * startIndex) - 1; lastIndex = (startIndex + n) - 1; for (sliceIndex = startIndex; sliceIndex <= lastIndex; sliceIndex++) { sampleIndex = ((scaledIndex += scaledIndexIncr)) >> 9; if ((sampleIndex > loopEnd) && (count > releaseCount)) { /* loop back if not within releaseCount of the note end */ /* note: unlooped sounds will have loopEnd = lastSample */ sampleIndex = ((scaledIndex -= scaledLoopLength)) >> 9; } if (((nextSampleIndex = sampleIndex + 1)) > lastSample) { if (sampleIndex > lastSample) { count = 0; if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(3, rcvr, scaledVol); interpreterProxy.storeIntegerofObjectwithValue(4, rcvr, scaledVolIncr); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, count); interpreterProxy.storeIntegerofObjectwithValue(19, rcvr, scaledIndex); interpreterProxy.popthenPush(6, null); return null; } if (scaledLoopLength === 0) { nextSampleIndex = sampleIndex; } else { nextSampleIndex = ((scaledIndex - scaledLoopLength) >> 9) + 1; } } m = scaledIndex & LoopIndexFractionMask; rightVal = (leftVal = ((leftSamples[sampleIndex - 1] * (LoopIndexScaleFactor - m)) + (leftSamples[nextSampleIndex - 1] * m)) >> 9); if (isInStereo) { rightVal = ((rightSamples[sampleIndex - 1] * (LoopIndexScaleFactor - m)) + (rightSamples[nextSampleIndex - 1] * m)) >> 9; } if (leftVol > 0) { s = aSoundBuffer[i - 1] + ((compositeLeftVol * leftVal) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } ++i; if (rightVol > 0) { s = aSoundBuffer[i - 1] + ((compositeRightVol * rightVal) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } ++i; if (scaledVolIncr !== 0) { /* update volume envelope if it is changing */ scaledVol += scaledVolIncr; if (((scaledVolIncr > 0) && (scaledVol >= scaledVolLimit)) || ((scaledVolIncr < 0) && (scaledVol <= scaledVolLimit))) { /* reached the limit; stop incrementing */ scaledVol = scaledVolLimit; scaledVolIncr = 0; } compositeLeftVol = (leftVol * scaledVol) >> 15; compositeRightVol = (rightVol * scaledVol) >> 15; } } count -= n; if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(3, rcvr, scaledVol); interpreterProxy.storeIntegerofObjectwithValue(4, rcvr, scaledVolIncr); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, count); interpreterProxy.storeIntegerofObjectwithValue(19, rcvr, scaledIndex); interpreterProxy.pop(5); } /* The Karplus-Strong plucked string algorithm: start with a buffer full of random noise and repeatedly play the contents of that buffer while averaging adjacent samples. High harmonics damp out more quickly, transfering their energy to lower ones. The length of the buffer corresponds to the length of the string. */ /* (PluckedSound pitch: 220.0 dur: 6.0 loudness: 0.8) play */ function primitiveMixPluckedSound() { var rcvr; var n; var aSoundBuffer; var startIndex; var leftVol; var rightVol; var average; var i; var lastIndex; var s; var sample; var scaledNextIndex; var scaledThisIndex; var sliceIndex; var count; var ring; var scaledIndex; var scaledIndexIncr; var scaledIndexLimit; var scaledVol; var scaledVolIncr; var scaledVolLimit; rcvr = interpreterProxy.stackValue(5); n = interpreterProxy.stackIntegerValue(4); aSoundBuffer = interpreterProxy.stackInt16Array(3); startIndex = interpreterProxy.stackIntegerValue(2); leftVol = interpreterProxy.stackIntegerValue(1); rightVol = interpreterProxy.stackIntegerValue(0); scaledVol = interpreterProxy.fetchIntegerofObject(3, rcvr); scaledVolIncr = interpreterProxy.fetchIntegerofObject(4, rcvr); scaledVolLimit = interpreterProxy.fetchIntegerofObject(5, rcvr); count = interpreterProxy.fetchIntegerofObject(7, rcvr); ring = interpreterProxy.fetchInt16ArrayofObject(8, rcvr); scaledIndex = interpreterProxy.fetchIntegerofObject(9, rcvr); scaledIndexIncr = interpreterProxy.fetchIntegerofObject(10, rcvr); scaledIndexLimit = interpreterProxy.fetchIntegerofObject(11, rcvr); if (interpreterProxy.failed()) { return null; } lastIndex = (startIndex + n) - 1; scaledThisIndex = (scaledNextIndex = scaledIndex); for (sliceIndex = startIndex; sliceIndex <= lastIndex; sliceIndex++) { scaledNextIndex = scaledThisIndex + scaledIndexIncr; if (scaledNextIndex >= scaledIndexLimit) { scaledNextIndex = ScaleFactor + (scaledNextIndex - scaledIndexLimit); } average = (ring[(scaledThisIndex >> 15) - 1] + ring[(scaledNextIndex >> 15) - 1]) >> 1; ring[(scaledThisIndex >> 15) - 1] = average; /* scale by volume */ sample = (average * scaledVol) >> 15; scaledThisIndex = scaledNextIndex; if (leftVol > 0) { i = (2 * sliceIndex) - 1; s = aSoundBuffer[i - 1] + ((sample * leftVol) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } if (rightVol > 0) { i = 2 * sliceIndex; s = aSoundBuffer[i - 1] + ((sample * rightVol) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } if (scaledVolIncr !== 0) { scaledVol += scaledVolIncr; if (((scaledVolIncr > 0) && (scaledVol >= scaledVolLimit)) || ((scaledVolIncr < 0) && (scaledVol <= scaledVolLimit))) { /* reached the limit; stop incrementing */ scaledVol = scaledVolLimit; scaledVolIncr = 0; } } } scaledIndex = scaledNextIndex; count -= n; if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(3, rcvr, scaledVol); interpreterProxy.storeIntegerofObjectwithValue(4, rcvr, scaledVolIncr); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, count); interpreterProxy.storeIntegerofObjectwithValue(9, rcvr, scaledIndex); interpreterProxy.pop(5); } /* Mix the given number of samples with the samples already in the given buffer starting at the given index. Assume that the buffer size is at least (index + count) - 1. */ function primitiveMixSampledSound() { var rcvr; var n; var aSoundBuffer; var startIndex; var leftVol; var rightVol; var i; var lastIndex; var outIndex; var overflow; var s; var sample; var sampleIndex; var count; var indexHighBits; var samples; var samplesSize; var scaledIncrement; var scaledIndex; var scaledVol; var scaledVolIncr; var scaledVolLimit; rcvr = interpreterProxy.stackValue(5); n = interpreterProxy.stackIntegerValue(4); aSoundBuffer = interpreterProxy.stackInt16Array(3); startIndex = interpreterProxy.stackIntegerValue(2); leftVol = interpreterProxy.stackIntegerValue(1); rightVol = interpreterProxy.stackIntegerValue(0); scaledVol = interpreterProxy.fetchIntegerofObject(3, rcvr); scaledVolIncr = interpreterProxy.fetchIntegerofObject(4, rcvr); scaledVolLimit = interpreterProxy.fetchIntegerofObject(5, rcvr); count = interpreterProxy.fetchIntegerofObject(7, rcvr); samples = interpreterProxy.fetchInt16ArrayofObject(8, rcvr); samplesSize = interpreterProxy.fetchIntegerofObject(10, rcvr); scaledIndex = interpreterProxy.fetchIntegerofObject(11, rcvr); indexHighBits = interpreterProxy.fetchIntegerofObject(12, rcvr); scaledIncrement = interpreterProxy.fetchIntegerofObject(13, rcvr); if (interpreterProxy.failed()) { return null; } lastIndex = (startIndex + n) - 1; /* index of next stereo output sample pair */ outIndex = startIndex; sampleIndex = indexHighBits + (scaledIndex >>> 16); while ((sampleIndex <= samplesSize) && (outIndex <= lastIndex)) { sample = (samples[sampleIndex - 1] * scaledVol) >> 15; if (leftVol > 0) { i = (2 * outIndex) - 1; s = aSoundBuffer[i - 1] + ((sample * leftVol) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } if (rightVol > 0) { i = 2 * outIndex; s = aSoundBuffer[i - 1] + ((sample * rightVol) >> 15); if (s > 32767) { s = 32767; } if (s < -32767) { s = -32767; } aSoundBuffer[i - 1] = s; } if (scaledVolIncr !== 0) { scaledVol += scaledVolIncr; if (((scaledVolIncr > 0) && (scaledVol >= scaledVolLimit)) || ((scaledVolIncr < 0) && (scaledVol <= scaledVolLimit))) { /* reached the limit; stop incrementing */ scaledVol = scaledVolLimit; scaledVolIncr = 0; } } scaledIndex += scaledIncrement; if (scaledIndex >= ScaledIndexOverflow) { overflow = scaledIndex >>> 16; indexHighBits += overflow; scaledIndex -= overflow << 16; } sampleIndex = indexHighBits + (scaledIndex >>> 16); ++outIndex; } count -= n; if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(3, rcvr, scaledVol); interpreterProxy.storeIntegerofObjectwithValue(4, rcvr, scaledVolIncr); interpreterProxy.storeIntegerofObjectwithValue(7, rcvr, count); interpreterProxy.storeIntegerofObjectwithValue(11, rcvr, scaledIndex); interpreterProxy.storeIntegerofObjectwithValue(12, rcvr, indexHighBits); interpreterProxy.pop(5); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("SoundGenerationPlugin", { primitiveMixFMSound: primitiveMixFMSound, primitiveMixSampledSound: primitiveMixSampledSound, setInterpreter: setInterpreter, getModuleName: getModuleName, primitiveApplyReverb: primitiveApplyReverb, primitiveMixPluckedSound: primitiveMixPluckedSound, primitiveMixLoopedSampledSound: primitiveMixLoopedSampledSound, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:26 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from StarSqueakPlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ (function StarSqueakPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function SHIFT(a, b) { return b < 0 ? (b < -31 ? 0 : a >>> (0-b) ) : (b > 31 ? 0 : a << b); } /*** Variables ***/ var interpreterProxy = null; var moduleName = "StarSqueakPlugin 3 November 2014 (e)"; /* Return an unsigned int pointer to the first indexable word of oop, which must be a words object. */ function checkedUnsignedIntPtrOf(oop) { interpreterProxy.success(interpreterProxy.isWords(oop)); if (interpreterProxy.failed()) { return 0; } return oop.words; } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Diffuse the integer values of the source patch variable Bitmap into the output Bitmap. Each cell of the output is the average of the NxN area around it in the source, where N = (2 * delta) + 1. */ function primitiveDiffuseFromToWidthHeightDelta() { var area; var delta; var dst; var dstOop; var endX; var endY; var height; var rowStart; var src; var srcOop; var startX; var startY; var sum; var width; var x; var x2; var y; var y2; srcOop = interpreterProxy.stackValue(4); dstOop = interpreterProxy.stackValue(3); width = interpreterProxy.stackIntegerValue(2); height = interpreterProxy.stackIntegerValue(1); delta = interpreterProxy.stackIntegerValue(0); src = checkedUnsignedIntPtrOf(srcOop); dst = checkedUnsignedIntPtrOf(dstOop); interpreterProxy.success(SIZEOF(srcOop) === SIZEOF(dstOop)); interpreterProxy.success(SIZEOF(srcOop) === (width * height)); if (interpreterProxy.failed()) { return null; } area = ((2 * delta) + 1) * ((2 * delta) + 1); for (y = 0; y <= (height - 1); y++) { startY = y - delta; if (startY < 0) { startY = 0; } endY = y + delta; if (endY >= height) { endY = height - 1; } for (x = 0; x <= (width - 1); x++) { startX = x - delta; if (startX < 0) { startX = 0; } endX = x + delta; if (endX >= width) { endX = width - 1; } sum = 0; for (y2 = startY; y2 <= endY; y2++) { rowStart = y2 * width; for (x2 = startX; x2 <= endX; x2++) { sum += src[rowStart + x2]; } } dst[(y * width) + x] = (DIV(sum, area)); } } interpreterProxy.pop(5); } /* Evaporate the integer values of the source Bitmap at the given rate. The rate is an integer between 0 and 1024, where 1024 is a scale factor of 1.0 (i.e., no evaporation). */ function primitiveEvaporateRate() { var i; var patchVar; var patchVarOop; var rate; var sz; patchVarOop = interpreterProxy.stackValue(1); rate = interpreterProxy.stackIntegerValue(0); patchVar = checkedUnsignedIntPtrOf(patchVarOop); sz = SIZEOF(patchVarOop); if (interpreterProxy.failed()) { return null; } for (i = 0; i <= (sz - 1); i++) { patchVar[i] = ((patchVar[i] * rate) >>> 10); } interpreterProxy.pop(2); } function primitiveMapFromToWidthHeightPatchSizeRgbFlagsShift() { var dst; var dstIndex; var dstOop; var h; var level; var offset; var patchSize; var pixel; var rgbFlags; var rgbMult; var rowStart; var shiftAmount; var src; var srcIndex; var srcOop; var w; var x; var y; srcOop = interpreterProxy.stackValue(6); dstOop = interpreterProxy.stackValue(5); w = interpreterProxy.stackIntegerValue(4); h = interpreterProxy.stackIntegerValue(3); patchSize = interpreterProxy.stackIntegerValue(2); rgbFlags = interpreterProxy.stackIntegerValue(1); shiftAmount = interpreterProxy.stackIntegerValue(0); src = checkedUnsignedIntPtrOf(srcOop); dst = checkedUnsignedIntPtrOf(dstOop); interpreterProxy.success(SIZEOF(dstOop) === (w * h)); interpreterProxy.success(SIZEOF(dstOop) === ((SIZEOF(srcOop) * patchSize) * patchSize)); if (interpreterProxy.failed()) { return null; } rgbMult = 0; if ((rgbFlags & 4) > 0) { rgbMult += 65536; } if ((rgbFlags & 2) > 0) { rgbMult += 256; } if ((rgbFlags & 1) > 0) { ++rgbMult; } srcIndex = -1; for (y = 0; y <= ((DIV(h, patchSize)) - 1); y++) { for (x = 0; x <= ((DIV(w, patchSize)) - 1); x++) { level = SHIFT(src[(++srcIndex)], shiftAmount); if (level > 255) { level = 255; } if (level <= 0) { /* non-transparent black */ pixel = 1; } else { pixel = level * rgbMult; } offset = ((y * w) + x) * patchSize; for (rowStart = offset; rowStart <= (offset + ((patchSize - 1) * w)); rowStart += w) { for (dstIndex = rowStart; dstIndex <= ((rowStart + patchSize) - 1); dstIndex++) { dst[dstIndex] = pixel; } } } } interpreterProxy.pop(7); } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("StarSqueakPlugin", { primitiveDiffuseFromToWidthHeightDelta: primitiveDiffuseFromToWidthHeightDelta, primitiveEvaporateRate: primitiveEvaporateRate, setInterpreter: setInterpreter, primitiveMapFromToWidthHeightPatchSizeRgbFlagsShift: primitiveMapFromToWidthHeightPatchSizeRgbFlagsShift, getModuleName: getModuleName, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* Smalltalk from Squeak4.5 with VMMaker 4.13.6 translated as JS source on 3 November 2014 1:52:20 pm */ /* Automatically generated by JSPluginCodeGenerator VMMakerJS-bf.15 uuid: fd4e10f2-3773-4e80-8bb5-c4b471a014e5 from DeflatePlugin VMMaker-bf.353 uuid: 8ae25e7e-8d2c-451e-8277-598b30e9c002 */ /* Manual fixes: 2022-01-15 VMMaker.oscog-mt.3135 and VMMaker.oscog-mt.3136 */ (function ZipPlugin() { var VM_PROXY_MAJOR = 1; var VM_PROXY_MINOR = 11; /*** Functions ***/ function CLASSOF(obj) { return typeof obj === "number" ? interpreterProxy.classSmallInteger() : obj.sqClass } function SIZEOF(obj) { return obj.pointers ? obj.pointers.length : obj.words ? obj.words.length : obj.bytes ? obj.bytes.length : 0 } function BYTESIZEOF(obj) { return obj.bytes ? obj.bytes.length : obj.words ? obj.words.length * 4 : 0 } function DIV(a, b) { return Math.floor(a / b) | 0; } // integer division function MOD(a, b) { return a - DIV(a, b) * b | 0; } // signed modulus function SHL(a, b) { return b > 31 ? 0 : a << b; } // fix JS shift function SHR(a, b) { return b > 31 ? 0 : a >>> b; } // fix JS shift /*** Constants ***/ var DeflateHashMask = 32767; var DeflateHashTableSize = 32768; var DeflateMaxDistance = 32768; var DeflateMaxDistanceCodes = 30; var DeflateMaxLiteralCodes = 286; var DeflateMaxMatch = 258; var DeflateMinMatch = 3; var DeflateWindowMask = 32767; var DeflateWindowSize = 32768; var MaxBits = 16; var StateNoMoreData = 1; /*** Variables ***/ var interpreterProxy = null; var moduleName = "ZipPlugin 3 November 2014 (e)"; var readStreamInstSize = 0; var writeStreamInstSize = 0; var zipBaseDistance = [ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576]; var zipBaseLength = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0]; var zipBitBuf = 0; var zipBitPos = 0; var zipBlockPos = 0; var zipCollection = null; var zipCollectionSize = 0; var zipCrcTable = [ 0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117]; var zipDistTable = null; var zipDistTableSize = 0; var zipDistanceCodes = [ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29]; var zipDistanceFreq = null; var zipDistances = null; var zipExtraDistanceBits = [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13]; var zipExtraLengthBits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0]; var zipHashHead = null; var zipHashTail = null; var zipHashValue = 0; var zipLitTable = null; var zipLitTableSize = 0; var zipLiteralCount = 0; var zipLiteralFreq = null; var zipLiteralSize = 0; var zipLiterals = null; var zipMatchCount = 0; var zipMatchLengthCodes = [ 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284]; var zipPosition = 0; var zipReadLimit = 0; var zipSource = null; var zipSourceLimit = 0; var zipSourcePos = 0; var zipState = 0; /* Compare the two strings and return the length of matching characters. minLength is a lower bound for match lengths that will be accepted. Note: here and matchPos are zero based. */ function comparewithmin(here, matchPos, minLength) { var length; /* First test if we can actually get longer than minLength */ if (zipCollection[here + minLength] !== zipCollection[matchPos + minLength]) { return 0; } if (zipCollection[(here + minLength) - 1] !== zipCollection[(matchPos + minLength) - 1]) { return 0; } if (zipCollection[here] !== zipCollection[matchPos]) { return 0; } if (zipCollection[here + 1] !== zipCollection[matchPos + 1]) { return 1; } length = 2; while ((length < DeflateMaxMatch) && (zipCollection[here + length] === zipCollection[matchPos + length])) { ++length; } return length; } /* Continue deflating the receiver's collection from blockPosition to lastIndex. Note that lastIndex must be at least MaxMatch away from the end of collection */ function deflateBlockchainLengthgoodMatch(lastIndex, chainLength, goodMatch) { var flushNeeded; var hasMatch; var here; var hereLength; var hereMatch; var i; var matchResult; var newLength; var newMatch; if (zipBlockPos > lastIndex) { return false; } if (zipLiteralCount >= zipLiteralSize) { return true; } hasMatch = false; here = zipBlockPos; while (here <= lastIndex) { if (!hasMatch) { /* Find the first match */ matchResult = findMatchlastLengthlastMatchchainLengthgoodMatch(here, DeflateMinMatch - 1, here, chainLength, goodMatch); insertStringAt(here); hereMatch = matchResult & 65535; hereLength = matchResult >>> 16; } matchResult = findMatchlastLengthlastMatchchainLengthgoodMatch(here + 1, hereLength, hereMatch, chainLength, goodMatch); newMatch = matchResult & 65535; /* Now check if the next match is better than the current one. If not, output the current match (provided that the current match is at least MinMatch long) */ newLength = matchResult >>> 16; if ((hereLength >= newLength) && (hereLength >= DeflateMinMatch)) { /* Encode the current match */ /* Insert all strings up to the end of the current match. Note: The first string has already been inserted. */ flushNeeded = encodeMatchdistance(hereLength, here - hereMatch); for (i = 1; i <= (hereLength - 1); i++) { insertStringAt((++here)); } hasMatch = false; ++here; } else { /* Either the next match is better than the current one or we didn't have a good match after all (e.g., current match length < MinMatch). Output a single literal. */ flushNeeded = encodeLiteral(zipCollection[here]); ++here; if ((here <= lastIndex) && (!flushNeeded)) { /* Cache the results for the next round */ insertStringAt(here); hasMatch = true; hereMatch = newMatch; hereLength = newLength; } } if (flushNeeded) { zipBlockPos = here; return true; } } zipBlockPos = here; return false; } /* Determine the inst size of the class above InflateStream by looking for the first class whose inst size is less than 13. */ function determineSizeOfReadStream(rcvr) { var sq_class; sq_class = CLASSOF(rcvr); while ((!sq_class.isNil) && (sq_class.classInstSize() >= 13)) { sq_class = sq_class.superclass(); } if (sq_class.isNil) { return false; } readStreamInstSize = sq_class.classInstSize(); return true; } /* Determine the inst size of the class above DeflateStream or ZipEncoder by looking for the first class whose inst size is less than 7. */ function determineSizeOfWriteStream(rcvr) { var sq_class; sq_class = CLASSOF(rcvr); while ((!sq_class.isNil) && (sq_class.classInstSize() >= 7)) { sq_class = sq_class.superclass(); } if (sq_class.isNil) { return false; } writeStreamInstSize = sq_class.classInstSize(); return true; } /* Encode the given literal */ function encodeLiteral(lit) { zipLiterals[zipLiteralCount] = lit; zipDistances[zipLiteralCount] = 0; zipLiteralFreq[lit]++; ++zipLiteralCount; return (zipLiteralCount === zipLiteralSize) || (((zipLiteralCount & 4095) === 0) && (shouldFlush())); } /* Encode the given match of length length starting at dist bytes ahead */ function encodeMatchdistance(length, dist) { var distance; var literal; zipLiterals[zipLiteralCount] = (length - DeflateMinMatch); zipDistances[zipLiteralCount] = dist; literal = zipMatchLengthCodes[length - DeflateMinMatch]; zipLiteralFreq[literal]++; if (dist < 257) { distance = zipDistanceCodes[dist - 1]; } else { distance = zipDistanceCodes[256 + ((dist - 1) >>> 7)]; } zipDistanceFreq[distance]++; ++zipLiteralCount; ++zipMatchCount; return (zipLiteralCount === zipLiteralSize) || (((zipLiteralCount & 4095) === 0) && (shouldFlush())); } /* Find the longest match for the string starting at here. If there is no match longer than lastLength return lastMatch/lastLength. Traverse at most maxChainLength entries in the hash table. Stop if a match of at least goodMatch size has been found. */ function findMatchlastLengthlastMatchchainLengthgoodMatch(here, lastLength, lastMatch, maxChainLength, goodMatch) { var bestLength; var chainLength; var distance; var length; var limit; var matchPos; var matchResult; /* Compute the default match result */ /* There is no way to find a better match than MaxMatch */ matchResult = (lastLength << 16) | lastMatch; if (lastLength >= DeflateMaxMatch) { return matchResult; } /* Compute the distance to the (possible) match */ matchPos = zipHashHead[updateHashAt((here + DeflateMinMatch) - 1)]; /* Note: It is required that 0 < distance < MaxDistance */ distance = here - matchPos; if (!((distance > 0) && (distance < DeflateMaxDistance))) { return matchResult; } /* Max. nr of match chain to search */ chainLength = maxChainLength; if (here > DeflateMaxDistance) { /* Limit for matches that are too old */ limit = here - DeflateMaxDistance; } else { limit = 0; } bestLength = lastLength; while (true) { /* Compare the current string with the string at match position */ /* Truncate accidental matches beyound stream position */ length = comparewithmin(here, matchPos, bestLength); if ((here + length) > zipPosition) { length = zipPosition - here; } if ((length === DeflateMinMatch) && ((here - matchPos) > (DeflateMaxDistance >> 2))) { length = DeflateMinMatch - 1; } if (length > bestLength) { /* We have a new (better) match than before */ /* Compute the new match result */ matchResult = (length << 16) | matchPos; /* There is no way to find a better match than MaxMatch */ bestLength = length; if (bestLength >= DeflateMaxMatch) { return matchResult; } if (bestLength > goodMatch) { return matchResult; } } if (!(((--chainLength)) > 0)) { return matchResult; } matchPos = zipHashTail[matchPos & DeflateWindowMask]; if (matchPos <= limit) { return matchResult; } } } /* Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important! */ function getModuleName() { return moduleName; } /* Insert the string at the given start position into the hash table. Note: The hash value is updated starting at MinMatch-1 since all strings before have already been inserted into the hash table (and the hash value is updated as well). */ function insertStringAt(here) { var prevEntry; zipHashValue = updateHashAt((here + DeflateMinMatch) - 1); prevEntry = zipHashHead[zipHashValue]; zipHashHead[zipHashValue] = here; zipHashTail[here & DeflateWindowMask] = prevEntry; } function loadDeflateStreamFrom(rcvr) { var oop; if (!(interpreterProxy.isPointers(rcvr) && (SIZEOF(rcvr) >= 15))) { return false; } oop = interpreterProxy.fetchPointerofObject(0, rcvr); if (!interpreterProxy.isBytes(oop)) { return false; } if (writeStreamInstSize === 0) { if (!determineSizeOfWriteStream(rcvr)) { return false; } if (SIZEOF(rcvr) < (writeStreamInstSize + 5)) { writeStreamInstSize = 0; return false; } } zipCollection = oop.bytes; zipCollectionSize = BYTESIZEOF(oop); zipPosition = interpreterProxy.fetchIntegerofObject(1, rcvr); /* zipWriteLimit := interpreterProxy fetchInteger: 3 ofObject: rcvr. */ zipReadLimit = interpreterProxy.fetchIntegerofObject(2, rcvr); oop = interpreterProxy.fetchPointerofObject(writeStreamInstSize + 0, rcvr); if (!(interpreterProxy.isWords(oop) && (SIZEOF(oop) === DeflateHashTableSize))) { return false; } zipHashHead = oop.words; oop = interpreterProxy.fetchPointerofObject(writeStreamInstSize + 1, rcvr); if (!(interpreterProxy.isWords(oop) && (SIZEOF(oop) === DeflateWindowSize))) { return false; } zipHashTail = oop.words; zipHashValue = interpreterProxy.fetchIntegerofObject(writeStreamInstSize + 2, rcvr); /* zipBlockStart := interpreterProxy fetchInteger: writeStreamInstSize + 4 ofObject: rcvr. */ zipBlockPos = interpreterProxy.fetchIntegerofObject(writeStreamInstSize + 3, rcvr); oop = interpreterProxy.fetchPointerofObject(writeStreamInstSize + 5, rcvr); if (!interpreterProxy.isBytes(oop)) { return false; } zipLiteralSize = SIZEOF(oop); zipLiterals = oop.bytes; oop = interpreterProxy.fetchPointerofObject(writeStreamInstSize + 6, rcvr); if (!(interpreterProxy.isWords(oop) && (SIZEOF(oop) >= zipLiteralSize))) { return false; } zipDistances = oop.words; oop = interpreterProxy.fetchPointerofObject(writeStreamInstSize + 7, rcvr); if (!(interpreterProxy.isWords(oop) && (SIZEOF(oop) === DeflateMaxLiteralCodes))) { return false; } zipLiteralFreq = oop.words; oop = interpreterProxy.fetchPointerofObject(writeStreamInstSize + 8, rcvr); if (!(interpreterProxy.isWords(oop) && (SIZEOF(oop) === DeflateMaxDistanceCodes))) { return false; } zipDistanceFreq = oop.words; zipLiteralCount = interpreterProxy.fetchIntegerofObject(writeStreamInstSize + 9, rcvr); zipMatchCount = interpreterProxy.fetchIntegerofObject(writeStreamInstSize + 10, rcvr); return !interpreterProxy.failed(); } function loadZipEncoderFrom(rcvr) { var oop; if (writeStreamInstSize === 0) { if (!determineSizeOfWriteStream(rcvr)) { return false; } if (SIZEOF(rcvr) < (writeStreamInstSize + 3)) { writeStreamInstSize = 0; return false; } } if (!(interpreterProxy.isPointers(rcvr) && (SIZEOF(rcvr) >= (writeStreamInstSize + 3)))) { return false; } oop = interpreterProxy.fetchPointerofObject(0, rcvr); if (!interpreterProxy.isBytes(oop)) { return interpreterProxy.primitiveFail(); } zipCollection = oop.bytes; zipCollectionSize = BYTESIZEOF(oop); zipPosition = interpreterProxy.fetchIntegerofObject(1, rcvr); /* zipWriteLimit := interpreterProxy fetchInteger: 3 ofObject: rcvr. */ zipReadLimit = interpreterProxy.fetchIntegerofObject(2, rcvr); zipBitBuf = interpreterProxy.fetchIntegerofObject(writeStreamInstSize + 0, rcvr); zipBitPos = interpreterProxy.fetchIntegerofObject(writeStreamInstSize + 1, rcvr); return !interpreterProxy.failed(); } /* Require: zipCollection, zipCollectionSize, zipPosition, zipBitBuf, zipBitPos. */ function nextZipBitsput(nBits, value) { if (!((value >= 0) && ((SHL(1, nBits)) > value))) { return interpreterProxy.primitiveFail(); } zipBitBuf = zipBitBuf | (SHL(value, zipBitPos)); zipBitPos += nBits; while ((zipBitPos >= 8) && (zipPosition < zipCollectionSize)) { zipCollection[zipPosition] = (zipBitBuf & 255); ++zipPosition; zipBitBuf = zipBitBuf >>> 8; zipBitPos -= 8; } } /* Primitive. Deflate the current contents of the receiver. */ function primitiveDeflateBlock() { var chainLength; var goodMatch; var lastIndex; var rcvr; var result; if (interpreterProxy.methodArgumentCount() !== 3) { return interpreterProxy.primitiveFail(); } goodMatch = interpreterProxy.stackIntegerValue(0); chainLength = interpreterProxy.stackIntegerValue(1); lastIndex = interpreterProxy.stackIntegerValue(2); rcvr = interpreterProxy.stackObjectValue(3); if (interpreterProxy.failed()) { return null; } if (!loadDeflateStreamFrom(rcvr)) { return interpreterProxy.primitiveFail(); } result = deflateBlockchainLengthgoodMatch(lastIndex, chainLength, goodMatch); if (!interpreterProxy.failed()) { /* Store back modified values */ interpreterProxy.storeIntegerofObjectwithValue(writeStreamInstSize + 2, rcvr, zipHashValue); interpreterProxy.storeIntegerofObjectwithValue(writeStreamInstSize + 3, rcvr, zipBlockPos); interpreterProxy.storeIntegerofObjectwithValue(writeStreamInstSize + 9, rcvr, zipLiteralCount); interpreterProxy.storeIntegerofObjectwithValue(writeStreamInstSize + 10, rcvr, zipMatchCount); } if (!interpreterProxy.failed()) { interpreterProxy.pop(4); interpreterProxy.pushBool(result); } } /* Primitive. Update the hash tables after data has been moved by delta. */ function primitiveDeflateUpdateHashTable() { var delta; var entry; var i; var table; var tablePtr; var tableSize; if (interpreterProxy.methodArgumentCount() !== 2) { return interpreterProxy.primitiveFail(); } delta = interpreterProxy.stackIntegerValue(0); table = interpreterProxy.stackObjectValue(1); if (interpreterProxy.failed()) { return null; } if (!interpreterProxy.isWords(table)) { return interpreterProxy.primitiveFail(); } tableSize = SIZEOF(table); tablePtr = table.wordsAsInt32Array(); for (i = 0; i <= (tableSize - 1); i++) { entry = tablePtr[i]; if (entry >= delta) { tablePtr[i] = (entry - delta); } else { tablePtr[i] = 0; } } interpreterProxy.pop(2); } /* Primitive. Inflate a single block. */ function primitiveInflateDecompressBlock() { var oop; var rcvr; if (interpreterProxy.methodArgumentCount() !== 2) { return interpreterProxy.primitiveFail(); } oop = interpreterProxy.stackValue(0); if (!interpreterProxy.isWords(oop)) { return interpreterProxy.primitiveFail(); } zipDistTable = oop.words; /* literal table */ zipDistTableSize = SIZEOF(oop); oop = interpreterProxy.stackValue(1); if (!interpreterProxy.isWords(oop)) { return interpreterProxy.primitiveFail(); } zipLitTable = oop.words; /* Receiver (InflateStream) */ zipLitTableSize = SIZEOF(oop); rcvr = interpreterProxy.stackValue(2); if (!interpreterProxy.isPointers(rcvr)) { return interpreterProxy.primitiveFail(); } if (readStreamInstSize === 0) { if (!determineSizeOfReadStream(rcvr)) { return interpreterProxy.primitiveFail(); } if (SIZEOF(rcvr) < (readStreamInstSize + 8)) { readStreamInstSize = 0; return interpreterProxy.primitiveFail(); } } if (SIZEOF(rcvr) < (readStreamInstSize + 8)) { return interpreterProxy.primitiveFail(); } zipReadLimit = interpreterProxy.fetchIntegerofObject(2, rcvr); zipState = interpreterProxy.fetchIntegerofObject(readStreamInstSize + 0, rcvr); zipBitBuf = interpreterProxy.fetchIntegerofObject(readStreamInstSize + 1, rcvr); zipBitPos = interpreterProxy.fetchIntegerofObject(readStreamInstSize + 2, rcvr); zipSourcePos = interpreterProxy.fetchIntegerofObject(readStreamInstSize + 4, rcvr); zipSourceLimit = interpreterProxy.fetchIntegerofObject(readStreamInstSize + 5, rcvr); if (interpreterProxy.failed()) { return null; } --zipReadLimit; --zipSourcePos; /* collection */ --zipSourceLimit; oop = interpreterProxy.fetchPointerofObject(0, rcvr); if (!interpreterProxy.isBytes(oop)) { return interpreterProxy.primitiveFail(); } zipCollection = oop.bytes; /* source */ zipCollectionSize = BYTESIZEOF(oop); oop = interpreterProxy.fetchPointerofObject(readStreamInstSize + 3, rcvr); if (!interpreterProxy.isBytes(oop)) { return interpreterProxy.primitiveFail(); } /* do the primitive */ zipSource = oop.bytes; zipDecompressBlock(); if (!interpreterProxy.failed()) { /* store modified values back */ interpreterProxy.storeIntegerofObjectwithValue(2, rcvr, zipReadLimit + 1); interpreterProxy.storeIntegerofObjectwithValue(readStreamInstSize + 0, rcvr, zipState); interpreterProxy.storeIntegerofObjectwithValue(readStreamInstSize + 1, rcvr, zipBitBuf); interpreterProxy.storeIntegerofObjectwithValue(readStreamInstSize + 2, rcvr, zipBitPos); interpreterProxy.storeIntegerofObjectwithValue(readStreamInstSize + 4, rcvr, zipSourcePos + 1); interpreterProxy.pop(2); } } /* Primitive. Update a 32bit CRC value. */ function primitiveUpdateAdler32() { var adler32; var b; var bytePtr; var collection; var i; var length; var s1; var s2; var startIndex; var stopIndex; if (interpreterProxy.methodArgumentCount() !== 4) { return interpreterProxy.primitiveFail(); } collection = interpreterProxy.stackObjectValue(0); stopIndex = interpreterProxy.stackIntegerValue(1); startIndex = interpreterProxy.stackIntegerValue(2); adler32 = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(3)); if (interpreterProxy.failed()) { return 0; } if (!(interpreterProxy.isBytes(collection) && ((stopIndex >= startIndex) && (startIndex > 0)))) { return interpreterProxy.primitiveFail(); } length = BYTESIZEOF(collection); if (!(stopIndex <= length)) { return interpreterProxy.primitiveFail(); } bytePtr = collection.bytes; --startIndex; --stopIndex; s1 = adler32 & 65535; s2 = (adler32 >>> 16) & 65535; for (i = startIndex; i <= stopIndex; i++) { b = bytePtr[i]; s1 = MOD((s1 + b), 65521); s2 = MOD((s2 + s1), 65521); } adler32 = (s2 << 16) + s1; interpreterProxy.popthenPush(5, interpreterProxy.positive32BitIntegerFor(adler32)); } /* Primitive. Update a 32bit CRC value. */ function primitiveUpdateGZipCrc32() { var bytePtr; var collection; var crc; var i; var length; var startIndex; var stopIndex; if (interpreterProxy.methodArgumentCount() !== 4) { return interpreterProxy.primitiveFail(); } collection = interpreterProxy.stackObjectValue(0); stopIndex = interpreterProxy.stackIntegerValue(1); startIndex = interpreterProxy.stackIntegerValue(2); crc = interpreterProxy.positive32BitValueOf(interpreterProxy.stackValue(3)); if (interpreterProxy.failed()) { return 0; } if (!(interpreterProxy.isBytes(collection) && ((stopIndex >= startIndex) && (startIndex > 0)))) { return interpreterProxy.primitiveFail(); } length = BYTESIZEOF(collection); if (!(stopIndex <= length)) { return interpreterProxy.primitiveFail(); } bytePtr = collection.bytes; --startIndex; --stopIndex; for (i = startIndex; i <= stopIndex; i++) { crc = zipCrcTable[(crc ^ bytePtr[i]) & 255] ^ (crc >>> 8); } interpreterProxy.popthenPush(5, interpreterProxy.positive32BitIntegerFor(crc)); } function primitiveZipSendBlock() { var distStream; var distTree; var litStream; var litTree; var rcvr; var result; if (interpreterProxy.methodArgumentCount() !== 4) { return interpreterProxy.primitiveFail(); } distTree = interpreterProxy.stackObjectValue(0); litTree = interpreterProxy.stackObjectValue(1); distStream = interpreterProxy.stackObjectValue(2); litStream = interpreterProxy.stackObjectValue(3); rcvr = interpreterProxy.stackObjectValue(4); if (interpreterProxy.failed()) { return null; } if (!loadZipEncoderFrom(rcvr)) { return interpreterProxy.primitiveFail(); } if (!(interpreterProxy.isPointers(distTree) && (SIZEOF(distTree) >= 2))) { return interpreterProxy.primitiveFail(); } if (!(interpreterProxy.isPointers(litTree) && (SIZEOF(litTree) >= 2))) { return interpreterProxy.primitiveFail(); } if (!(interpreterProxy.isPointers(litStream) && (SIZEOF(litStream) >= 3))) { return interpreterProxy.primitiveFail(); } if (!(interpreterProxy.isPointers(distStream) && (SIZEOF(distStream) >= 3))) { return interpreterProxy.primitiveFail(); } result = sendBlockwithwithwith(litStream, distStream, litTree, distTree); if (!interpreterProxy.failed()) { interpreterProxy.storeIntegerofObjectwithValue(1, rcvr, zipPosition); interpreterProxy.storeIntegerofObjectwithValue(writeStreamInstSize + 0, rcvr, zipBitBuf); interpreterProxy.storeIntegerofObjectwithValue(writeStreamInstSize + 1, rcvr, zipBitPos); } if (!interpreterProxy.failed()) { interpreterProxy.pop(5); interpreterProxy.pushInteger(result); } } /* Require: zipCollection, zipCollectionSize, zipPosition, zipBitBuf, zipBitPos. */ function sendBlockwithwithwith(literalStream, distanceStream, litTree, distTree) { var code; var dist; var distArray; var distBitLengths; var distBlCount; var distCodes; var extra; var lit; var litArray; var litBlCount; var litLimit; var litPos; var llBitLengths; var llCodes; var oop; var sum; oop = interpreterProxy.fetchPointerofObject(0, literalStream); litPos = interpreterProxy.fetchIntegerofObject(1, literalStream); litLimit = interpreterProxy.fetchIntegerofObject(2, literalStream); if (!((litPos <= litLimit) && (interpreterProxy.isBytes(oop) && (litLimit <= BYTESIZEOF(oop))))) { return interpreterProxy.primitiveFail(); } litArray = oop.bytes; oop = interpreterProxy.fetchPointerofObject(0, distanceStream); if (!(interpreterProxy.isWords(oop) && ((litLimit <= SIZEOF(oop)) && ((interpreterProxy.fetchIntegerofObject(1, distanceStream) === litPos) && (interpreterProxy.fetchIntegerofObject(2, distanceStream) === litLimit))))) { return interpreterProxy.primitiveFail(); } distArray = oop.words; oop = interpreterProxy.fetchPointerofObject(0, litTree); if (!interpreterProxy.isWords(oop)) { return interpreterProxy.primitiveFail(); } litBlCount = SIZEOF(oop); llBitLengths = oop.words; oop = interpreterProxy.fetchPointerofObject(1, litTree); if (!(interpreterProxy.isWords(oop) && (litBlCount === SIZEOF(oop)))) { return interpreterProxy.primitiveFail(); } llCodes = oop.words; oop = interpreterProxy.fetchPointerofObject(0, distTree); if (!interpreterProxy.isWords(oop)) { return interpreterProxy.primitiveFail(); } distBlCount = SIZEOF(oop); distBitLengths = oop.words; oop = interpreterProxy.fetchPointerofObject(1, distTree); if (!(interpreterProxy.isWords(oop) && (distBlCount === SIZEOF(oop)))) { return interpreterProxy.primitiveFail(); } distCodes = oop.words; nextZipBitsput(0, 0); sum = 0; while ((litPos < litLimit) && ((zipPosition + 4) < zipCollectionSize)) { lit = litArray[litPos]; dist = distArray[litPos]; ++litPos; if (dist === 0) { /* literal */ ++sum; if (!(lit < litBlCount)) { return interpreterProxy.primitiveFail(); } nextZipBitsput(llBitLengths[lit], llCodes[lit]); } else { /* match */ sum = (sum + lit) + DeflateMinMatch; if (!(lit < 256)) { return interpreterProxy.primitiveFail(); } code = zipMatchLengthCodes[lit]; if (!(code < litBlCount)) { return interpreterProxy.primitiveFail(); } nextZipBitsput(llBitLengths[code], llCodes[code]); extra = zipExtraLengthBits[code - 257]; if (extra !== 0) { lit -= zipBaseLength[code - 257]; nextZipBitsput(extra, lit); } --dist; if (!(dist < 32768)) { return interpreterProxy.primitiveFail(); } if (dist < 256) { code = zipDistanceCodes[dist]; } else { code = zipDistanceCodes[256 + (dist >>> 7)]; } if (!(code < distBlCount)) { return interpreterProxy.primitiveFail(); } nextZipBitsput(distBitLengths[code], distCodes[code]); extra = zipExtraDistanceBits[code]; if (extra !== 0) { dist -= zipBaseDistance[code]; nextZipBitsput(extra, dist); } } } if (interpreterProxy.failed()) { return null; } interpreterProxy.storeIntegerofObjectwithValue(1, literalStream, litPos); interpreterProxy.storeIntegerofObjectwithValue(1, distanceStream, litPos); return sum; } /* Note: This is coded so that is can be run from Squeak. */ function setInterpreter(anInterpreter) { var ok; interpreterProxy = anInterpreter; ok = interpreterProxy.majorVersion() == VM_PROXY_MAJOR; if (ok === false) { return false; } ok = interpreterProxy.minorVersion() >= VM_PROXY_MINOR; return ok; } /* Check if we should flush the current block. Flushing can be useful if the input characteristics change. */ function shouldFlush() { var nLits; if (zipLiteralCount === zipLiteralSize) { return true; } if ((zipLiteralCount & 4095) !== 0) { return false; } if ((zipMatchCount * 10) <= zipLiteralCount) { /* This is basically random data. There is no need to flush early since the overhead for encoding the trees will add to the overall size */ return false; } nLits = zipLiteralCount - zipMatchCount; if (nLits <= zipMatchCount) { return false; } return (nLits * 4) <= zipMatchCount; } /* Update the running hash value based on the next input byte. Return the new updated hash value. */ function updateHash(nextValue) { return ((zipHashValue << 5) ^ nextValue) & DeflateHashMask; } /* Update the hash value at position here (one based) */ function updateHashAt(here) { return updateHash(zipCollection[here]); } /* Decode the next value in the receiver using the given huffman table. */ function zipDecodeValueFromsize(table, tableSize) { var bits; var bitsNeeded; var index; var tableIndex; var value; /* Initial bits needed */ bitsNeeded = table[0] >>> 24; if (bitsNeeded > MaxBits) { interpreterProxy.primitiveFail(); return 0; } /* First real table */ tableIndex = 2; while (true) { /* Get bits */ bits = zipNextBits(bitsNeeded); index = (tableIndex + bits) - 1; if (index >= tableSize) { interpreterProxy.primitiveFail(); return 0; } /* Lookup entry in table */ value = table[index]; if ((value & 1056964608) === 0) { return value; } /* Table offset in low 16 bit */ tableIndex = value & 65535; /* Additional bits in high 8 bit */ bitsNeeded = (value >>> 24) & 255; if (bitsNeeded > MaxBits) { interpreterProxy.primitiveFail(); return 0; } } return 0; } function zipDecompressBlock() { var distance; var dstPos; var extra; var i; var length; var max; var oldBitPos; var oldBits; var oldPos; var srcPos; var value; max = zipCollectionSize - 1; while ((zipReadLimit < max) && (zipSourcePos <= zipSourceLimit)) { /* Back up stuff if we're running out of space */ oldBits = zipBitBuf; oldBitPos = zipBitPos; oldPos = zipSourcePos; value = zipDecodeValueFromsize(zipLitTable, zipLitTableSize); if (value < 256) { /* A literal */ zipCollection[(++zipReadLimit)] = value; } else { /* length/distance or end of block */ if (value === 256) { /* End of block */ zipState = zipState & StateNoMoreData; return 0; } extra = (value >>> 16) - 1; length = value & 65535; if (extra > 0) { length += zipNextBits(extra); } value = zipDecodeValueFromsize(zipDistTable, zipDistTableSize); extra = value >>> 16; distance = value & 65535; if (extra > 0) { distance += zipNextBits(extra); } if ((zipReadLimit + length) >= max) { zipBitBuf = oldBits; zipBitPos = oldBitPos; zipSourcePos = oldPos; return 0; } dstPos = zipReadLimit; srcPos = zipReadLimit - distance; for (i = 1; i <= length; i++) { zipCollection[dstPos + i] = zipCollection[srcPos + i]; } zipReadLimit += length; } } } function zipNextBits(n) { var bits; var byte; while (zipBitPos < n) { byte = zipSource[(++zipSourcePos)]; zipBitBuf += SHL(byte, zipBitPos); zipBitPos += 8; } bits = zipBitBuf & ((SHL(1, n)) - 1); zipBitBuf = SHR(zipBitBuf, n); zipBitPos -= n; return bits; } function registerPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule("ZipPlugin", { primitiveZipSendBlock: primitiveZipSendBlock, primitiveUpdateAdler32: primitiveUpdateAdler32, primitiveUpdateGZipCrc32: primitiveUpdateGZipCrc32, primitiveDeflateUpdateHashTable: primitiveDeflateUpdateHashTable, setInterpreter: setInterpreter, getModuleName: getModuleName, primitiveDeflateBlock: primitiveDeflateBlock, primitiveInflateDecompressBlock: primitiveInflateDecompressBlock, }); } else self.setTimeout(registerPlugin, 100); } registerPlugin(); })(); // Register module/plugin /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ // This is a minimal libc module for SqueakJS // serving mostly as a demo for FFI function libc() { return { // LIBC module getModuleName() { return "libc (SqueakJS)"; }, setInterpreter(proxy) { this.vm = proxy.vm; return true; }, // helper functions bytesToString(bytes) { const zero = bytes.indexOf(0); if (zero >= 0) bytes = bytes.subarray(0, zero); return String.fromCharCode.apply(null, bytes); }, stringToBytes(string, bytes) { for (let i = 0; i < string.length; i++) bytes[i] = string.charCodeAt(i); bytes[string.length] = 0; return bytes; }, // LIBC emulation functions called via FFI getenv(v) { v = this.bytesToString(v); switch (v) { case "USER": return this.vm.options.user || "squeak"; case "HOME": return this.vm.options.root || "/"; } this.vm.warnOnce("UNIMPLEMENTED getenv: " + v); return null; }, getcwd(buf, size) { const cwd = this.vm.options.root || "/"; if (!buf) buf = new Uint8Array(cwd.length + 1); if (size < cwd.length + 1) return 0; // should set errno to ERANGE this.stringToBytes(cwd, buf); return buf; // converted to Smalltalk String by FFI if declared as char* }, }; } function registerLibC() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('libc', libc()); } else self.setTimeout(registerLibC, 100); } registerLibC(); // This is an OpenGL implementation for SqueakJS using WebGL. // It is very much incomplete and currently only implements // the subset of OpenGL that is used by Croquet Jasmine, // but could be extended to support more. // The functions are invoked via FFI, which takes care of // converting the arguments and return values between JS // and Smalltalk. // The OpenGL context is global and created by B3DAcceleratorPlugin. // Context switching is done by B3DAcceleratorPlugin.makeCurrent(). // helpful constant lookup: // https://javagl.github.io/GLConstantsTranslator/GLConstantsTranslator.html // TODO // [X] implement draw arrays // [X] implement draw elements // [ ] implement vertex buffer objects // [X] implement material + lighting // [X] implement clip planes // [X] implement fog // [ ] implement tex coord gen // [ ] fix glBitmap for size other than 640x480 (also, make pixel perfect) // [ ] optimize list compilation glBegin/glEnd // [ ] implement light attenuation // [ ] implement spot lights // [ ] implement color material // [ ] emulate glLineWidth (WebGL usually only supports 1px lines) // e.g. using https://wwwtyro.net/2019/11/18/instanced-lines.html // [ ] full OpenGL 1.0 support // [ ] full OpenGL 1.1 support // [ ] full OpenGL 1.2 support // [ ] full OpenGL 1.3 support // [ ] full OpenGL 1.4 support // [ ] full OpenGL 1.5 support // [ ] some extensions? // OpenGL constants (many missing in WebGL) var GL; function OpenGL() { var DEBUG = 0; // 0 = off // 1 = some (errors, warnings) // 2 = lots (function calls) // 3 = all (call details) var identity = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ]); // Primitive attributes for glBegin/glEnd var flagCounter = 0; var HAS_NORMAL = 1 << flagCounter++; var HAS_COLOR = 1 << flagCounter++; var HAS_TEXCOORD = 1 << flagCounter++; // additional flags for selecting shader var USE_TEXTURE = 1 << flagCounter++; var USE_ALPHA_TEST = 1 << flagCounter++; var USE_POINT_SIZE = 1 << flagCounter++; var NUM_LIGHTS_MASK = (1 << flagCounter++) // 3 bits for number of lights (0-7) + (1 << flagCounter++) + (1 << flagCounter++); var NUM_CLIP_PLANES_MASK = (1 << flagCounter++) // 3 bits for number of clip planes (0-5) + (1 << flagCounter++) + (1 << flagCounter++); var FOG_MASK = (1 << flagCounter++) // 2 bits for fog mode (off, linear, exp, exp2) + (1 << flagCounter++); // this math is silly but fun ... var NUM_LIGHTS_SHIFT = Math.floor(Math.log2(NUM_LIGHTS_MASK)) - 2; var MAX_LIGHTS = (NUM_LIGHTS_MASK >> NUM_LIGHTS_SHIFT) + 1; var ANY_LIGHTS = (MAX_LIGHTS-1) << NUM_LIGHTS_SHIFT; if (ANY_LIGHTS !== NUM_LIGHTS_MASK) throw Error("OpenGL: bad NUM_LIGHTS_MASK"); var NUM_CLIP_PLANES_SHIFT = Math.floor(Math.log2(NUM_CLIP_PLANES_MASK)) - 2; var MAX_CLIP_PLANES = (NUM_CLIP_PLANES_MASK >> NUM_CLIP_PLANES_SHIFT) + 1; var ANY_CLIP_PLANES = (MAX_CLIP_PLANES-1) << NUM_CLIP_PLANES_SHIFT; if (ANY_CLIP_PLANES !== NUM_CLIP_PLANES_MASK) throw Error("OpenGL: bad NUM_CLIP_PLANES_MASK"); var FOG_SHIFT = Math.floor(Math.log2(FOG_MASK)) - 1; var MAX_FOG = (FOG_MASK >> FOG_SHIFT) + 1; var ANY_FOG = (MAX_FOG-1) << FOG_SHIFT; if (ANY_FOG !== FOG_MASK) throw Error("OpenGL: bad ANY_FOG"); var NO_FOG = 0; var LINEAR_FOG = 1; var EXP_FOG = 2; var EXP2_FOG = 3; var gl; // the emulated OpenGL state var webgl; // the actual WebGL context return { getModuleName: function() { return 'libGL.so (SqueakJS)'; }, setInterpreter: function(anInterpreterProxy) { this.vm = anInterpreterProxy.vm; this.ffi = this.vm.primHandler; return true; }, ffiFunctionNotFoundHandler: function(name, args) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED (missing) " + name); return null; // do not fail but return nil }, initialiseModule: function() { if (!GL) initGLConstants(); // connect to B3DAcceleratorPlugin to get WebGL context var modules = SqueakJS.vm.primHandler.loadedModules; var B3DAcceleratorPlugin = modules['B3DAcceleratorPlugin']; if (!B3DAcceleratorPlugin) throw Error("OpenGL: B3DAcceleratorPlugin not loaded"); this.GL = GL; B3DAcceleratorPlugin.setOpenGL(this); // will call makeCurrent() }, makeCurrent: function(renderer) { if (webgl === renderer.webgl) return; // already current webgl = renderer.webgl; gl = renderer.opengl; if (!gl) renderer.opengl = this.initGL(); }, initGL: function() { // if webgl-lint is loaded, configure it const ext = webgl.getExtension('GMAN_debug_helper'); if (ext) ext.setConfiguration({ throwOnError: false, }); // if Spector script is loaded, capture WebGL calls if (typeof SPECTOR !== "undefined") { var spector = new SPECTOR.Spector(); spector.captureContext(webgl); spector.displayUI(); } // initialize emulated OpenGL state gl = { alphaTest: false, alphaFunc: null, alphaRef: 0, extensions: "GL_ARB_texture_non_power_of_two GL_SGIS_generate_mipmap GL_ARB_transpose_matrix", color: new Float32Array(4), normal: new Float32Array([0, 0, 1]), texCoord: new Float32Array(2), primitive: null, // for glBegin/glEnd primitiveAttrs: 0, // for glVertex clipPlanes: [], // clip plane equations clientState: {}, // enabled arrays by attr fogMode: GL.EXP, // fog mode fogEnabled: false, // fog enabled fogDensity: 1, // fog density fogStart: 0, // fog start fogEnd: 1, // fog end fogColor: new Float32Array([0, 0, 0, 0]), // fog color fogHint: GL.DONT_CARE, // fog hint shaders: {}, // shader programs by attr/flags matrixMode: 0, // current matrix mode matrices: {}, // matrix stacks by mode matrix: null, // current matrix (matrices[mode][0]) lightingEnabled: false, lights: [], // light states lightModelAmbient: null, // scene ambient color material: null, // material state textureIdGen: 0, // texture id generator textures: {}, // webgl texture objects by id texture: null, // texture textureEnabled: false, // texture enabled textureEnvMode: GL.MODULATE, // texture environment mode listIdGen: 0, // display list id generator lists: {}, // display lists by id list: null, // current display list listMode: 0, // current display list mode listBase: 0, // base for glCallLists pixelStoreUnpackRowLength: 0, pixelStoreUnpackSkipRows: 0, pixelStoreUnpackSkipPixels: 0, rasterPos: new Float32Array(4), rasterColor: new Float32Array([1, 1, 1, 1]), bitmapTexture: null, // texture for glBitmap bitmapVertexBuffer: null, // vertex buffer for glBitmap bitmapShader: { // shader program for glBitmap program: null, locations: {}, }, // shader for glBitmap vendor: "Codefrau", renderer: "SqueakJS", version: "1.0", viewport: new Int32Array([0, 0, 0, 0]), depthRange: new Float32Array([0, 1]), }; // set initial state gl.matrices[GL.MODELVIEW] = [new Float32Array(identity)]; gl.matrices[GL.PROJECTION] = [new Float32Array(identity)]; gl.matrices[GL.TEXTURE] = [new Float32Array(identity)]; gl.matrixMode = GL.MODELVIEW; gl.matrix = gl.matrices[gl.matrixMode][0]; gl.color.set([1, 1, 1, 1]); for (var i = 0; i < MAX_CLIP_PLANES; i++) { gl.clipPlanes[i] = { enabled: false, equation: new Float32Array([0, 0, 0, 0]), }; } for (var i = 0; i < MAX_LIGHTS; i++) { gl.lights[i] = { enabled: false, ambient: new Float32Array([0, 0, 0, 1]), diffuse: new Float32Array(i === 0 ? [1, 1, 1, 1] : [0, 0, 0, 1]), specular: new Float32Array(i === 0 ? [1, 1, 1, 1] : [0, 0, 0, 1]), position: new Float32Array([0, 0, 1, 0]), }; } gl.lightModelAmbient = new Float32Array([0.2, 0.2, 0.2, 1]); gl.material = { ambient: new Float32Array([0.2, 0.2, 0.2, 1]), diffuse: new Float32Array([0.8, 0.8, 0.8, 1]), specular: new Float32Array([0, 0, 0, 1]), emission: new Float32Array([0, 0, 0, 1]), shininess: 0, }; var clientStates = ["vertexArray", "normalArray", "colorArray", "textureCoordArray"]; for (var i = 0; i < clientStates.length; i++) { var attr = clientStates[i]; gl.clientState[attr] = { enabled: false, size: 0, type: GL.FLOAT, stride: 0, pointer: null, // binding: null, TODO: support VBOs }; } return gl; }, destroyGL: function(renderer) { // TODO: delete textures, arrays, shaders? renderer.opengl = null; webgl = null; gl = null; }, // FFI functions get JS args, return JS result addToList: function(name, args) { if (!gl.list) return false; gl.list.commands.push({name: name, args: args}); if (gl.listMode === GL.COMPILE) { return true; } return false; }, glAlphaFunc: function(func, ref) { if (gl.listMode && this.addToList("glAlphaFunc", [func, ref])) return; gl.alphaFunc = func; gl.alphaRef = ref; }, glBegin: function(mode) { if (gl.listMode && this.addToList("glBegin", [mode])) return; gl.primitive = { mode: mode, vertices: [], vertexSize: 0, vertexAttrs: 0, }; gl.primitiveAttrs = 0; }, glBindTexture: function(target, texture) { if (gl.listMode && this.addToList("glBindTexture", [target, texture])) return; var textureObj = gl.textures[texture]; if (!textureObj) throw Error("OpenGL: texture not found"); webgl.bindTexture(target, textureObj); gl.texture = textureObj; }, glBitmap: function(width, height, xorig, yorig, xmove, ymove, bitmap) { // bitmap is supposed to be declared as "GLubyte*" per OpenGL spec, // which the FFI would convert to Uint8Array for us. However, the // image FFI declaration uses "void*", probably because it makes no // difference in C, a pointer is a pointer. In JS, we get an // ArrayBuffer for "void*" so we need to convert it to Uint8Array // ourselves. if (!bitmap.buffer) bitmap = new Uint8Array(bitmap); if (gl.listMode && this.addToList("glBitmap", [width, height, xorig, yorig, xmove, ymove, bitmap])) return; if (width > 0 && height > 0) { // we need to convert the 1-bit deep bitmap to a 1-byte // per pixel texture in ALPHA format, with the bitmap // mapping 0-bits to transparent, 1-bits to opaque, // and then draw it as a textured quad covering the viewport var texels = new Uint8Array(width * height); var bytesPerRow = Math.ceil(width / 32) * 4; for (var y = 0; y < height; y++) { var byteIndex = y * bytesPerRow; var bitIndex = 7; for (var x = 0; x < width; x++) { var bit = bitmap[byteIndex] & (1 << bitIndex); if (bit) texels[y * width + x] = 255; bitIndex--; if (bitIndex < 0) { byteIndex++; bitIndex = 7; } } } // debug: print bitmap // s=''; for (y = height -1 ; y >= 0; y--) { for (x = 0; x < width; x++) s += texels[y * width + x] ? '⬛️' : '⬜️'; s+='\n'}; console.log(s) var texture = gl.bitmapTexture; if (!texture) { texture = gl.bitmapTexture = webgl.createTexture(); webgl.bindTexture(webgl.TEXTURE_2D, texture); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.LINEAR); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE); webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE); } else { webgl.bindTexture(webgl.TEXTURE_2D, texture); } webgl.pixelStorei(webgl.UNPACK_ALIGNMENT, 1); webgl.texImage2D(webgl.TEXTURE_2D, 0, webgl.ALPHA, width, height, 0, webgl.ALPHA, webgl.UNSIGNED_BYTE, texels); webgl.pixelStorei(webgl.UNPACK_ALIGNMENT, 4); webgl.disable(webgl.CULL_FACE); webgl.disable(webgl.DEPTH_TEST); webgl.disable(webgl.BLEND); webgl.colorMask(true, true, true, true); webgl.viewport(0, 0, webgl.drawingBufferWidth, webgl.drawingBufferHeight); var vertexBuffer = gl.bitmapVertexBuffer; if (!vertexBuffer) { var vertices = new Float32Array([ 0, 0, 1, 0, 0, 1, 1, 1, ]); vertexBuffer = gl.bitmapVertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, vertices, webgl.STATIC_DRAW); } else { webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); } var shader = gl.bitmapShader; if (!shader.program) { shader.program = webgl.createProgram(); var vs = webgl.createShader(webgl.VERTEX_SHADER); webgl.shaderSource(vs, ` attribute vec2 a_position; uniform vec3 u_raster; uniform vec2 u_rasterOffset; uniform vec2 u_rasterScale; uniform vec2 u_translate; uniform vec2 u_scale; varying vec2 v_texcoord; void main() { vec2 raster = u_raster.xy * u_rasterScale + u_rasterOffset; vec2 pos = raster + a_position * u_scale + u_translate; gl_Position = vec4(pos, u_raster.z, 1); v_texcoord = a_position; } `); webgl.compileShader(vs); if (!webgl.getShaderParameter(vs, webgl.COMPILE_STATUS)) { console.error("OpenGL: vertex shader compile error: " + webgl.getShaderInfoLog(vs)); debugger; return; } var fs = webgl.createShader(webgl.FRAGMENT_SHADER); webgl.shaderSource(fs, ` precision mediump float; uniform sampler2D u_texture; uniform vec4 u_color; varying vec2 v_texcoord; void main() { float alpha = texture2D(u_texture, v_texcoord).a; if (alpha < 0.5) discard; gl_FragColor = u_color; } `); webgl.compileShader(fs); if (!webgl.getShaderParameter(fs, webgl.COMPILE_STATUS)) { console.error("OpenGL: fragment shader compile error: " + webgl.getShaderInfoLog(fs)); debugger; return; } webgl.attachShader(shader.program, vs); webgl.attachShader(shader.program, fs); webgl.linkProgram(shader.program); if (!webgl.getProgramParameter(shader.program, webgl.LINK_STATUS)) { console.error("OpenGL: shader link error: " + webgl.getProgramInfoLog(shader.program)); debugger return; } shader.locations = { a_position: webgl.getAttribLocation(shader.program, "a_position"), u_texture: webgl.getUniformLocation(shader.program, "u_texture"), u_color: webgl.getUniformLocation(shader.program, "u_color"), u_raster: webgl.getUniformLocation(shader.program, "u_raster"), u_rasterOffset: webgl.getUniformLocation(shader.program, "u_rasterOffset"), u_rasterScale: webgl.getUniformLocation(shader.program, "u_rasterScale"), u_translate: webgl.getUniformLocation(shader.program, "u_translate"), u_scale: webgl.getUniformLocation(shader.program, "u_scale"), }; } webgl.useProgram(shader.program); webgl.enableVertexAttribArray(shader.locations.a_position); webgl.vertexAttribPointer(shader.locations.a_position, 2, webgl.FLOAT, false, 0, 0); webgl.uniform1i(shader.locations.u_texture, 0); webgl.uniform4fv(shader.locations.u_color, gl.rasterColor); var w = webgl.drawingBufferWidth; var h = webgl.drawingBufferHeight; // this still isn't pixel perfect. Appears to work best for 640x480 // but not when changing the extent?! Weird. Also, some letters are still // cut off (like "m"). if (!this.bitmapRasterScale) this.bitmapRasterScale = [2/w, 2/h]; if (!this.bitmapScale) this.bitmapScale = [2*width/w, 2*height/h]; if (!this.bitmapTranslate) this.bitmapTranslate = [0, 0]; if (!this.bitmapRasterOffset) this.bitmapRasterOffset = [-1, -1]; // the properties above are written to allow intereactive debugging webgl.uniform3f(shader.locations.u_raster, gl.rasterPos[0] - xorig, gl.rasterPos[1] - yorig, gl.rasterPos[2]); webgl.uniform2fv(shader.locations.u_rasterOffset, this.bitmapRasterOffset); webgl.uniform2fv(shader.locations.u_rasterScale, this.bitmapRasterScale); webgl.uniform2fv(shader.locations.u_translate, this.bitmapTranslate); webgl.uniform2fv(shader.locations.u_scale, this.bitmapScale); webgl.drawArrays(webgl.TRIANGLE_STRIP, 0, 4); webgl.disableVertexAttribArray(shader.locations.a_position); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.useProgram(null); webgl.bindTexture(webgl.TEXTURE_2D, null); webgl.enable(webgl.CULL_FACE); webgl.enable(webgl.DEPTH_TEST); webgl.enable(webgl.BLEND); } gl.rasterPos[0] += xmove; gl.rasterPos[1] += ymove; }, glBlendFunc: function(sfactor, dfactor) { if (gl.listMode && this.addToList("glBlendFunc", [sfactor, dfactor])) return; webgl.blendFunc(sfactor, dfactor); }, glCallList: function(list) { if (gl.listMode && this.addToList("glCallList", [list])) return; this.executeList(list); }, glCallLists: function(n, type, lists) { if (gl.listMode && this.addToList("glCallLists", [n, type, lists])) return; var array; switch (type) { case GL.BYTE: array = new Int8Array(lists); break; case GL.UNSIGNED_BYTE: array = new Uint8Array(lists); break; case GL.INT: array = new Int32Array(lists); break; case GL.UNSIGNED_INT: array = new Uint32Array(lists); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glCallLists type " + GL_Symbol(type)); return; } for (var i = 0; i < n; i++) { var list = gl.listBase + array[i]; this.executeList(list); } }, glClear: function(mask) { if (gl.listMode && this.addToList("glClear", [mask])) return; if (mask & webgl.COLOR_BUFFER_BIT) ; if (mask & webgl.DEPTH_BUFFER_BIT) ; if (mask & webgl.STENCIL_BUFFER_BIT) ; webgl.clear(mask); // B3DAcceleratorPlugin will call vm.breakNow() // to emulate double buffering (which will return // control to the browser which will flush the canvas). // We discourage breaking until then to avoid flicker // glClear is a good place for that since it's usually // called at least once per frame this.vm.breakAfter(500); }, glClearColor: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glClearColor", [red, green, blue, alpha])) return; webgl.clearColor(red, green, blue, alpha); }, glColor3f: function(red, green, blue) { if (gl.listMode && this.addToList("glColor3f", [red, green, blue])) return; gl.color[0] = red; gl.color[1] = green; gl.color[2] = blue; gl.color[3] = 1; gl.primitiveAttrs |= HAS_COLOR; }, glColor3fv: function(v) { if (gl.listMode && this.addToList("glColor3fv", [v.slice()])) return; gl.color.set(v); gl.color[3] = 1; gl.primitiveAttrs |= HAS_COLOR; }, glColor4d: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glColor4d", [red, green, blue, alpha])) return; gl.color[0] = red; gl.color[1] = green; gl.color[2] = blue; gl.color[3] = alpha; gl.primitiveAttrs |= HAS_COLOR; }, glColor4f: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glColor4f", [red, green, blue, alpha])) return; gl.color[0] = red; gl.color[1] = green; gl.color[2] = blue; gl.color[3] = alpha; gl.primitiveAttrs |= HAS_COLOR; }, glColor4fv: function(v) { if (gl.listMode && this.addToList("glColor4fv", [v.slice()])) return; gl.color.set(v); gl.primitiveAttrs |= HAS_COLOR; }, glColorPointer: function(size, type, stride, pointer) { if (gl.listMode && this.addToList("glColorPointer", [size, type, stride, pointer])) return; gl.clientState.colorArray.size = size; gl.clientState.colorArray.type = type; gl.clientState.colorArray.stride = stride; gl.clientState.colorArray.pointer = pointer; }, glColorMask: function(red, green, blue, alpha) { if (gl.listMode && this.addToList("glColorMask", [red, green, blue, alpha])) return; webgl.colorMask(red, green, blue, alpha); }, glClipPlane: function(plane, equation) { if (gl.listMode && this.addToList("glClipPlane", [plane, equation])) return; var clipPlane = gl.clipPlanes[plane - GL.CLIP_PLANE0]; // multiply by inverse of modelview matrix var m = new Float32Array(16); invertMatrix(gl.matrices[GL.MODELVIEW][0], m); transposeMatrix(m); multVec4(m, equation, clipPlane.equation); }, glDeleteLists: function(list, range) { for (var i = 0; i < range; i++) { delete gl.lists[list + i]; } }, glDeleteTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = textures[i]; var texture = gl.textures[id]; if (texture) { webgl.deleteTexture(texture); if (gl.texture === texture) gl.texture = null; } delete gl.textures[id]; } }, glDepthFunc: function(func) { if (gl.listMode && this.addToList("glDepthFunc", [func])) return; webgl.depthFunc(func); }, glDepthMask: function(flag) { if (gl.listMode && this.addToList("glDepthMask", [flag])) return; webgl.depthMask(flag); }, glDepthRange: function(zNear, zFar) { if (gl.listMode && this.addToList("glDepthRange", [zNear, zFar])) return; webgl.depthRange(zNear, zFar); }, glDisable: function(cap) { if (gl.listMode && this.addToList("glDisable", [cap])) return; switch (cap) { case GL.ALPHA_TEST: gl.alphaTest = false; break; case webgl.BLEND: webgl.disable(webgl.BLEND); break; case GL.CLIP_PLANE0: case GL.CLIP_PLANE1: case GL.CLIP_PLANE2: case GL.CLIP_PLANE3: case GL.CLIP_PLANE4: case GL.CLIP_PLANE5: case GL.CLIP_PLANE6: case GL.CLIP_PLANE7: gl.clipPlanes[cap - GL.CLIP_PLANE0].enabled = false; break; case webgl.CULL_FACE: webgl.disable(webgl.CULL_FACE); break; case webgl.DEPTH_TEST: webgl.disable(webgl.DEPTH_TEST); break; case GL.FOG: gl.fogEnabled = false; break; case GL.NORMALIZE: // we always normalize normals break; case GL.LIGHT0: case GL.LIGHT1: case GL.LIGHT2: case GL.LIGHT3: case GL.LIGHT4: case GL.LIGHT5: case GL.LIGHT6: case GL.LIGHT7: gl.lights[cap - GL.LIGHT0].enabled = false; break; case GL.LIGHTING: gl.lightingEnabled = false; break; case webgl.POLYGON_OFFSET_FILL: webgl.disable(webgl.POLYGON_OFFSET_FILL); break; case webgl.STENCIL_TEST: webgl.disable(webgl.STENCIL_TEST); break; case webgl.TEXTURE_2D: gl.textureEnabled = false; break; case GL.TEXTURE_GEN_S: case GL.TEXTURE_GEN_T: case GL.TEXTURE_GEN_R: case GL.TEXTURE_GEN_Q: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDisable GL_TEXTURE_GEN_" + (cap - GL.TEXTURE_GEN_S)); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDisable " + GL_Symbol(cap)); } }, glDisableClientState: function(cap) { if (gl.listMode && this.addToList("glDisableClientState", [cap])) return; switch (cap) { case GL.VERTEX_ARRAY: gl.clientState.vertexArray.enabled = false; return; case GL.NORMAL_ARRAY: gl.clientState.normalArray.enabled = false; return; case GL.COLOR_ARRAY: gl.clientState.colorArray.enabled = false; return; case GL.TEXTURE_COORD_ARRAY: gl.clientState.textureCoordArray.enabled = false; return; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDisableClientState " + GL_Symbol(cap)); } }, glDrawArrays: function(mode, first, count) { if (gl.listMode && this.addToList("glDrawArrays", [mode, first, count])) return; var geometryFlags = 0; if (gl.clientState.normalArray.enabled) geometryFlags |= HAS_NORMAL; if (gl.clientState.colorArray.enabled) geometryFlags |= HAS_COLOR; if (gl.clientState.textureCoordArray.enabled) geometryFlags |= HAS_TEXCOORD; if (mode === GL.POINTS) geometryFlags |= USE_POINT_SIZE; var shader = this.getShader(geometryFlags); if (!shader.program) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDrawArrays shader: " + shader.label); return; } var vertexArray = gl.clientState.vertexArray; if (!vertexArray.enabled || !vertexArray.pointer) { return; } webgl.useProgram(shader.program); this.setShaderUniforms(shader); var loc = shader.locations; var vertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, vertexArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aPosition'], vertexArray.size, vertexArray.type, false, vertexArray.stride, 0); webgl.enableVertexAttribArray(loc['aPosition']); var normalBuffer; if (loc['aNormal'] >= 0) { var normalArray = gl.clientState.normalArray; normalBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, normalBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, normalArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aNormal'], normalArray.size, normalArray.type, false, normalArray.stride, 0); webgl.enableVertexAttribArray(loc['aNormal']); } var colorBuffer; if (loc['aColor'] >= 0) { var colorArray = gl.clientState.colorArray; colorBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, colorBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, colorArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aColor'], colorArray.size, colorArray.type, false, colorArray.stride, 0); webgl.enableVertexAttribArray(loc['aColor']); } var texCoordBuffer; if (loc['aTexCoord'] >= 0) { var texCoordArray = gl.clientState.textureCoordArray; texCoordBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, texCoordBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, texCoordArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aTexCoord'], texCoordArray.size, texCoordArray.type, false, texCoordArray.stride, 0); webgl.enableVertexAttribArray(loc['aTexCoord']); } webgl.drawArrays(mode, first, count); webgl.useProgram(null); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.disableVertexAttribArray(loc['aPosition']); webgl.deleteBuffer(vertexBuffer); if (normalBuffer) { webgl.disableVertexAttribArray(loc['aNormal']); webgl.deleteBuffer(normalBuffer); } if (colorBuffer) { webgl.disableVertexAttribArray(loc['aColor']); webgl.deleteBuffer(colorBuffer); } if (texCoordBuffer) { webgl.disableVertexAttribArray(loc['aTexCoord']); webgl.deleteBuffer(texCoordBuffer); } }, glDrawElements: function(mode, count, type, indicesPtr) { if (gl.listMode && this.addToList("glDrawElements", [mode, count, type, indicesPtr])) return; var indices; switch (type) { case GL.UNSIGNED_BYTE: indices = new Uint8Array(indicesPtr); break; case GL.UNSIGNED_SHORT: indices = new Uint16Array(indicesPtr); break; case GL.UNSIGNED_INT: // not directly supported by WebGL without OES_element_index_uint var indices32 = new Uint32Array(indicesPtr); var max = Math.max.apply(null, indices32); if (max > 0xFFFF) console.warn("OpenGL: glDrawElements with indices > 65535 not supported, truncating", max); if (max <= 0xFF) { indices = new Uint8Array(count); type = GL.UNSIGNED_BYTE; } else { indices = new Uint16Array(count); type = GL.UNSIGNED_SHORT; } for (var i = 0; i < count; i++) indices[i] = indices32[i]; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDrawElements type " + GL_Symbol(type)); return; } // convert quads to triangles if (mode === GL.QUADS) { var arrayClass = indices.constructor; var newIndices = new arrayClass(count * 6 / 4); var j = 0; for (var i = 0; i < count; i += 4) { newIndices[j++] = indices[i]; newIndices[j++] = indices[i + 1]; newIndices[j++] = indices[i + 2]; newIndices[j++] = indices[i]; newIndices[j++] = indices[i + 2]; newIndices[j++] = indices[i + 3]; } indices = newIndices; count = newIndices.length; mode = GL.TRIANGLES; } var geometryFlags = 0; if (gl.clientState.normalArray.enabled) geometryFlags |= HAS_NORMAL; if (gl.clientState.colorArray.enabled) geometryFlags |= HAS_COLOR; if (gl.clientState.textureCoordArray.enabled) geometryFlags |= HAS_TEXCOORD; if (mode === GL.POINTS) geometryFlags |= USE_POINT_SIZE; var shader = this.getShader(geometryFlags); if (!shader.program) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glDrawElements shader: " + shader.label); return; } var vertexArray = gl.clientState.vertexArray; if (!vertexArray.enabled || !vertexArray.pointer) { return; } webgl.useProgram(shader.program); this.setShaderUniforms(shader); var loc = shader.locations; var vertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, vertexArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aPosition'], vertexArray.size, vertexArray.type, false, vertexArray.stride, 0); webgl.enableVertexAttribArray(loc['aPosition']); var normalBuffer; if (loc['aNormal'] >= 0) { var normalArray = gl.clientState.normalArray; normalBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, normalBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, normalArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aNormal'], normalArray.size, normalArray.type, false, normalArray.stride, 0); webgl.enableVertexAttribArray(loc['aNormal']); } var colorBuffer; if (loc['aColor'] >= 0) { var colorArray = gl.clientState.colorArray; colorBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, colorBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, colorArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aColor'], colorArray.size, colorArray.type, false, colorArray.stride, 0); webgl.enableVertexAttribArray(loc['aColor']); } var texCoordBuffer; if (loc['aTexCoord'] >= 0) { var texCoordArray = gl.clientState.textureCoordArray; texCoordBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, texCoordBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, texCoordArray.pointer, webgl.DYNAMIC_DRAW); webgl.vertexAttribPointer(loc['aTexCoord'], texCoordArray.size, texCoordArray.type, false, texCoordArray.stride, 0); webgl.enableVertexAttribArray(loc['aTexCoord']); } var indexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, indexBuffer); webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, indices, webgl.DYNAMIC_DRAW); webgl.drawElements(mode, indices.length, type, 0); webgl.useProgram(null); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, null); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.deleteBuffer(indexBuffer); webgl.disableVertexAttribArray(loc['aPosition']); webgl.deleteBuffer(vertexBuffer); if (normalBuffer) { webgl.disableVertexAttribArray(loc['aNormal']); webgl.deleteBuffer(normalBuffer); } if (colorBuffer) { webgl.disableVertexAttribArray(loc['aColor']); webgl.deleteBuffer(colorBuffer); } if (texCoordBuffer) { webgl.disableVertexAttribArray(loc['aTexCoord']); webgl.deleteBuffer(texCoordBuffer); } }, glEnable: function(cap) { if (gl.listMode && this.addToList("glEnable", [cap])) return; switch (cap) { case GL.ALPHA_TEST: gl.alphaTest = true; break; case webgl.BLEND: webgl.enable(webgl.BLEND); break; case GL.CLIP_PLANE0: case GL.CLIP_PLANE1: case GL.CLIP_PLANE2: case GL.CLIP_PLANE3: case GL.CLIP_PLANE4: case GL.CLIP_PLANE5: case GL.CLIP_PLANE6: case GL.CLIP_PLANE7: gl.clipPlanes[cap - GL.CLIP_PLANE0].enabled = true; break; case webgl.CULL_FACE: webgl.enable(webgl.CULL_FACE); break; case webgl.DEPTH_TEST: webgl.enable(webgl.DEPTH_TEST); break; case GL.FOG: gl.fogEnabled = true; break; case GL.NORMALIZE: // we always normalize normals break; case GL.LIGHT0: case GL.LIGHT1: case GL.LIGHT2: case GL.LIGHT3: case GL.LIGHT4: case GL.LIGHT5: case GL.LIGHT6: case GL.LIGHT7: gl.lights[cap - GL.LIGHT0].enabled = true; break; case GL.LIGHTING: gl.lightingEnabled = true; break; case webgl.POLYGON_OFFSET_FILL: webgl.enable(webgl.POLYGON_OFFSET_FILL); break; case webgl.STENCIL_TEST: webgl.enable(webgl.STENCIL_TEST); break; case webgl.TEXTURE_2D: gl.textureEnabled = true; break; case GL.TEXTURE_GEN_S: case GL.TEXTURE_GEN_T: case GL.TEXTURE_GEN_R: case GL.TEXTURE_GEN_Q: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnable GL_" + GL_Symbol(cap, "TEXTURE_GEN_S")); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnable " + GL_Symbol(cap)); } }, glEnableClientState: function(cap) { if (gl.listMode && this.addToList("glEnableClientState", [cap])) return; switch (cap) { case GL.VERTEX_ARRAY: gl.clientState.vertexArray.enabled = true; return; case GL.NORMAL_ARRAY: gl.clientState.normalArray.enabled = true; return; case GL.COLOR_ARRAY: gl.clientState.colorArray.enabled = true; return; case GL.TEXTURE_COORD_ARRAY: gl.clientState.textureCoordArray.enabled = true; return; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnableClientState " + GL_Symbol(cap)); } }, glFog: function(pname, param) { if (gl.listMode && this.addToList("glFog", [pname, param])) return; switch (pname) { case GL.FOG_MODE: gl.fogMode = param; break; case GL.FOG_DENSITY: gl.fogDensity = param; break; case GL.FOG_START: gl.fogStart = param; break; case GL.FOG_END: gl.fogEnd = param; break; case GL.FOG_COLOR: gl.fogColor.set(param); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glFog " + GL_Symbol(pname)); } }, glFogi: function(pname, param) { this.glFog(pname, param); }, glFogf: function(pname, param) { this.glFog(pname, param); }, glFogiv: function(pname, params) { // FOG_COLOR integer values are mapped linearly such that the most positive representable value maps to 1.0, // and the most negative representable value maps to -1.0 this.glFog(pname, pname === GL.FOG_COLOR ? params.map(function(x) { return (x + 0.5) / (0x7FFFFFFF + 0.5); }) : params[0]); }, glFogfv: function(pname, params) { this.glFog(pname, pname === GL.FOG_COLOR ? params : params[0]); }, getShader: function(geometryFlags) { // geometryFlags: HAS_TEXCOORD, HAS_NORMAL, HAS_COLOR, USE_POINT_SIZE var shaderFlags = geometryFlags; if (gl.textureEnabled && gl.texture) shaderFlags |= USE_TEXTURE; if (gl.alphaTest) shaderFlags |= USE_ALPHA_TEST; // UNIMPLEMENTED if (gl.fogEnabled) switch (gl.fogMode) { case GL.EXP: shaderFlags |= EXP_FOG << FOG_SHIFT; break; case GL.EXP2: shaderFlags |= EXP2_FOG << FOG_SHIFT; break; case GL.LINEAR: shaderFlags |= LINEAR_FOG << FOG_SHIFT; break; } var numLights = 0; if (gl.lightingEnabled) { for (var i = 0; i < MAX_LIGHTS; i++) { if (gl.lights[i].enabled) numLights++; } shaderFlags |= numLights << NUM_LIGHTS_SHIFT; } var numClipPlanes = 0; for (var i = 0; i < MAX_CLIP_PLANES; i++) { if (gl.clipPlanes[i].enabled) { numClipPlanes++; } } shaderFlags |= numClipPlanes << NUM_CLIP_PLANES_SHIFT; // create shader program var shader = gl.shaders[shaderFlags]; if (!shader) { var flagString = "[POSITION"; if (shaderFlags & HAS_NORMAL) flagString += ", NORMAL"; if (shaderFlags & HAS_COLOR) flagString += ", COLOR"; if (shaderFlags & HAS_TEXCOORD) flagString += ", TEXCOORD"; flagString += "]"; if (shaderFlags & USE_TEXTURE) flagString += ", TEXTURE"; if (shaderFlags & ANY_LIGHTS) { flagString += ", "+ numLights +" LIGHT"; if (numLights !== 1) flagString += "S"; } if (shaderFlags & ANY_CLIP_PLANES) { flagString += ", "+ numClipPlanes +" CLIP_PLANE"; if (numClipPlanes !== 1) flagString += "S"; } if (shaderFlags & USE_ALPHA_TEST) flagString += ", ALPHA_TEST"; if (shaderFlags & USE_POINT_SIZE) flagString += ", POINT_SIZE"; if (shaderFlags & ANY_FOG) flagString += ", FOG"; shader = gl.shaders[shaderFlags] = { flags: shaderFlags, label: flagString, program: null, locations: null, vsource: null, // for debugging fsource: null, // for debugging }; var implemented = HAS_TEXCOORD | HAS_NORMAL | HAS_COLOR | USE_TEXTURE | NUM_LIGHTS_MASK | NUM_CLIP_PLANES_MASK | USE_POINT_SIZE | FOG_MASK; if (shaderFlags & ~implemented) return shader; var program = webgl.createProgram(); var vs = webgl.createShader(webgl.VERTEX_SHADER); shader.vsource = this.vertexShaderSource(shaderFlags); webgl.shaderSource(vs, shader.vsource); webgl.compileShader(vs); if (!webgl.getShaderParameter(vs, webgl.COMPILE_STATUS)) { console.error("OpenGL: vertex shader compile error: " + webgl.getShaderInfoLog(vs)); debugger; return shader; } var fs = webgl.createShader(webgl.FRAGMENT_SHADER); shader.fsource = this.fragmentShaderSource(shaderFlags); webgl.shaderSource(fs, shader.fsource); webgl.compileShader(fs); if (!webgl.getShaderParameter(fs, webgl.COMPILE_STATUS)) { console.error("OpenGL: fragment shader compile error: " + webgl.getShaderInfoLog(fs)); debugger; return shader; } webgl.attachShader(program, vs); webgl.attachShader(program, fs); webgl.linkProgram(program); if (!webgl.getProgramParameter(program, webgl.LINK_STATUS)) { console.error("OpenGL: shader link error: " + webgl.getProgramInfoLog(program)); debugger return shader; } shader.program = program; shader.locations = this.getLocations(program, shaderFlags); } return shader; }, setShaderUniforms: function(shader) { var loc = shader.locations; webgl.uniformMatrix4fv(loc['uModelView'], false, gl.matrices[GL.MODELVIEW][0]); webgl.uniformMatrix4fv(loc['uProjection'], false, gl.matrices[GL.PROJECTION][0]); if (loc['uNormalMatrix']) { var normalMatrix = asNormalMatrix(gl.matrices[GL.MODELVIEW][0]); webgl.uniformMatrix3fv(loc['uNormalMatrix'], false, normalMatrix); } if (loc['uNormal']) { webgl.uniform3fv(loc['uNormal'], gl.normal); } if (loc['uColor']) { var color = gl.color; if (gl.textureEnvMode === GL.REPLACE) color = [1, 1, 1, 1]; // HACK webgl.uniform4fv(loc['uColor'], color); } if (loc['uTextureMatrix']) { webgl.uniformMatrix4fv(loc['uTextureMatrix'], false, gl.matrices[GL.TEXTURE][0]); } if (loc['uTexCoord']) { webgl.uniform2fv(loc['uTexCoord'], gl.texCoord); } if (loc['uSampler']) { webgl.activeTexture(webgl.TEXTURE0); webgl.bindTexture(webgl.TEXTURE_2D, gl.texture); webgl.uniform1i(loc['uSampler'], 0); } if (loc['uPointSize']) { webgl.uniform1f(loc['uPointSize'], gl.pointSize); } var numLights = (shader.flags & NUM_LIGHTS_MASK) >> NUM_LIGHTS_SHIFT; if (numLights > 0) { webgl.uniform4fv(loc['uLightModelAmbient'], gl.lightModelAmbient); webgl.uniform4fv(loc['uMaterialAmbient'], gl.material.ambient); webgl.uniform4fv(loc['uMaterialDiffuse'], gl.material.diffuse); webgl.uniform4fv(loc['uMaterialSpecular'], gl.material.specular); webgl.uniform4fv(loc['uMaterialEmission'], gl.material.emission); webgl.uniform1f(loc['uMaterialShininess'], gl.material.shininess); var index = 0; for (var i = 0; i < MAX_LIGHTS; i++) { var light = gl.lights[i]; if (!light.enabled) continue; webgl.uniform4fv(loc['uLights'][index].ambient, light.ambient); webgl.uniform4fv(loc['uLights'][index].diffuse, light.diffuse); webgl.uniform4fv(loc['uLights'][index].specular, light.specular); webgl.uniform4fv(loc['uLights'][index].position, light.position); index++; } } var numClipPlanes = (shader.flags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { var index = 0; for (var i = 0; i < MAX_CLIP_PLANES; i++) { var clipPlane = gl.clipPlanes[i]; if (!clipPlane.enabled) continue; webgl.uniform4fv(loc['uClipPlanes'][index], clipPlane.equation); index++; } } if (loc['uFogColor']) { webgl.uniform4fv(loc['uFogColor'], gl.fogColor); } if (loc['uFogEnd']) { webgl.uniform1f(loc['uFogEnd'], gl.fogEnd); } if (loc['uFogRange']) { webgl.uniform1f(loc['uFogRange'], gl.fogEnd - gl.fogStart); } if (loc['uFogDensity']) { webgl.uniform1f(loc['uFogDensity'], gl.fogDensity); } }, glEnd: function() { if (gl.listMode && this.addToList("glEnd", [])) return; var primitive = gl.primitive; gl.primitive = null; // select shader var geometryFlags = primitive.vertexAttrs; if (primitive.mode === GL.POINTS) geometryFlags |= USE_POINT_SIZE; var shader = this.getShader(geometryFlags); if (!shader.program) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnd shader: " + shader.label); return; } // create interleaved vertex buffer var vertices = primitive.vertices; var size = primitive.vertexSize; var data = new Float32Array(vertices.length * size); for (var i = 0, offset = 0; i < vertices.length; i++, offset += size) { data.set(vertices[i], offset); } var vertexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ARRAY_BUFFER, vertexBuffer); webgl.bufferData(webgl.ARRAY_BUFFER, data, webgl.DYNAMIC_DRAW); // set mode depending on primitive mode // and create index buffer if needed var mode; var indices; switch (primitive.mode) { // supported by WebGL, no index buffer needed case webgl.POINTS: case webgl.LINES: case webgl.LINE_LOOP: case webgl.LINE_STRIP: case webgl.TRIANGLES: case webgl.TRIANGLE_STRIP: case webgl.TRIANGLE_FAN: mode = primitive.mode; break; // not supported by WebGL, emulate case GL.QUADS: indices = vertices.length > 256 ? new Uint16Array(vertices.length * 3 / 2) : new Uint8Array(vertices.length * 3 / 2); var offset = 0; for (var i = 0; i < vertices.length; i += 4) { indices[offset++] = i; indices[offset++] = i+1; indices[offset++] = i+2; indices[offset++] = i; indices[offset++] = i+2; indices[offset++] = i+3; } mode = webgl.TRIANGLES; break; case GL.QUAD_STRIP: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnd GL_QUAD_STRIP"); return; case GL.POLYGON: mode = webgl.TRIANGLE_FAN; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glEnd " + primitive.mode); return; } var indexBuffer; if (indices) { indexBuffer = webgl.createBuffer(); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, indexBuffer); webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, indices, webgl.DYNAMIC_DRAW); } // set up uniforms and vertex attributes var stride = size * 4; var offset = 0; webgl.useProgram(shader.program); this.setShaderUniforms(shader); var loc = shader.locations; webgl.vertexAttribPointer(loc['aPosition'], 3, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aPosition']); offset += 12; if (loc['aNormal'] >= 0) { webgl.vertexAttribPointer(loc['aNormal'], 3, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aNormal']); } if (geometryFlags & HAS_NORMAL) offset += 12; if (loc['aColor'] >= 0) { webgl.vertexAttribPointer(loc['aColor'], 4, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aColor']); } if (geometryFlags & HAS_COLOR) offset += 16; if (loc['aTexCoord'] >= 0) { webgl.vertexAttribPointer(loc['aTexCoord'], 2, webgl.FLOAT, false, stride, offset); webgl.enableVertexAttribArray(loc['aTexCoord']); } if (geometryFlags & HAS_TEXCOORD) offset += 8; // draw if (indexBuffer) { webgl.drawElements(mode, indices.length, vertices.length > 256 ? webgl.UNSIGNED_SHORT : webgl.UNSIGNED_BYTE, 0); } else { webgl.drawArrays(mode, 0, vertices.length); } webgl.useProgram(null); webgl.disableVertexAttribArray(loc['aPosition']); if (loc['aNormal'] >= 0) webgl.disableVertexAttribArray(loc['aNormal']); if (loc['aColor'] >= 0) webgl.disableVertexAttribArray(loc['aColor']); if (loc['aTexCoord'] >= 0) webgl.disableVertexAttribArray(loc['aTexCoord']); webgl.bindBuffer(webgl.ARRAY_BUFFER, null); webgl.deleteBuffer(vertexBuffer); if (indexBuffer) { webgl.deleteBuffer(indexBuffer); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, null); } }, glEndList: function() { var list = gl.list; gl.list = null; gl.lists[list.id] = list; gl.listMode = 0; }, glFinish: function() { webgl.finish(); }, glFlush: function() { webgl.flush(); }, glFrontFace: function(mode) { if (gl.listMode && this.addToList("glFrontFace", [mode])) return; webgl.frontFace(mode); }, glFrustum: function(left, right, bottom, top, zNear, zFar) { if (gl.listMode && this.addToList("glFrustum", [left, right, bottom, top, zNear, zFar])) return; var m = gl.matrix; m[0] = 2 * zNear / (right - left); m[1] = 0; m[2] = 0; m[3] = 0; m[4] = 0; m[5] = 2 * zNear / (top - bottom); m[6] = 0; m[7] = 0; m[8] = (right + left) / (right - left); m[9] = (top + bottom) / (top - bottom); m[10] = -(zFar + zNear) / (zFar - zNear); m[11] = -1; m[12] = 0; m[13] = 0; m[14] = -(2 * zFar * zNear) / (zFar - zNear); m[15] = 0; }, glGenLists: function(range) { var firstId = 0; if (range > 0) { firstId = gl.listIdGen + 1; gl.listIdGen += range; } return firstId; }, glGenTextures: function(n, textures) { for (var i = 0; i < n; i++) { var id = ++gl.textureIdGen; gl.textures[id] = webgl.createTexture(); textures[i] = id; } }, glGetFloatv: function(pname, params) { switch (pname) { case GL.MODELVIEW_MATRIX: params.set(gl.matrices[GL.MODELVIEW][0]); break; case GL.PROJECTION_MATRIX: params.set(gl.matrices[GL.PROJECTION][0]); break; case GL.TEXTURE_MATRIX: params.set(gl.matrices[GL.TEXTURE][0]); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetFloatv " + GL_Symbol(pname)); } }, glGetIntegerv(name, params) { switch (name) { case GL.LIST_INDEX: params[0] = gl.list ? gl.list.id : 0; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetIntegerv " + GL_Symbol(name)); } }, glGetString: function(name) { switch (name) { case GL.VERSION: return gl.version; case GL.VENDOR: return gl.vendor; case GL.RENDERER: return gl.renderer; case GL.EXTENSIONS: return gl.extensions; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetString " + name); } return ""; }, glGetTexLevelParameteriv: function(target, level, pname, params) { switch (pname) { case GL.TEXTURE_COMPRESSED: return false; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glGetTexLevelParameteriv " + GL_Symbol(target) + " " + GL_Symbol(pname)); } }, glHint: function(target, mode) { if (gl.listMode && this.addToList("glHint", [target, mode])) return; switch (target) { case webgl.GENERATE_MIPMAP_HINT: webgl.hint(target, mode); break; } }, glIsEnabled: function(cap) { switch (cap) { case GL.LIGHTING: return gl.lightingEnabled; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glIsEnabled " + cap); } return false; }, glIsTexture: function(texture) { return !!gl.textures[texture]; }, glLightf: function(light, pname, param) { if (gl.listMode && this.addToList("glLightf", [light, pname, param])) return; light - GL.LIGHT0; switch (pname) { case GL.SPOT_CUTOFF: if (param === 180) { return; } // fall through if not default default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glLightf " + GL_Symbol(pname)); } }, glLightfv: function(light, pname, param) { if (gl.listMode && this.addToList("glLightfv", [light, pname, param])) return; var i = light - GL.LIGHT0; switch (pname) { case GL.AMBIENT: gl.lights[i].ambient = param; break; case GL.DIFFUSE: gl.lights[i].diffuse = param; break; case GL.SPECULAR: gl.lights[i].specular = param; break; case GL.POSITION: if (param[3] === 0) { transformDirection(gl.matrices[GL.MODELVIEW][0], param, gl.lights[i].position); } else { transformPoint(gl.matrices[GL.MODELVIEW][0], param, gl.lights[i].position); } break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glLightfv " + GL_Symbol(pname)); } }, glLightModelfv: function(pname, params) { if (gl.listMode && this.addToList("glLightModelfv", [pname, params])) return; switch (pname) { case GL.LIGHT_MODEL_AMBIENT: gl.lightModelAmbient = params; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glLightModelfv " + GL_Symbol(pname)); } }, glLineWidth: function(width) { if (gl.listMode && this.addToList("glLineWidth", [width])) return; webgl.lineWidth(width); }, glListBase: function(base) { gl.listBase = base; }, glLoadIdentity: function() { if (gl.listMode && this.addToList("glLoadIdentity", [])) return; gl.matrix.set(identity); }, glLoadMatrixf: function(m) { if (gl.listMode && this.addToList("glLoadMatrixf", [m])) return; gl.matrix.set(m); }, glLoadTransposeMatrixf: function(m) { if (gl.listMode && this.addToList("glLoadTransposeMatrixf", [m])) return; var t = new Float32Array(m); transposeMatrix(t); gl.matrix.set(t); }, glMaterialf: function(face, pname, param) { if (gl.listMode && this.addToList("glMaterialf", [face, pname, param])) return; switch (pname) { case GL.SHININESS: gl.material.shininess = param; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glMaterialf " + GL_Symbol(pname)); } }, glMaterialfv: function(face, pname, param) { if (gl.listMode && this.addToList("glMaterialfv", [face, pname, param])) return; switch (pname) { case GL.AMBIENT: gl.material.ambient = param; break; case GL.DIFFUSE: gl.material.diffuse = param; break; case GL.SPECULAR: gl.material.specular = param; break; case GL.EMISSION: gl.material.emission = param; break; case GL.SHININESS: gl.material.shininess = param[0]; break; case GL.AMBIENT_AND_DIFFUSE: gl.material.ambient = param; gl.material.diffuse = param; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glMaterialfv " + GL_Symbol(pname)); } }, glMatrixMode: function(mode) { if (gl.listMode && this.addToList("glMatrixMode", [mode])) return; if (mode !== GL.MODELVIEW && mode !== GL.PROJECTION && mode !== GL.TEXTURE) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glMatrixMode " + GL_Symbol(mode)); } gl.matrixMode = mode; if (!gl.matrices[mode]) gl.matrices[mode] = [new Float32Array(identity)]; gl.matrix = gl.matrices[mode][0]; }, glMultMatrixf: function(m) { if (gl.listMode && this.addToList("glMultMatrixf", [m])) return; multMatrix(gl.matrix, m); }, glMultTransposeMatrixf: function(m) { if (gl.listMode && this.addToList("glMultTransposeMatrixf", [m])) return; var t = new Float32Array(m); transposeMatrix(t); multMatrix(gl.matrix, t); }, glNewList: function(list, mode) { var newList = { id: list, commands: [], }; gl.list = newList; gl.listMode = mode; }, glNormal3f: function(nx, ny, nz) { if (gl.listMode && this.addToList("glNormal3f", [nx, ny, nz])) return; gl.normal[0] = nx; gl.normal[1] = ny; gl.normal[2] = nz; gl.primitiveAttrs |= HAS_NORMAL; }, glNormal3fv: function(v) { if (gl.listMode && this.addToList("glNormal3fv", [v.slice()])) return; gl.normal.set(v); gl.primitiveAttrs |= HAS_NORMAL; }, glNormalPointer: function(type, stride, pointer) { if (gl.listMode && this.addToList("glNormalPointer", [type, stride, pointer])) return; gl.clientState.normalArray.size = 3; gl.clientState.normalArray.type = type; gl.clientState.normalArray.stride = stride; gl.clientState.normalArray.pointer = pointer; }, glPixelStorei: function(pname, param) { switch (pname) { case webgl.UNPACK_ALIGNMENT: //@webgl.pixelStorei(webgl.UNPACK_ALIGNMENT, param); break; case GL.UNPACK_LSB_FIRST: if (param !== 0) console.log("UNIMPLEMENTED glPixelStorei GL_UNPACK_LSB_FIRST", param); break; case GL.UNPACK_ROW_LENGTH: gl.pixelStoreUnpackRowLength = param; break; case GL.UNPACK_SKIP_ROWS: gl.pixelStoreUnpackSkipRows = param; break; case GL.UNPACK_SKIP_PIXELS: gl.pixelStoreUnpackSkipPixels = param; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glPixelStorei " + pname); } }, glPolygonOffset: function(factor, units) { if (gl.listMode && this.addToList("glPolygonOffset", [factor, units])) return; webgl.polygonOffset(factor, units); }, glPushAttrib: function(mask) { if (gl.listMode && this.addToList("glPushAttrib", [mask])) return; this.vm.warnOnce("OpenGL: UNIMPLEMENTED glPushAttrib"); }, glPushMatrix: function() { if (gl.listMode && this.addToList("glPushMatrix", [])) return; gl.matrix = new Float32Array(gl.matrix); gl.matrices[gl.matrixMode].unshift(gl.matrix); }, glPointSize: function(size) { if (gl.listMode && this.addToList("glPointSize", [size])) return; gl.pointSize = size; }, glPopAttrib: function() { if (gl.listMode && this.addToList("glPopAttrib", [])) return; this.vm.warnOnce("OpenGL: UNIMPLEMENTED glPopAttrib"); }, glPopMatrix: function() { if (gl.listMode && this.addToList("glPopMatrix", [])) return; if (gl.matrices[gl.matrixMode].length <= 1) return DEBUG > 0 && console.warn("OpenGL: matrix stack underflow"); gl.matrices[gl.matrixMode].shift(); gl.matrix = gl.matrices[gl.matrixMode][0]; }, glRasterPos3f: function(x, y, z) { if (gl.listMode && this.addToList("glRasterPos3f", [x, y, z])) return; gl.rasterPos[0] = x; gl.rasterPos[1] = y; gl.rasterPos[2] = z; gl.rasterPos[3] = 1; // transform point via modelview and projection matrices var m = gl.matrices[GL.PROJECTION][0]; transformPoint(m, gl.rasterPos, gl.rasterPos); m = gl.matrices[GL.MODELVIEW][0]; transformPoint(m, gl.rasterPos, gl.rasterPos); // transform to window coordinates gl.rasterPos[0] = (gl.rasterPos[0] * 0.5 + 0.5) * gl.viewport[2] + gl.viewport[0]; gl.rasterPos[1] = (gl.rasterPos[1] * 0.5 + 0.5) * gl.viewport[3] + gl.viewport[1]; gl.rasterPos[2] = (gl.rasterPos[2] * 0.5 + 0.5) * (gl.depthRange[1] - gl.depthRange[0]) + gl.depthRange[0]; // remember raster color gl.rasterColor.set(gl.color); }, glReadPixels: function(x, y, width, height, format, type, pixels) { if (gl.listMode && this.addToList("glReadPixels", [x, y, width, height, format, type, pixels])) return; var swizzle = false; switch (format) { case webgl.RGBA: break; case GL.BGRA: format = webgl.RGBA; swizzle = true; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glReadPixels format " + GL_Symbol(format)); return; } switch (type) { case webgl.UNSIGNED_BYTE: pixels = new Uint8Array(pixels); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glReadPixels type " + GL_Symbol(type)); return; } webgl.readPixels(x, y, width, height, format, type, pixels); if (swizzle) { for (var i = 0; i < pixels.length; i += 4) { var r = pixels[i]; var b = pixels[i+2]; pixels[i] = b; pixels[i+2] = r; } } }, glTranslated: function(x, y, z) { if (gl.listMode && this.addToList("glTranslated", [x, y, z])) return; translateMatrix(gl.matrix, x, y, z); }, glTranslatef: function(x, y, z) { if (gl.listMode && this.addToList("glTranslatef", [x, y, z])) return; translateMatrix(gl.matrix, x, y, z); }, glRotatef: function(angle, x, y, z) { if (gl.listMode && this.addToList("glRotatef", [angle, x, y, z])) return; rotateMatrix(gl.matrix, angle, x, y, z); }, glScalef: function(x, y, z) { if (gl.listMode && this.addToList("glScalef", [x, y, z])) return; scaleMatrix(gl.matrix, x, y, z); }, glScaled: function(x, y, z) { if (gl.listMode && this.addToList("glScaled", [x, y, z])) return; scaleMatrix(gl.matrix, x, y, z); }, glShadeModel: function(mode) { if (gl.listMode && this.addToList("glShadeModel", [mode])) return; this.vm.warnOnce("OpenGL: UNIMPLEMENTED glShadeModel " + GL_Symbol(mode)); }, glStencilFunc: function(func, ref, mask) { if (gl.listMode && this.addToList("glStencilFunc", [func, ref, mask])) return; webgl.stencilFunc(func, ref, mask); }, glStencilOp: function(fail, zfail, zpass) { if (gl.listMode && this.addToList("glStencilOp", [fail, zfail, zpass])) return; webgl.stencilOp(fail, zfail, zpass); }, glTexEnv: function(target, pname, param) { if (gl.listMode && this.addToList("glTexEnv", [target, pname, param])) return; switch (pname) { case GL.TEXTURE_ENV_MODE: switch (param) { case GL.MODULATE: gl.textureEnvMode = param; return; case GL.REPLACE: gl.textureEnvMode = param; return; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexEnv " + GL_Symbol(target) + " GL_TEXTURE_ENV_MODE " + GL_Symbol(param)); } break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexEnv " + GL_Symbol(target) + " " + GL_Symbol(pname)); } }, glTexEnvi: function(target, pname, param) { this.glTexEnv(target, pname, param); }, glTexEnvf: function(target, pname, param) { this.glTexEnv(target, pname, param); }, glTexGen: function(coord, pname, param) { if (gl.listMode && this.addToList("glTexGen", [coord, pname, param])) return; this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexGen " + GL_Symbol(pname)); }, glTexGeni: function(coord, pname, param) { this.glTexGen(coord, pname, param); }, glTexGenf: function(coord, pname, param) { this.glTexGen(coord, pname, param); }, glTexGend: function(coord, pname, params) { this.glTexGen(coord, pname, param); }, glTexGenfv: function(coord, pname, params) { if (gl.listMode && this.addToList("glTexGenfv", [coord, pname, params])) return; this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexGenfv " + GL_Symbol(pname)); }, glTexImage2D: function(target, level, internalformat, width, height, border, format, type, pixels) { if (gl.listMode && this.addToList("glTexImage2D", [target, level, internalformat, width, height, border, format, type, pixels])) return; gl.texture.width = width; gl.texture.height = height; // WebGL does not support GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS if (gl.pixelStoreUnpackRowLength !== 0 && gl.pixelStoreUnpackRowLength !== gl.texture.width) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D GL_UNPACK_ROW_LENGTH"); } if (gl.pixelStoreUnpackSkipRows !== 0) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D GL_UNPACK_SKIP_ROWS"); } if (gl.pixelStoreUnpackSkipPixels !== 0) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D GL_UNPACK_SKIP_PIXELS"); } // WebGL only supports GL_RGBA switch (format) { case webgl.RGBA: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D format GL_RGBA"); break; case GL.BGRA: format = webgl.RGBA; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D format " + GL_Symbol(format)); return; } // pixels are coming in via FFI as void* (ArrayBuffer) // convert to appropriate typed array // in OpenGL, pixels can be null to allocate texture storage if (!pixels) pixels = new ArrayBuffer(width * height * 4); switch (type) { case webgl.UNSIGNED_BYTE: pixels = new Uint8Array(pixels); gl.texture.pixels = pixels; // for debugging break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexImage2D type " + GL_Symbol(type)); return; } webgl.texImage2D(target, level, internalformat, width, height, border, format, type, pixels); if (gl.texture.generateMipmapSGIS) webgl.generateMipmap(target); }, debugTexture: function(texture) { if (texture === false) { var canvas = document.getElementById("texDebug"); if (canvas) canvas.remove(); return; } if (!texture) texture = gl.texture; var pixels = texture.pixels; var width = texture.width; var height = texture.height; var data = new Uint8Array(pixels); var canvas = document.getElementById("texDebug"); if (!canvas) { canvas = document.createElement('canvas'); canvas.id = "texDebug"; canvas.style.position = "absolute"; canvas.style.zIndex = 1000; document.body.appendChild(canvas); } canvas.width = width; canvas.height = height; var ctx = canvas.getContext('2d'); var imageData = ctx.createImageData(width, height); imageData.data.set(data); ctx.putImageData(imageData, 0, 0); }, glTexCoord2d: function(s, t) { if (gl.listMode && this.addToList("glTexCoord2d", [s, t])) return; gl.texCoord[0] = s; gl.texCoord[1] = t; gl.primitiveAttrs |= HAS_TEXCOORD; }, glTexCoord2f: function(s, t) { if (gl.listMode && this.addToList("glTexCoord2f", [s, t])) return; gl.texCoord[0] = s; gl.texCoord[1] = t; gl.primitiveAttrs |= HAS_TEXCOORD; }, glTexCoord2fv: function(v) { if (gl.listMode && this.addToList("glTexCoord2fv", [v.slice()])) return; gl.texCoord.set(v); gl.primitiveAttrs |= HAS_TEXCOORD; }, glTexCoordPointer: function(size, type, stride, pointer) { if (gl.listMode && this.addToList("glTexCoordPointer", [size, type, stride, pointer])) return; gl.clientState.textureCoordArray.size = size; gl.clientState.textureCoordArray.type = type; gl.clientState.textureCoordArray.stride = stride; gl.clientState.textureCoordArray.pointer = pointer; }, glTexParameteri: function(target, pname, param) { if (gl.listMode && this.addToList("glTexParameteri", [target, pname, param])) return; if (pname === GL.GENERATE_MIPMAP_SGIS) { // WebGL does not support GL_GENERATE_MIPMAP_SGIS. Emulate it gl.texture.generateMipmapSGIS = param; // if an image has been uploaded already, generate mipmaps now if (param && gl.texture.width > 1) webgl.generateMipmap(target); return; } webgl.texParameteri(target, pname, param); }, glTexSubImage2D: function(target, level, xoffset, yoffset, width, height, format, type, pixels) { if (gl.listMode && this.addToList("glTexSubImage2D", [target, level, xoffset, yoffset, width, height, format, type, pixels])) return; // WebGL does not support GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_PIXELS // emulate GL_UNPACK_SKIP_ROWS var pixelsOffset = gl.pixelStoreUnpackSkipRows * gl.texture.width; // to be multiplied by pixel size below // assume GL_UNPACK_ROW_LENGTH is full width (which is the case when uploading part of a bitmap in Squeak) if (gl.pixelStoreUnpackRowLength !== 0 && gl.pixelStoreUnpackRowLength !== gl.texture.width) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexSubImage2D GL_UNPACK_ROW_LENGTH"); } // WebGL does not support GL_UNPACK_SKIP_PIXELS to allow different width if (width !== gl.texture.width) { // we could either // 1. call texSubImage2D for each row // 2. copy subimage pixels into a new buffer // 3. call texSubImage2D for the whole width so we don't need to skip pixels // we choose 3. for now width = gl.texture.width; xoffset = 0; } // WebGL only supports RGB not BGR switch (format) { case webgl.RGBA: pixelsOffset *= 4; break; case GL.BGRA: pixelsOffset *= 4; format = webgl.RGBA; break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexSubImage2D format " + GL_Symbol(format)); return; } // pixels are coming in via FFI as void* (ArrayBuffer) // convert to appropriate typed array switch (type) { case webgl.UNSIGNED_BYTE: pixels = new Uint8Array(pixels, pixelsOffset); break; default: this.vm.warnOnce("OpenGL: UNIMPLEMENTED glTexSubImage2D type " + GL_Symbol(type)); return; } webgl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels); if (gl.texture.generateMipmapSGIS) webgl.generateMipmap(target); }, glVertex2f: function(x, y) { if (gl.listMode && this.addToList("glVertex2f", [x, y])) return; var position = [x, y]; this.pushVertex(position); }, glVertex3f: function(x, y, z) { if (gl.listMode && this.addToList("glVertex3f", [x, y, z])) return; var position = [x, y, z]; this.pushVertex(position); }, glVertex3fv: function(v) { if (gl.listMode && this.addToList("glVertex3fv", [v.slice()])) return; this.pushVertex(v); }, glVertex2i: function(x, y) { if (gl.listMode && this.addToList("glVertex2i", [x, y])) return; var position = [x, y]; this.pushVertex(position); }, glVertexPointer: function(size, type, stride, pointer) { if (gl.listMode && this.addToList("glVertexPointer", [size, type, stride, pointer])) return; gl.clientState.vertexArray.size = size; gl.clientState.vertexArray.type = type; gl.clientState.vertexArray.stride = stride; gl.clientState.vertexArray.pointer = pointer; }, glViewport: function(x, y, width, height) { if (gl.listMode && this.addToList("glViewport", [x, y, width, height])) return; webgl.viewport(x, y, width, height); gl.viewport[0] = x; gl.viewport[1] = y; gl.viewport[2] = width; gl.viewport[3] = height; }, glXGetProcAddressARB: function(procName) { procName = Squeak.bytesAsString(procName); var handle = this.ffi.ffiLookupFunc(this, procName); if (!handle) { this.vm.warnOnce("OpenGL: UNIMPLEMENTED EXT FUNC" + procName); } return handle; }, pushVertex: function(position) { var primitive = gl.primitive; if (!primitive) throw Error("OpenGL: glBegin not called"); if (!primitive.vertexSize) { var vertexSize = 3; if (gl.primitiveAttrs & HAS_NORMAL) vertexSize += 3; if (gl.primitiveAttrs & HAS_COLOR) vertexSize += 4; if (gl.primitiveAttrs & HAS_TEXCOORD) vertexSize += 2; primitive.vertexSize = vertexSize; primitive.vertexAttrs = gl.primitiveAttrs; } var vertex = new Float32Array(primitive.vertexSize); var offset = 0; vertex.set(position, offset); offset += 3; if (primitive.vertexAttrs & HAS_NORMAL) { vertex.set(gl.normal, offset); offset += 3; } if (primitive.vertexAttrs & HAS_COLOR) { vertex.set(gl.color, offset); offset += 4; } if (primitive.vertexAttrs & HAS_TEXCOORD) { vertex.set(gl.texCoord, offset); offset += 2; } primitive.vertices.push(vertex); }, // Shader source code // Note: we could take a look at Emscripten's glemu to add some more features // https://github.com/emscripten-core/emscripten/blob/cb99414efed02dc61d04315d3e3cf5ad3180e56f/src/library_glemu.js#L2170 // The structure is a bit different but applicable vertexShaderSource: function(shaderFlags) { var src = []; src.push("uniform mat4 uModelView;"); src.push("uniform mat4 uProjection;"); src.push("attribute vec3 aPosition;"); if (shaderFlags & HAS_COLOR) { src.push("attribute vec4 aColor;"); } else if (shaderFlags & ANY_LIGHTS) { src.push("uniform vec4 uColor;"); } if (shaderFlags & (HAS_COLOR | ANY_LIGHTS)) { src.push("varying vec4 vColor;"); } if (shaderFlags & HAS_TEXCOORD && shaderFlags & USE_TEXTURE) { src.push("uniform mat4 uTextureMatrix;"); src.push("attribute vec2 aTexCoord;"); src.push("varying vec2 vTexCoord;"); } if (shaderFlags & USE_POINT_SIZE) { src.push("uniform float uPointSize;"); } var numLights = (shaderFlags & NUM_LIGHTS_MASK) >> NUM_LIGHTS_SHIFT; if (numLights > 0) { if (shaderFlags & HAS_NORMAL) { src.push("attribute vec3 aNormal;"); } else { src.push("uniform vec3 uNormal;"); } src.push("uniform mat3 uNormalMatrix;"); src.push("uniform vec4 uLightModelAmbient;"); src.push("uniform vec4 uMaterialAmbient;"); src.push("uniform vec4 uMaterialDiffuse;"); src.push("uniform vec4 uMaterialSpecular;"); src.push("uniform vec4 uMaterialEmission;"); src.push("uniform float uMaterialShininess;"); src.push("struct Light {"); src.push(" vec4 position;"); src.push(" vec4 ambient;"); src.push(" vec4 diffuse;"); src.push(" vec4 specular;"); src.push("};"); src.push("uniform Light uLights[" + numLights + "];"); } var numClipPlanes = (shaderFlags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { src.push("uniform vec4 uClipPlanes[" + numClipPlanes + "];"); src.push("varying float vClipDist[" + numClipPlanes + "];"); } var fog = (shaderFlags & FOG_MASK) >> FOG_SHIFT; if (fog !== NO_FOG) { if (fog === LINEAR_FOG) { src.push("uniform float uFogEnd;"); src.push("uniform float uFogRange;"); } else { src.push("uniform float uFogDensity;"); } src.push("varying float vFogDist;"); } src.push("void main(void) {"); if (shaderFlags & HAS_COLOR) { src.push(" vColor = aColor;"); } else if (shaderFlags & ANY_LIGHTS) { src.push(" vColor = uColor;"); } src.push(" vec4 position = uModelView * vec4(aPosition, 1.0);"); if (numLights > 0) { if (shaderFlags & HAS_NORMAL) { src.push(" vec3 normal = normalize(uNormalMatrix * aNormal);"); } else { src.push(" vec3 normal = normalize(uNormalMatrix * uNormal);"); } src.push(" vec4 lighting = uMaterialEmission;"); src.push(" lighting += uMaterialAmbient * uLightModelAmbient;"); src.push(" vec3 eyeDir = normalize(-position.xyz);"); src.push(" for (int i = 0; i < " + numLights + "; i++) {"); src.push(" Light light = uLights[i];"); src.push(" vec3 lightDir;"); src.push(" if (light.position.w == 0.0) {"); src.push(" lightDir = normalize(light.position.xyz);"); src.push(" } else {"); src.push(" lightDir = normalize(light.position.xyz - position.xyz);"); src.push(" }"); src.push(" float nDotL = max(dot(normal, lightDir), 0.0);"); src.push(" vec4 ambient = uMaterialAmbient * light.ambient;"); src.push(" vec4 diffuse = uMaterialDiffuse * light.diffuse * nDotL;"); src.push(" vec4 specular = vec4(0.0);"); src.push(" if (nDotL > 0.0) {"); src.push(" vec3 halfVector = normalize(lightDir + eyeDir);"); src.push(" float nDotHV = max(dot(normal, halfVector), 0.0);"); src.push(" specular = uMaterialSpecular * light.specular * pow(nDotHV, uMaterialShininess);"); src.push(" }"); src.push(" lighting += ambient + diffuse + specular;"); src.push(" }"); src.push(" vColor = clamp(lighting, 0.0, 1.0);"); src.push(" vColor.a = uMaterialDiffuse.a;"); } if (numClipPlanes > 0) { src.push(" for (int i = 0; i < " + numClipPlanes + "; i++) {"); src.push(" vClipDist[i] = dot(position, uClipPlanes[i]);"); src.push(" }"); } if (shaderFlags & HAS_TEXCOORD && shaderFlags & USE_TEXTURE) { src.push(" vec4 texCoord = uTextureMatrix * vec4(aTexCoord, 0.0, 1.0);"); src.push(" vTexCoord = texCoord.xy / texCoord.w;"); } if (fog !== NO_FOG) { src.push(" vFogDist = -position.z;"); } if (shaderFlags & USE_POINT_SIZE) { src.push(" gl_PointSize = uPointSize;"); } src.push(" gl_Position = uProjection * position;"); src.push("}"); var src = src.join("\n"); return src; }, fragmentShaderSource: function(shaderFlags) { var src = []; src.push("precision mediump float;"); var numClipPlanes = (shaderFlags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { src.push("varying float vClipDist[" + numClipPlanes + "];"); } if (shaderFlags & (HAS_COLOR | ANY_LIGHTS)) { src.push("varying vec4 vColor;"); } else { src.push("uniform vec4 uColor;"); } if (shaderFlags & USE_TEXTURE) { if (shaderFlags & HAS_TEXCOORD) { src.push("varying vec2 vTexCoord;"); } else { src.push("uniform vec2 uTexCoord;"); } src.push("uniform sampler2D uSampler;"); } var fog = (shaderFlags & FOG_MASK) >> FOG_SHIFT; if (fog !== NO_FOG) { if (fog === LINEAR_FOG) { src.push("uniform float uFogEnd;"); src.push("uniform float uFogRange;"); } else { src.push("uniform float uFogDensity;"); } src.push("uniform vec4 uFogColor;"); src.push("varying float vFogDist;"); } src.push("void main(void) {"); if (numClipPlanes > 0) { src.push(" bool clipped = false;"); src.push(" for (int i = 0; i < " + numClipPlanes + "; i++) {"); src.push(" if (vClipDist[i] < 0.0) clipped = true;"); src.push(" }"); src.push(" if (clipped) discard;"); } if (shaderFlags & (HAS_COLOR | ANY_LIGHTS)) { src.push(" vec4 color = vColor;"); } else { src.push(" vec4 color = uColor;"); } if (shaderFlags & USE_TEXTURE) { if (shaderFlags & HAS_TEXCOORD) { src.push(" vec2 texCoord = vTexCoord;"); } else { src.push(" vec2 texCoord = uTexCoord;"); } src.push(" color *= texture2D(uSampler, texCoord).bgra;"); } if (fog !== NO_FOG) { switch (fog) { case LINEAR_FOG: src.push(" float fogAmount = (uFogEnd - vFogDist) / uFogRange;"); break; case EXP_FOG: src.push(" float fogAmount = exp(-uFogDensity * vFogDist);"); break; case EXP2_FOG: src.push(" float fogAmount = exp(-uFogDensity * uFogDensity * vFogDist * vFogDist);"); break; } src.push(" color.rgb = mix(uFogColor.rgb, color.rgb, clamp(fogAmount, 0.0, 1.0));"); } src.push(" gl_FragColor = color;"); src.push("}"); var src = src.join("\n"); return src; }, getLocations: function(program, shaderFlags) { var locations = {}; locations.uModelView = webgl.getUniformLocation(program, "uModelView"); locations.uProjection = webgl.getUniformLocation(program, "uProjection"); locations.aPosition = webgl.getAttribLocation(program, "aPosition"); if (shaderFlags & USE_TEXTURE) { if (shaderFlags & HAS_TEXCOORD) { locations.uTextureMatrix = webgl.getUniformLocation(program, "uTextureMatrix"); locations.aTexCoord = webgl.getAttribLocation(program, "aTexCoord"); } else { locations.uTexCoord = webgl.getUniformLocation(program, "uTexCoord"); } locations.uSampler = webgl.getUniformLocation(program, "uSampler"); } if (shaderFlags & HAS_COLOR) { locations.aColor = webgl.getAttribLocation(program, "aColor"); } else { locations.uColor = webgl.getUniformLocation(program, "uColor"); } if (shaderFlags & USE_POINT_SIZE) { locations.uPointSize = webgl.getUniformLocation(program, "uPointSize"); } var numLights = (shaderFlags & NUM_LIGHTS_MASK) >> NUM_LIGHTS_SHIFT; if (numLights > 0) { if (shaderFlags & HAS_NORMAL) { locations.aNormal = webgl.getAttribLocation(program, "aNormal"); } else { locations.uNormal = webgl.getUniformLocation(program, "uNormal"); } locations.uNormalMatrix = webgl.getUniformLocation(program, "uNormalMatrix"); locations.uLightModelAmbient = webgl.getUniformLocation(program, "uLightModelAmbient"); locations.uMaterialAmbient = webgl.getUniformLocation(program, "uMaterialAmbient"); locations.uMaterialDiffuse = webgl.getUniformLocation(program, "uMaterialDiffuse"); locations.uMaterialSpecular = webgl.getUniformLocation(program, "uMaterialSpecular"); locations.uMaterialEmission = webgl.getUniformLocation(program, "uMaterialEmission"); locations.uMaterialShininess = webgl.getUniformLocation(program, "uMaterialShininess"); locations.uLights = []; for (var i = 0; i < numLights; i++) { var light = {}; light.position = webgl.getUniformLocation(program, "uLights[" + i + "].position"); light.ambient = webgl.getUniformLocation(program, "uLights[" + i + "].ambient"); light.diffuse = webgl.getUniformLocation(program, "uLights[" + i + "].diffuse"); light.specular = webgl.getUniformLocation(program, "uLights[" + i + "].specular"); locations.uLights.push(light); } } var numClipPlanes = (shaderFlags & NUM_CLIP_PLANES_MASK) >> NUM_CLIP_PLANES_SHIFT; if (numClipPlanes > 0) { locations.uClipPlanes = []; for (var i = 0; i < numClipPlanes; i++) { locations.uClipPlanes.push(webgl.getUniformLocation(program, "uClipPlanes[" + i + "]")); } } var fog = (shaderFlags & FOG_MASK) >> FOG_SHIFT; if (fog !== NO_FOG) { if (fog === LINEAR_FOG) { locations.uFogEnd = webgl.getUniformLocation(program, "uFogEnd"); locations.uFogRange = webgl.getUniformLocation(program, "uFogRange"); } else { locations.uFogDensity = webgl.getUniformLocation(program, "uFogDensity"); } locations.uFogColor = webgl.getUniformLocation(program, "uFogColor"); } return locations; }, executeList: function(listId) { var list = gl.lists[listId]; if (!list) { return; } for (var i = 0; i < list.commands.length; i++) { var cmd = list.commands[i]; this[cmd.name].apply(this, cmd.args); } }, }; } function transformDirection(matrix, src, dst) { var x = src[0]; var y = src[1]; var z = src[2]; var rx = matrix[0] * x + matrix[4] * y + matrix[8] * z; var ry = matrix[1] * x + matrix[5] * y + matrix[9] * z; var rz = matrix[2] * x + matrix[6] * y + matrix[10] * z; dst[0] = rx; dst[1] = ry; dst[2] = rz; dst[3] = src[3]; } function transformPoint(matrix, src, dst) { var x = src[0]; var y = src[1]; var z = src[2]; var rx = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]; var ry = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]; var rz = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]; var rw = matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15]; if (rw === 1) { dst[0] = rx; dst[1] = ry; dst[2] = rz; } else { if (rw !== 0) rw = 1 / rw; dst[0] = rx * rw; dst[1] = ry * rw; dst[2] = rz * rw; } dst[3] = src[3]; } function multVec4(matrix, src, dst) { var x = src[0]; var y = src[1]; var z = src[2]; var w = src[3]; dst[0] = matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12] * w; dst[1] = matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13] * w; dst[2] = matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14] * w; dst[3] = matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15] * w; } function multMatrix(m1, m2) { var m00 = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3]; var m01 = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3]; var m02 = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3]; var m03 = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3]; var m10 = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7]; var m11 = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7]; var m12 = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7]; var m13 = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7]; var m20 = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11]; var m21 = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11]; var m22 = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11]; var m23 = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11]; var m30 = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15]; var m31 = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15]; var m32 = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15]; var m33 = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15]; m1[0] = m00; m1[1] = m01; m1[2] = m02; m1[3] = m03; m1[4] = m10; m1[5] = m11; m1[6] = m12; m1[7] = m13; m1[8] = m20; m1[9] = m21; m1[10] = m22; m1[11] = m23; m1[12] = m30; m1[13] = m31; m1[14] = m32; m1[15] = m33; } function translateMatrix(m, x, y, z) { m[12] += x * m[0] + y * m[4] + z * m[8]; m[13] += x * m[1] + y * m[5] + z * m[9]; m[14] += x * m[2] + y * m[6] + z * m[10]; m[15] += x * m[3] + y * m[7] + z * m[11]; } function rotateMatrix(m, angle, x, y, z) { // normalize axis vector var len = Math.sqrt(x * x + y * y + z * z); if (len === 0) return; if (len !== 1) { len = 1 / len; x *= len; y *= len; z *= len; } // create rotation matrix var degrees = Math.PI / 180; var c = Math.cos(angle * degrees); var s = Math.sin(angle * degrees); var t = 1 - c; var m00 = x * x * t + c; var m01 = x * y * t - z * s; var m02 = x * z * t + y * s; var m10 = y * x * t + z * s; var m11 = y * y * t + c; var m12 = y * z * t - x * s; var m20 = z * x * t - y * s; var m21 = z * y * t + x * s; var m22 = z * z * t + c; // multiply rotation matrix var m0 = m[0] * m00 + m[4] * m01 + m[8] * m02; var m1 = m[1] * m00 + m[5] * m01 + m[9] * m02; var m2 = m[2] * m00 + m[6] * m01 + m[10] * m02; var m3 = m[3] * m00 + m[7] * m01 + m[11] * m02; var m4 = m[0] * m10 + m[4] * m11 + m[8] * m12; var m5 = m[1] * m10 + m[5] * m11 + m[9] * m12; var m6 = m[2] * m10 + m[6] * m11 + m[10] * m12; var m7 = m[3] * m10 + m[7] * m11 + m[11] * m12; m[8] = m[0] * m20 + m[4] * m21 + m[8] * m22; m[9] = m[1] * m20 + m[5] * m21 + m[9] * m22; m[10] = m[2] * m20 + m[6] * m21 + m[10] * m22; m[11] = m[3] * m20 + m[7] * m21 + m[11] * m22; m[0] = m0; m[1] = m1; m[2] = m2; m[3] = m3; m[4] = m4; m[5] = m5; m[6] = m6; m[7] = m7; } function scaleMatrix(m, x, y, z) { m[0] *= x; m[1] *= x; m[2] *= x; m[3] *= x; m[4] *= y; m[5] *= y; m[6] *= y; m[7] *= y; m[8] *= z; m[9] *= z; m[10] *= z; m[11] *= z; } function transposeMatrix(m) { var m01 = m[1], m02 = m[2], m03 = m[3], m12 = m[6], m13 = m[7], m23 = m[11]; m[1] = m[4]; m[2] = m[8]; m[3] = m[12]; m[4] = m01; m[6] = m[9]; m[7] = m[13]; m[8] = m02; m[9] = m12; m[11] = m[14]; m[12] = m03; m[13] = m13; m[14] = m23; } function invertMatrix(src, dst) { if (!dst) dst = src; var m00 = src[0], m01 = src[1], m02 = src[2], m03 = src[3], m10 = src[4], m11 = src[5], m12 = src[6], m13 = src[7], m20 = src[8], m21 = src[9], m22 = src[10], m23 = src[11], m30 = src[12], m31 = src[13], m32 = src[14], m33 = src[15]; var t00 = m00 * m11 - m01 * m10, t01 = m00 * m12 - m02 * m10, t02 = m00 * m13 - m03 * m10, t03 = m01 * m12 - m02 * m11, t04 = m01 * m13 - m03 * m11, t05 = m02 * m13 - m03 * m12, t06 = m20 * m31 - m21 * m30, t07 = m20 * m32 - m22 * m30, t08 = m20 * m33 - m23 * m30, t09 = m21 * m32 - m22 * m31, t10 = m21 * m33 - m23 * m31, t11 = m22 * m33 - m23 * m32; var det = t00 * t11 - t01 * t10 + t02 * t09 + t03 * t08 - t04 * t07 + t05 * t06; if (det === 0) return; var invDet = 1 / det; dst[0] = (m11 * t11 - m12 * t10 + m13 * t09) * invDet; dst[1] = (-m01 * t11 + m02 * t10 - m03 * t09) * invDet; dst[2] = (m31 * t05 - m32 * t04 + m33 * t03) * invDet; dst[3] = (-m21 * t05 + m22 * t04 - m23 * t03) * invDet; dst[4] = (-m10 * t11 + m12 * t08 - m13 * t07) * invDet; dst[5] = (m00 * t11 - m02 * t08 + m03 * t07) * invDet; dst[6] = (-m30 * t05 + m32 * t02 - m33 * t01) * invDet; dst[7] = (m20 * t05 - m22 * t02 + m23 * t01) * invDet; dst[8] = (m10 * t10 - m11 * t08 + m13 * t06) * invDet; dst[9] = (-m00 * t10 + m01 * t08 - m03 * t06) * invDet; dst[10] = (m30 * t04 - m31 * t02 + m33 * t00) * invDet; dst[11] = (-m20 * t04 + m21 * t02 - m23 * t00) * invDet; dst[12] = (-m10 * t09 + m11 * t07 - m12 * t06) * invDet; dst[13] = (m00 * t09 - m01 * t07 + m02 * t06) * invDet; dst[14] = (-m30 * t03 + m31 * t01 - m32 * t00) * invDet; dst[15] = (m20 * t03 - m21 * t01 + m22 * t00) * invDet; } function asNormalMatrix(m) { // inverse transpose of upper-left 3x3 matrix var out = new Float32Array(9); var m11 = m[0], m21 = m[1], m31 = m[2], m12 = m[4], m22 = m[5], m32 = m[6], m13 = m[8], m23 = m[9], m33 = m[10]; var t11 = m33 * m22 - m32 * m23, t12 = m32 * m13 - m33 * m12, t13 = m23 * m12 - m22 * m13; var det = m11 * t11 + m21 * t12 + m31 * t13; if (det !== 0 ) { const s = 1 / det; // store in transposed order out[0] = t11 * s; out[3] = (m31 * m23 - m33 * m21) * s; out[6] = (m32 * m21 - m31 * m22) * s; out[1] = t12 * s; out[4] = (m33 * m11 - m31 * m13) * s; out[7] = (m31 * m12 - m32 * m11) * s; out[2] = t13 * s; out[5] = (m21 * m13 - m23 * m11) * s; out[8] = (m22 * m11 - m21 * m12) * s; } return out; } var GL_Symbols; // reverse mapping for debug printing function GL_Symbol(constant, rangeStart) { if (constant === undefined) { debugger; return /* should not happen */} if (rangeStart !== undefined) { // rangeStart is e.g. "FALSE" or "POINTS" which are both 0 var all = Object.keys(GL); // we're relying on insertion order here var start = all.indexOf(rangeStart); return all[start + constant - GL[rangeStart]]; } else return GL_Symbols[constant] || constant; } function initGLConstants() { GL = { DEPTH_BUFFER_BIT: 0x00000100, STENCIL_BUFFER_BIT: 0x00000400, COLOR_BUFFER_BIT: 0x00004000, FALSE: 0, TRUE: 1, POINTS: 0x0000, LINES: 0x0001, LINE_LOOP: 0x0002, LINE_STRIP: 0x0003, TRIANGLES: 0x0004, TRIANGLE_STRIP: 0x0005, TRIANGLE_FAN: 0x0006, QUADS: 0x0007, QUAD_STRIP: 0x0008, POLYGON: 0x0009, NEVER: 0x0200, LESS: 0x0201, EQUAL: 0x0202, LEQUAL: 0x0203, GREATER: 0x0204, NOTEQUAL: 0x0205, GEQUAL: 0x0206, ALWAYS: 0x0207, SRC_COLOR: 0x0300, ONE_MINUS_SRC_COLOR: 0x0301, SRC_ALPHA: 0x0302, ONE_MINUS_SRC_ALPHA: 0x0303, DST_ALPHA: 0x0304, ONE_MINUS_DST_ALPHA: 0x0305, DST_COLOR: 0x0306, ONE_MINUS_DST_COLOR: 0x0307, SRC_ALPHA_SATURATE: 0x0308, NONE: 0, FRONT_LEFT: 0x0400, FRONT_RIGHT: 0x0401, BACK_LEFT: 0x0402, BACK_RIGHT: 0x0403, FRONT: 0x0404, BACK: 0x0405, LEFT: 0x0406, RIGHT: 0x0407, FRONT_AND_BACK: 0x0408, NO_ERROR: 0, INVALID_ENUM: 0x0500, INVALID_VALUE: 0x0501, INVALID_OPERATION: 0x0502, OUT_OF_MEMORY: 0x0505, EXP: 0x0800, EXP2: 0x0801, CW: 0x0900, CCW: 0x0901, POINT_SIZE: 0x0B11, POINT_SIZE_RANGE: 0x0B12, POINT_SIZE_GRANULARITY: 0x0B13, LINE_SMOOTH: 0x0B20, LINE_WIDTH: 0x0B21, LINE_WIDTH_RANGE: 0x0B22, LINE_WIDTH_GRANULARITY: 0x0B23, LIST_INDEX: 0x0B33, LIGHTING: 0x0B50, ALPHA_TEST: 0x0BC0, POLYGON_MODE: 0x0B40, POLYGON_SMOOTH: 0x0B41, CULL_FACE: 0x0B44, CULL_FACE_MODE: 0x0B45, FRONT_FACE: 0x0B46, COLOR_MATERIAL: 0x0B57, FOG: 0x0B60, FOG_DENSITY: 0x0B62, FOG_START: 0x0B63, FOG_END: 0x0B64, FOG_MODE: 0x0B65, FOG_COLOR: 0x0B66, DEPTH_RANGE: 0x0B70, DEPTH_TEST: 0x0B71, DEPTH_WRITEMASK: 0x0B72, DEPTH_CLEAR_VALUE: 0x0B73, DEPTH_FUNC: 0x0B74, STENCIL_TEST: 0x0B90, STENCIL_CLEAR_VALUE: 0x0B91, STENCIL_FUNC: 0x0B92, STENCIL_VALUE_MASK: 0x0B93, STENCIL_FAIL: 0x0B94, STENCIL_PASS_DEPTH_FAIL: 0x0B95, STENCIL_PASS_DEPTH_PASS: 0x0B96, STENCIL_REF: 0x0B97, STENCIL_WRITEMASK: 0x0B98, MATRIX_MODE: 0x0BA0, NORMALIZE: 0x0BA1, VIEWPORT: 0x0BA2, MODELVIEW_STACK_DEPTH: 0x0BA3, PROJECTION_STACK_DEPTH: 0x0BA4, TEXTURE_STACK_DEPTH: 0x0BA5, MODELVIEW_MATRIX: 0x0BA6, PROJECTION_MATRIX: 0x0BA7, TEXTURE_MATRIX: 0x0BA8, ATTRIB_STACK_DEPTH: 0x0BB0, CLIENT_ATTRIB_STACK_DEPTH: 0x0BB1, ALPHA_TEST: 0x0BC0, ALPHA_TEST_FUNC: 0x0BC1, ALPHA_TEST_REF: 0x0BC2, DITHER: 0x0BD0, BLEND_DST: 0x0BE0, BLEND_SRC: 0x0BE1, BLEND: 0x0BE2, LOGIC_OP_MODE: 0x0BF0, DRAW_BUFFER: 0x0C01, READ_BUFFER: 0x0C02, SCISSOR_BOX: 0x0C10, SCISSOR_TEST: 0x0C11, COLOR_CLEAR_VALUE: 0x0C22, COLOR_WRITEMASK: 0x0C23, DOUBLEBUFFER: 0x0C32, STEREO: 0x0C33, PERSPECTIVE_CORRECTION_HINT: 0x0C50, LINE_SMOOTH_HINT: 0x0C52, POLYGON_SMOOTH_HINT: 0x0C53, FOG_HINT: 0x0C54, TEXTURE_GEN_S: 0x0C60, TEXTURE_GEN_T: 0x0C61, TEXTURE_GEN_R: 0x0C62, TEXTURE_GEN_Q: 0x0C63, PIXEL_MAP_I_TO_I: 0x0C70, PIXEL_MAP_S_TO_S: 0x0C71, PIXEL_MAP_I_TO_R: 0x0C72, PIXEL_MAP_I_TO_G: 0x0C73, PIXEL_MAP_I_TO_B: 0x0C74, PIXEL_MAP_I_TO_A: 0x0C75, PIXEL_MAP_R_TO_R: 0x0C76, PIXEL_MAP_G_TO_G: 0x0C77, PIXEL_MAP_B_TO_B: 0x0C78, PIXEL_MAP_A_TO_A: 0x0C79, PIXEL_MAP_I_TO_I_SIZE: 0x0CB0, PIXEL_MAP_S_TO_S_SIZE: 0x0CB1, PIXEL_MAP_I_TO_R_SIZE: 0x0CB2, PIXEL_MAP_I_TO_G_SIZE: 0x0CB3, PIXEL_MAP_I_TO_B_SIZE: 0x0CB4, PIXEL_MAP_I_TO_A_SIZE: 0x0CB5, PIXEL_MAP_R_TO_R_SIZE: 0x0CB6, PIXEL_MAP_G_TO_G_SIZE: 0x0CB7, PIXEL_MAP_B_TO_B_SIZE: 0x0CB8, PIXEL_MAP_A_TO_A_SIZE: 0x0CB9, UNPACK_SWAP_BYTES: 0x0CF0, UNPACK_LSB_FIRST: 0x0CF1, UNPACK_ROW_LENGTH: 0x0CF2, UNPACK_SKIP_ROWS: 0x0CF3, UNPACK_SKIP_PIXELS: 0x0CF4, UNPACK_ALIGNMENT: 0x0CF5, PACK_SWAP_BYTES: 0x0D00, PACK_LSB_FIRST: 0x0D01, PACK_ROW_LENGTH: 0x0D02, PACK_SKIP_ROWS: 0x0D03, PACK_SKIP_PIXELS: 0x0D04, PACK_ALIGNMENT: 0x0D05, MAX_TEXTURE_SIZE: 0x0D33, MAX_VIEWPORT_DIMS: 0x0D3A, SUBPIXEL_BITS: 0x0D50, TEXTURE_1D: 0x0DE0, TEXTURE_2D: 0x0DE1, TEXTURE_WIDTH: 0x1000, TEXTURE_HEIGHT: 0x1001, TEXTURE_BORDER_COLOR: 0x1004, DONT_CARE: 0x1100, FASTEST: 0x1101, NICEST: 0x1102, AMBIENT: 0x1200, DIFFUSE: 0x1201, SPECULAR: 0x1202, POSITION: 0x1203, SPOT_CUTOFF: 0x1206, COMPILE: 0x1300, COMPILE_AND_EXECUTE: 0x1301, BYTE: 0x1400, UNSIGNED_BYTE: 0x1401, SHORT: 0x1402, UNSIGNED_SHORT: 0x1403, INT: 0x1404, UNSIGNED_INT: 0x1405, FLOAT: 0x1406, STACK_OVERFLOW: 0x0503, STACK_UNDERFLOW: 0x0504, CLEAR: 0x1500, AND: 0x1501, AND_REVERSE: 0x1502, COPY: 0x1503, AND_INVERTED: 0x1504, NOOP: 0x1505, XOR: 0x1506, OR: 0x1507, NOR: 0x1508, EQUIV: 0x1509, INVERT: 0x150A, OR_REVERSE: 0x150B, COPY_INVERTED: 0x150C, OR_INVERTED: 0x150D, NAND: 0x150E, SET: 0x150F, EMISSION: 0x1600, SHININESS: 0x1601, AMBIENT_AND_DIFFUSE: 0x1602, COLOR_INDEXES: 0x1603, MODELVIEW: 0x1700, PROJECTION: 0x1701, TEXTURE: 0x1702, COLOR: 0x1800, DEPTH: 0x1801, STENCIL: 0x1802, STENCIL_INDEX: 0x1901, DEPTH_COMPONENT: 0x1902, RED: 0x1903, GREEN: 0x1904, BLUE: 0x1905, ALPHA: 0x1906, RGB: 0x1907, RGBA: 0x1908, POINT: 0x1B00, LINE: 0x1B01, FILL: 0x1B02, FLAT: 0x1D00, SMOOTH: 0x1D01, KEEP: 0x1E00, REPLACE: 0x1E01, INCR: 0x1E02, DECR: 0x1E03, VENDOR: 0x1F00, RENDERER: 0x1F01, VERSION: 0x1F02, EXTENSIONS: 0x1F03, S: 0x2000, T: 0x2001, R: 0x2002, Q: 0x2003, MODULATE: 0x2100, DECAL: 0x2101, TEXTURE_ENV_MODE: 0x2200, TEXTURE_ENV: 0x2300, EYE_LINEAR: 0x2400, OBJECT_LINEAR: 0x2401, SPHERE_MAP: 0x2402, TEXTURE_GEN_MODE: 0x2500, OBJECT_PLANE: 0x2501, EYE_PLANE: 0x2502, SPHERE_MAP: 0x2402, NEAREST: 0x2600, LINEAR: 0x2601, NEAREST_MIPMAP_NEAREST: 0x2700, LINEAR_MIPMAP_NEAREST: 0x2701, NEAREST_MIPMAP_LINEAR: 0x2702, LINEAR_MIPMAP_LINEAR: 0x2703, TEXTURE_MAG_FILTER: 0x2800, TEXTURE_MIN_FILTER: 0x2801, TEXTURE_WRAP_S: 0x2802, TEXTURE_WRAP_T: 0x2803, REPEAT: 0x2901, CLIP_PLANE0: 0x3000, CLIP_PLANE1: 0x3001, CLIP_PLANE2: 0x3002, CLIP_PLANE3: 0x3003, CLIP_PLANE4: 0x3004, CLIP_PLANE5: 0x3005, CLIP_PLANE6: 0x3006, CLIP_PLANE7: 0x3007, LIGHT0: 0x4000, LIGHT1: 0x4001, LIGHT2: 0x4002, LIGHT3: 0x4003, LIGHT4: 0x4004, LIGHT5: 0x4005, LIGHT6: 0x4006, LIGHT7: 0x4007, BGRA: 0x80E1, CLAMP_TO_EDGE: 0x812F, VERTEX_ARRAY: 0x8074, NORMAL_ARRAY: 0x8075, COLOR_ARRAY: 0x8076, INDEX_ARRAY: 0x8077, TEXTURE_COORD_ARRAY: 0x8078, GENERATE_MIPMAP_SGIS: 0x8191, GENERATE_MIPMAP_HINT_SGIS: 0x8192, TEXTURE_COMPRESSED: 0x86A1, CURRENT_BIT: 0x00001, POINT_BIT: 0x00002, LINE_BIT: 0x00004, POLYGON_BIT: 0x00008, POLYGON_STIPPLE_BIT: 0x00010, PIXEL_MODE_BIT: 0x00020, LIGHTING_BIT: 0x00040, FOG_BIT: 0x00080, DEPTH_BUFFER_BIT: 0x00100, ACCUM_BUFFER_BIT: 0x00200, STENCIL_BUFFER_BIT: 0x00400, VIEWPORT_BIT: 0x00800, TRANSFORM_BIT: 0x01000, ENABLE_BIT: 0x02000, COLOR_BUFFER_BIT: 0x04000, HINT_BIT: 0x08000, EVAL_BIT: 0x10000, LIST_BIT: 0x20000, TEXTURE_BIT: 0x40000, SCISSOR_BIT: 0x80000, ALL_ATTRIB_BITS: 0xFFFFF, ZERO: 0, ONE: 1, }; GL_Symbols = {}; for (var name in GL) { var value = GL[name]; GL_Symbols[value] = name; } } function registerOpenGL() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { Squeak.registerExternalModule('libGL.so', OpenGL()); } else self.setTimeout(registerOpenGL, 100); } registerOpenGL(); // Copyright (c) 2013 Pieroxy // This work is free. You can redistribute it and/or modify it // under the terms of the WTFPL, Version 2 // For more information see LICENSE.txt or http://www.wtfpl.net/ // // For more information, the home page: // http://pieroxy.net/blog/pages/lz-string/testing.html // // LZ-based compression algorithm, version 1.3.3 var LZString$1 = { // private property _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", _f : String.fromCharCode, compressToBase64 : function (input) { if (input == null) return ""; var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = LZString$1.compress(input); while (i < input.length*2) { if (i%2==0) { chr1 = input.charCodeAt(i/2) >> 8; chr2 = input.charCodeAt(i/2) & 255; if (i/2+1 < input.length) chr3 = input.charCodeAt(i/2+1) >> 8; else chr3 = NaN; } else { chr1 = input.charCodeAt((i-1)/2) & 255; if ((i+1)/2 < input.length) { chr2 = input.charCodeAt((i+1)/2) >> 8; chr3 = input.charCodeAt((i+1)/2) & 255; } else chr2=chr3=NaN; } i+=3; enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + LZString$1._keyStr.charAt(enc1) + LZString$1._keyStr.charAt(enc2) + LZString$1._keyStr.charAt(enc3) + LZString$1._keyStr.charAt(enc4); } return output; }, decompressFromBase64 : function (input) { if (input == null) return ""; var output = "", ol = 0, output_, chr1, chr2, chr3, enc1, enc2, enc3, enc4, i = 0, f=LZString$1._f; input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (i < input.length) { enc1 = LZString$1._keyStr.indexOf(input.charAt(i++)); enc2 = LZString$1._keyStr.indexOf(input.charAt(i++)); enc3 = LZString$1._keyStr.indexOf(input.charAt(i++)); enc4 = LZString$1._keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; if (ol%2==0) { output_ = chr1 << 8; if (enc3 != 64) { output += f(output_ | chr2); } if (enc4 != 64) { output_ = chr3 << 8; } } else { output = output + f(output_ | chr1); if (enc3 != 64) { output_ = chr2 << 8; } if (enc4 != 64) { output += f(output_ | chr3); } } ol+=3; } return LZString$1.decompress(output); }, compressToUTF16 : function (input) { if (input == null) return ""; var output = "", i,c, current, status = 0, f = LZString$1._f; input = LZString$1.compress(input); for (i=0 ; i> 1)+32); current = (c & 1) << 14; break; case 1: output += f((current + (c >> 2))+32); current = (c & 3) << 13; break; case 2: output += f((current + (c >> 3))+32); current = (c & 7) << 12; break; case 3: output += f((current + (c >> 4))+32); current = (c & 15) << 11; break; case 4: output += f((current + (c >> 5))+32); current = (c & 31) << 10; break; case 5: output += f((current + (c >> 6))+32); current = (c & 63) << 9; break; case 6: output += f((current + (c >> 7))+32); current = (c & 127) << 8; break; case 7: output += f((current + (c >> 8))+32); current = (c & 255) << 7; break; case 8: output += f((current + (c >> 9))+32); current = (c & 511) << 6; break; case 9: output += f((current + (c >> 10))+32); current = (c & 1023) << 5; break; case 10: output += f((current + (c >> 11))+32); current = (c & 2047) << 4; break; case 11: output += f((current + (c >> 12))+32); current = (c & 4095) << 3; break; case 12: output += f((current + (c >> 13))+32); current = (c & 8191) << 2; break; case 13: output += f((current + (c >> 14))+32); current = (c & 16383) << 1; break; case 14: output += f((current + (c >> 15))+32, (c & 32767)+32); status = 0; break; } } return output + f(current + 32); }, decompressFromUTF16 : function (input) { if (input == null) return ""; var output = "", current,c, status=0, i = 0, f = LZString$1._f; while (i < input.length) { c = input.charCodeAt(i) - 32; switch (status++) { case 0: current = c << 1; break; case 1: output += f(current | (c >> 14)); current = (c&16383) << 2; break; case 2: output += f(current | (c >> 13)); current = (c&8191) << 3; break; case 3: output += f(current | (c >> 12)); current = (c&4095) << 4; break; case 4: output += f(current | (c >> 11)); current = (c&2047) << 5; break; case 5: output += f(current | (c >> 10)); current = (c&1023) << 6; break; case 6: output += f(current | (c >> 9)); current = (c&511) << 7; break; case 7: output += f(current | (c >> 8)); current = (c&255) << 8; break; case 8: output += f(current | (c >> 7)); current = (c&127) << 9; break; case 9: output += f(current | (c >> 6)); current = (c&63) << 10; break; case 10: output += f(current | (c >> 5)); current = (c&31) << 11; break; case 11: output += f(current | (c >> 4)); current = (c&15) << 12; break; case 12: output += f(current | (c >> 3)); current = (c&7) << 13; break; case 13: output += f(current | (c >> 2)); current = (c&3) << 14; break; case 14: output += f(current | (c >> 1)); current = (c&1) << 15; break; case 15: output += f(current | c); status=0; break; } i++; } return LZString$1.decompress(output); //return output; }, compress: function (uncompressed) { if (uncompressed == null) return ""; var i, value, context_dictionary= {}, context_dictionaryToCreate= {}, context_c="", context_wc="", context_w="", context_enlargeIn= 2, // Compensate for the first entry which should not count context_dictSize= 3, context_numBits= 2, context_data_string="", context_data_val=0, context_data_position=0, ii, f=LZString$1._f; for (ii = 0; ii < uncompressed.length; ii += 1) { context_c = uncompressed.charAt(ii); if (!Object.prototype.hasOwnProperty.call(context_dictionary,context_c)) { context_dictionary[context_c] = context_dictSize++; context_dictionaryToCreate[context_c] = true; } context_wc = context_w + context_c; if (Object.prototype.hasOwnProperty.call(context_dictionary,context_wc)) { context_w = context_wc; } else { if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { if (context_w.charCodeAt(0)<256) { for (i=0 ; i> 1; } } else { value = 1; for (i=0 ; i> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } delete context_dictionaryToCreate[context_w]; } else { value = context_dictionary[context_w]; for (i=0 ; i> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } // Add wc to the dictionary. context_dictionary[context_wc] = context_dictSize++; context_w = String(context_c); } } // Output the code for w. if (context_w !== "") { if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { if (context_w.charCodeAt(0)<256) { for (i=0 ; i> 1; } } else { value = 1; for (i=0 ; i> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } delete context_dictionaryToCreate[context_w]; } else { value = context_dictionary[context_w]; for (i=0 ; i> 1; } } context_enlargeIn--; if (context_enlargeIn == 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } } // Mark the end of the stream value = 2; for (i=0 ; i> 1; } // Flush the last char while (true) { context_data_val = (context_data_val << 1); if (context_data_position == 15) { context_data_string += f(context_data_val); break; } else context_data_position++; } return context_data_string; }, decompress: function (compressed) { if (compressed == null) return ""; if (compressed == "") return null; var dictionary = [], enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", result = "", i, w, bits, resb, maxpower, power, c, f = LZString$1._f, data = {string:compressed, val:compressed.charCodeAt(0), position:32768, index:1}; for (i = 0; i < 3; i += 1) { dictionary[i] = i; } bits = 0; maxpower = Math.pow(2,2); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = 32768; data.val = data.string.charCodeAt(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } switch (bits) { case 0: bits = 0; maxpower = Math.pow(2,8); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = 32768; data.val = data.string.charCodeAt(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } c = f(bits); break; case 1: bits = 0; maxpower = Math.pow(2,16); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = 32768; data.val = data.string.charCodeAt(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } c = f(bits); break; case 2: return ""; } dictionary[3] = c; w = result = c; while (true) { if (data.index > data.string.length) { return ""; } bits = 0; maxpower = Math.pow(2,numBits); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = 32768; data.val = data.string.charCodeAt(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } switch (c = bits) { case 0: bits = 0; maxpower = Math.pow(2,8); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = 32768; data.val = data.string.charCodeAt(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } dictionary[dictSize++] = f(bits); c = dictSize-1; enlargeIn--; break; case 1: bits = 0; maxpower = Math.pow(2,16); power=1; while (power!=maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position == 0) { data.position = 32768; data.val = data.string.charCodeAt(data.index++); } bits |= (resb>0 ? 1 : 0) * power; power <<= 1; } dictionary[dictSize++] = f(bits); c = dictSize-1; enlargeIn--; break; case 2: return result; } if (enlargeIn == 0) { enlargeIn = Math.pow(2, numBits); numBits++; } if (dictionary[c]) { entry = dictionary[c]; } else { if (c === dictSize) { entry = w + w.charAt(0); } else { return null; } } result += entry; // Add w+entry[0] to the dictionary. dictionary[dictSize++] = w + entry.charAt(0); enlargeIn--; w = entry; if (enlargeIn == 0) { enlargeIn = Math.pow(2, numBits); numBits++; } } } }; if( typeof module !== 'undefined' && module != null ) { module.exports = LZString$1; } /*! JSZip - A Javascript class for generating and reading zip files (c) 2009-2014 Stuart Knightley Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. JSZip uses the library pako released under the MIT license : https://github.com/nodeca/pako/blob/master/LICENSE */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f();}else if(typeof define==="function"&&define.amd){define([],f);}else {var g;if(typeof window!=="undefined"){g=window;}else if(typeof global!=="undefined"){g=global;}else if(typeof self!=="undefined"){g=self;}else {g=this;}g.JSZip = f();}})(function(){return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,true);if(i)return i(o,true);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r);}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = remainingBytes > 1 ? (((chr2 & 15) << 2) | (chr3 >> 6)) : 64; enc4 = remainingBytes > 2 ? (chr3 & 63) : 64; output.push(_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _keyStr.charAt(enc4)); } return output.join(""); }; // public method for decoding exports.decode = function(input) { var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0, resultIndex = 0; input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); var totalLength = input.length * 3 / 4; if(input.charAt(input.length - 1) === _keyStr.charAt(64)) { totalLength--; } if(input.charAt(input.length - 2) === _keyStr.charAt(64)) { totalLength--; } var output; if (support.uint8array) { output = new Uint8Array(totalLength); } else { output = new Array(totalLength); } while (i < input.length) { enc1 = _keyStr.indexOf(input.charAt(i++)); enc2 = _keyStr.indexOf(input.charAt(i++)); enc3 = _keyStr.indexOf(input.charAt(i++)); enc4 = _keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output[resultIndex++] = chr1; if (enc3 !== 64) { output[resultIndex++] = chr2; } if (enc4 !== 64) { output[resultIndex++] = chr3; } } return output; }; },{"./support":27,"./utils":29}],2:[function(require,module,exports){ var external = require("./external"); var DataWorker = require('./stream/DataWorker'); var DataLengthProbe = require('./stream/DataLengthProbe'); var Crc32Probe = require('./stream/Crc32Probe'); var DataLengthProbe = require('./stream/DataLengthProbe'); /** * Represent a compressed object, with everything needed to decompress it. * @constructor * @param {number} compressedSize the size of the data compressed. * @param {number} uncompressedSize the size of the data after decompression. * @param {number} crc32 the crc32 of the decompressed file. * @param {object} compression the type of compression, see lib/compressions.js. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the compressed data. */ function CompressedObject(compressedSize, uncompressedSize, crc32, compression, data) { this.compressedSize = compressedSize; this.uncompressedSize = uncompressedSize; this.crc32 = crc32; this.compression = compression; this.compressedContent = data; } CompressedObject.prototype = { /** * Create a worker to get the uncompressed content. * @return {GenericWorker} the worker. */ getContentWorker : function () { var worker = new DataWorker(external.Promise.resolve(this.compressedContent)) .pipe(this.compression.uncompressWorker()) .pipe(new DataLengthProbe("data_length")); var that = this; worker.on("end", function () { if(this.streamInfo['data_length'] !== that.uncompressedSize) { throw new Error("Bug : uncompressed data size mismatch"); } }); return worker; }, /** * Create a worker to get the compressed content. * @return {GenericWorker} the worker. */ getCompressedWorker : function () { return new DataWorker(external.Promise.resolve(this.compressedContent)) .withStreamInfo("compressedSize", this.compressedSize) .withStreamInfo("uncompressedSize", this.uncompressedSize) .withStreamInfo("crc32", this.crc32) .withStreamInfo("compression", this.compression) ; } }; /** * Chain the given worker with other workers to compress the content with the * given compresion. * @param {GenericWorker} uncompressedWorker the worker to pipe. * @param {Object} compression the compression object. * @param {Object} compressionOptions the options to use when compressing. * @return {GenericWorker} the new worker compressing the content. */ CompressedObject.createWorkerFrom = function (uncompressedWorker, compression, compressionOptions) { return uncompressedWorker .pipe(new Crc32Probe()) .pipe(new DataLengthProbe("uncompressedSize")) .pipe(compression.compressWorker(compressionOptions)) .pipe(new DataLengthProbe("compressedSize")) .withStreamInfo("compression", compression); }; module.exports = CompressedObject; },{"./external":6,"./stream/Crc32Probe":22,"./stream/DataLengthProbe":23,"./stream/DataWorker":24}],3:[function(require,module,exports){ var GenericWorker = require("./stream/GenericWorker"); exports.STORE = { magic: "\x00\x00", compressWorker : function (compressionOptions) { return new GenericWorker("STORE compression"); }, uncompressWorker : function () { return new GenericWorker("STORE decompression"); } }; exports.DEFLATE = require('./flate'); },{"./flate":7,"./stream/GenericWorker":25}],4:[function(require,module,exports){ var utils = require('./utils'); /** * The following functions come from pako, from pako/lib/zlib/crc32.js * released under the MIT license, see pako https://github.com/nodeca/pako/ */ // Use ordinary array, since untyped makes no boost here function makeTable() { var c, table = []; for(var n =0; n < 256; n++){ c = n; for(var k =0; k < 8; k++){ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } table[n] = c; } return table; } // Create table on load. Just 255 signed longs. Not a problem. var crcTable = makeTable(); function crc32(crc, buf, len, pos) { var t = crcTable, end = pos + len; crc = crc ^ (-1); for (var i = pos; i < end; i++ ) { crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; } return (crc ^ (-1)); // >>> 0; } // That's all for the pako functions. /** * Compute the crc32 of a string. * This is almost the same as the function crc32, but for strings. Using the * same function for the two use cases leads to horrible performances. * @param {Number} crc the starting value of the crc. * @param {String} str the string to use. * @param {Number} len the length of the string. * @param {Number} pos the starting position for the crc32 computation. * @return {Number} the computed crc32. */ function crc32str(crc, str, len, pos) { var t = crcTable, end = pos + len; crc = crc ^ (-1); for (var i = pos; i < end; i++ ) { crc = (crc >>> 8) ^ t[(crc ^ str.charCodeAt(i)) & 0xFF]; } return (crc ^ (-1)); // >>> 0; } module.exports = function crc32wrapper(input, crc) { if (typeof input === "undefined" || !input.length) { return 0; } var isArray = utils.getTypeOf(input) !== "string"; if(isArray) { return crc32(crc|0, input, input.length, 0); } else { return crc32str(crc|0, input, input.length, 0); } }; // vim: set shiftwidth=4 softtabstop=4: },{"./utils":29}],5:[function(require,module,exports){ exports.base64 = false; exports.binary = false; exports.dir = false; exports.createFolders = true; exports.date = null; exports.compression = null; exports.compressionOptions = null; exports.comment = null; exports.unixPermissions = null; exports.dosPermissions = null; },{}],6:[function(require,module,exports){ var ES6Promise = require("es6-promise").Promise; /** * Let the user use/change some implementations. */ module.exports = { Promise: ES6Promise }; },{"es6-promise":37}],7:[function(require,module,exports){ var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined'); var pako = require("pako"); var utils = require("./utils"); var GenericWorker = require("./stream/GenericWorker"); var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array"; exports.magic = "\x08\x00"; /** * Create a worker that uses pako to inflate/deflate. * @constructor * @param {String} action the name of the pako function to call : either "Deflate" or "Inflate". * @param {Object} options the options to use when (de)compressing. */ function FlateWorker(action, options) { GenericWorker.call(this, "FlateWorker/" + action); this._pako = new pako[action]({ raw:true, level : options.level || -1 // default compression }); // the `meta` object from the last chunk received // this allow this worker to pass around metadata this.meta = {}; var self = this; this._pako.onData = function(data) { self.push({ data : data, meta : self.meta }); }; } utils.inherits(FlateWorker, GenericWorker); /** * @see GenericWorker.processChunk */ FlateWorker.prototype.processChunk = function (chunk) { this.meta = chunk.meta; this._pako.push(utils.transformTo(ARRAY_TYPE, chunk.data), false); }; /** * @see GenericWorker.flush */ FlateWorker.prototype.flush = function () { GenericWorker.prototype.flush.call(this); this._pako.push([], true); }; /** * @see GenericWorker.cleanUp */ FlateWorker.prototype.cleanUp = function () { GenericWorker.prototype.cleanUp.call(this); this._pako = null; }; exports.compressWorker = function (compressionOptions) { return new FlateWorker("Deflate", compressionOptions); }; exports.uncompressWorker = function () { return new FlateWorker("Inflate", {}); }; },{"./stream/GenericWorker":25,"./utils":29,"pako":38}],8:[function(require,module,exports){ var utils = require('../utils'); var GenericWorker = require('../stream/GenericWorker'); var utf8 = require('../utf8'); var crc32 = require('../crc32'); var signature = require('../signature'); /** * Transform an integer into a string in hexadecimal. * @private * @param {number} dec the number to convert. * @param {number} bytes the number of bytes to generate. * @returns {string} the result. */ var decToHex = function(dec, bytes) { var hex = "", i; for (i = 0; i < bytes; i++) { hex += String.fromCharCode(dec & 0xff); dec = dec >>> 8; } return hex; }; /** * Generate the UNIX part of the external file attributes. * @param {Object} unixPermissions the unix permissions or null. * @param {Boolean} isDir true if the entry is a directory, false otherwise. * @return {Number} a 32 bit integer. * * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute : * * TTTTsstrwxrwxrwx0000000000ADVSHR * ^^^^____________________________ file type, see zipinfo.c (UNX_*) * ^^^_________________________ setuid, setgid, sticky * ^^^^^^^^^________________ permissions * ^^^^^^^^^^______ not used ? * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only */ var generateUnixExternalFileAttr = function (unixPermissions, isDir) { var result = unixPermissions; if (!unixPermissions) { // I can't use octal values in strict mode, hence the hexa. // 040775 => 0x41fd // 0100664 => 0x81b4 result = isDir ? 0x41fd : 0x81b4; } return (result & 0xFFFF) << 16; }; /** * Generate the DOS part of the external file attributes. * @param {Object} dosPermissions the dos permissions or null. * @param {Boolean} isDir true if the entry is a directory, false otherwise. * @return {Number} a 32 bit integer. * * Bit 0 Read-Only * Bit 1 Hidden * Bit 2 System * Bit 3 Volume Label * Bit 4 Directory * Bit 5 Archive */ var generateDosExternalFileAttr = function (dosPermissions, isDir) { // the dir flag is already set for compatibility return (dosPermissions || 0) & 0x3F; }; /** * Generate the various parts used in the construction of the final zip file. * @param {Object} streamInfo the hash with informations about the compressed file. * @param {Boolean} streamedContent is the content streamed ? * @param {Boolean} streamingEnded is the stream finished ? * @param {number} offset the current offset from the start of the zip file. * @param {String} platform let's pretend we are this platform (change platform dependents fields) * @param {Function} encodeFileName the function to encode the file name / comment. * @return {Object} the zip parts. */ var generateZipParts = function(streamInfo, streamedContent, streamingEnded, offset, platform, encodeFileName) { var file = streamInfo['file'], compression = streamInfo['compression'], useCustomEncoding = encodeFileName !== utf8.utf8encode, encodedFileName = utils.transformTo("string", encodeFileName(file.name)), utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), comment = file.comment, encodedComment = utils.transformTo("string", encodeFileName(comment)), utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, useUTF8ForComment = utfEncodedComment.length !== comment.length, dosTime, dosDate, extraFields = "", unicodePathExtraField = "", unicodeCommentExtraField = "", dir = file.dir, date = file.date; var dataInfo = { crc32 : 0, compressedSize : 0, uncompressedSize : 0 }; // if the content is streamed, the sizes/crc32 are only available AFTER // the end of the stream. if (!streamedContent || streamingEnded) { dataInfo.crc32 = streamInfo['crc32']; dataInfo.compressedSize = streamInfo['compressedSize']; dataInfo.uncompressedSize = streamInfo['uncompressedSize']; } var bitflag = 0; if (streamedContent) { bitflag |= 0x0008; } if (!useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment)) { bitflag |= 0x0800; } var extFileAttr = 0; var versionMadeBy = 0; if (dir) { // dos or unix, we set the dos dir flag extFileAttr |= 0x00010; } if(platform === "UNIX") { versionMadeBy = 0x031E; // UNIX, version 3.0 extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir); } else { // DOS or other, fallback to DOS versionMadeBy = 0x0014; // DOS, version 2.0 extFileAttr |= generateDosExternalFileAttr(file.dosPermissions); } // date // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html // @see http://www.delorie.com/djgpp/doc/rbinter/it/66/16.html dosTime = date.getUTCHours(); dosTime = dosTime << 6; dosTime = dosTime | date.getUTCMinutes(); dosTime = dosTime << 5; dosTime = dosTime | date.getUTCSeconds() / 2; dosDate = date.getUTCFullYear() - 1980; dosDate = dosDate << 4; dosDate = dosDate | (date.getUTCMonth() + 1); dosDate = dosDate << 5; dosDate = dosDate | date.getUTCDate(); if (useUTF8ForFileName) { // set the unicode path extra field. unzip needs at least one extra // field to correctly handle unicode path, so using the path is as good // as any other information. This could improve the situation with // other archive managers too. // This field is usually used without the utf8 flag, with a non // unicode path in the header (winrar, winzip). This helps (a bit) // with the messy Windows' default compressed folders feature but // breaks on p7zip which doesn't seek the unicode path extra field. // So for now, UTF-8 everywhere ! unicodePathExtraField = // Version decToHex(1, 1) + // NameCRC32 decToHex(crc32(encodedFileName), 4) + // UnicodeName utfEncodedFileName; extraFields += // Info-ZIP Unicode Path Extra Field "\x75\x70" + // size decToHex(unicodePathExtraField.length, 2) + // content unicodePathExtraField; } if(useUTF8ForComment) { unicodeCommentExtraField = // Version decToHex(1, 1) + // CommentCRC32 decToHex(crc32(encodedComment), 4) + // UnicodeName utfEncodedComment; extraFields += // Info-ZIP Unicode Path Extra Field "\x75\x63" + // size decToHex(unicodeCommentExtraField.length, 2) + // content unicodeCommentExtraField; } var header = ""; // version needed to extract header += "\x0A\x00"; // general purpose bit flag // set bit 11 if utf8 header += decToHex(bitflag, 2); // compression method header += compression.magic; // last mod file time header += decToHex(dosTime, 2); // last mod file date header += decToHex(dosDate, 2); // crc-32 header += decToHex(dataInfo.crc32, 4); // compressed size header += decToHex(dataInfo.compressedSize, 4); // uncompressed size header += decToHex(dataInfo.uncompressedSize, 4); // file name length header += decToHex(encodedFileName.length, 2); // extra field length header += decToHex(extraFields.length, 2); var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields; var dirRecord = signature.CENTRAL_FILE_HEADER + // version made by (00: DOS) decToHex(versionMadeBy, 2) + // file header (common to file and central directory) header + // file comment length decToHex(encodedComment.length, 2) + // disk number start "\x00\x00" + // internal file attributes TODO "\x00\x00" + // external file attributes decToHex(extFileAttr, 4) + // relative offset of local header decToHex(offset, 4) + // file name encodedFileName + // extra field extraFields + // file comment encodedComment; return { fileRecord: fileRecord, dirRecord: dirRecord }; }; /** * Generate the EOCD record. * @param {Number} entriesCount the number of entries in the zip file. * @param {Number} centralDirLength the length (in bytes) of the central dir. * @param {Number} localDirLength the length (in bytes) of the local dir. * @param {String} comment the zip file comment as a binary string. * @param {Function} encodeFileName the function to encode the comment. * @return {String} the EOCD record. */ var generateCentralDirectoryEnd = function (entriesCount, centralDirLength, localDirLength, comment, encodeFileName) { var dirEnd = ""; var encodedComment = utils.transformTo("string", encodeFileName(comment)); // end of central dir signature dirEnd = signature.CENTRAL_DIRECTORY_END + // number of this disk "\x00\x00" + // number of the disk with the start of the central directory "\x00\x00" + // total number of entries in the central directory on this disk decToHex(entriesCount, 2) + // total number of entries in the central directory decToHex(entriesCount, 2) + // size of the central directory 4 bytes decToHex(centralDirLength, 4) + // offset of start of central directory with respect to the starting disk number decToHex(localDirLength, 4) + // .ZIP file comment length decToHex(encodedComment.length, 2) + // .ZIP file comment encodedComment; return dirEnd; }; /** * Generate data descriptors for a file entry. * @param {Object} streamInfo the hash generated by a worker, containing informations * on the file entry. * @return {String} the data descriptors. */ var generateDataDescriptors = function (streamInfo) { var descriptor = ""; descriptor = signature.DATA_DESCRIPTOR + // crc-32 4 bytes decToHex(streamInfo['crc32'], 4) + // compressed size 4 bytes decToHex(streamInfo['compressedSize'], 4) + // uncompressed size 4 bytes decToHex(streamInfo['uncompressedSize'], 4); return descriptor; }; /** * A worker to concatenate other workers to create a zip file. * @param {Boolean} streamFiles `true` to stream the content of the files, * `false` to accumulate it. * @param {String} comment the comment to use. * @param {String} platform the platform to use, "UNIX" or "DOS". * @param {Function} encodeFileName the function to encode file names and comments. */ function ZipFileWorker(streamFiles, comment, platform, encodeFileName) { GenericWorker.call(this, "ZipFileWorker"); // The number of bytes written so far. This doesn't count accumulated chunks. this.bytesWritten = 0; // The comment of the zip file this.zipComment = comment; // The platform "generating" the zip file. this.zipPlatform = platform; // the function to encode file names and comments. this.encodeFileName = encodeFileName; // Should we stream the content of the files ? this.streamFiles = streamFiles; // If `streamFiles` is false, we will need to accumulate the content of the // files to calculate sizes / crc32 (and write them *before* the content). // This boolean indicates if we are accumulating chunks (it will change a lot // during the lifetime of this worker). this.accumulate = false; // The buffer receiving chunks when accumulating content. this.contentBuffer = []; // The list of generated directory records. this.dirRecords = []; // The offset (in bytes) from the beginning of the zip file for the current source. this.currentSourceOffset = 0; // The total number of entries in this zip file. this.entriesCount = 0; // the name of the file currently being added, null when handling the end of the zip file. // Used for the emited metadata. this.currentFile = null; this._sources = []; } utils.inherits(ZipFileWorker, GenericWorker); /** * @see GenericWorker.push */ ZipFileWorker.prototype.push = function (chunk) { var currentFilePercent = chunk.meta.percent || 0; var entriesCount = this.entriesCount; var remainingFiles = this._sources.length; if(this.accumulate) { this.contentBuffer.push(chunk); } else { this.bytesWritten += chunk.data.length; GenericWorker.prototype.push.call(this, { data : chunk.data, meta : { currentFile : this.currentFile, percent : entriesCount ? (currentFilePercent + 100 * (entriesCount - remainingFiles - 1)) / entriesCount : 100 } }); } }; /** * The worker started a new source (an other worker). * @param {Object} streamInfo the streamInfo object from the new source. */ ZipFileWorker.prototype.openedSource = function (streamInfo) { this.currentSourceOffset = this.bytesWritten; this.currentFile = streamInfo['file'].name; // don't stream folders (because they don't have any content) if(this.streamFiles && !streamInfo['file'].dir) { var record = generateZipParts(streamInfo, this.streamFiles, false, this.currentSourceOffset, this.zipPlatform, this.encodeFileName); this.push({ data : record.fileRecord, meta : {percent:0} }); } else { // we need to wait for the whole file before pushing anything this.accumulate = true; } }; /** * The worker finished a source (an other worker). * @param {Object} streamInfo the streamInfo object from the finished source. */ ZipFileWorker.prototype.closedSource = function (streamInfo) { this.accumulate = false; var record = generateZipParts(streamInfo, this.streamFiles, true, this.currentSourceOffset, this.zipPlatform, this.encodeFileName); this.dirRecords.push(record.dirRecord); if(this.streamFiles && !streamInfo['file'].dir) { // after the streamed file, we put data descriptors this.push({ data : generateDataDescriptors(streamInfo), meta : {percent:100} }); } else { // the content wasn't streamed, we need to push everything now // first the file record, then the content this.push({ data : record.fileRecord, meta : {percent:0} }); while(this.contentBuffer.length) { this.push(this.contentBuffer.shift()); } } this.currentFile = null; }; /** * @see GenericWorker.flush */ ZipFileWorker.prototype.flush = function () { var localDirLength = this.bytesWritten; for(var i = 0; i < this.dirRecords.length; i++) { this.push({ data : this.dirRecords[i], meta : {percent:100} }); } var centralDirLength = this.bytesWritten - localDirLength; var dirEnd = generateCentralDirectoryEnd(this.dirRecords.length, centralDirLength, localDirLength, this.zipComment, this.encodeFileName); this.push({ data : dirEnd, meta : {percent:100} }); }; /** * Prepare the next source to be read. */ ZipFileWorker.prototype.prepareNextSource = function () { this.previous = this._sources.shift(); this.openedSource(this.previous.streamInfo); if (this.isPaused) { this.previous.pause(); } else { this.previous.resume(); } }; /** * @see GenericWorker.registerPrevious */ ZipFileWorker.prototype.registerPrevious = function (previous) { this._sources.push(previous); var self = this; previous.on('data', function (chunk) { self.processChunk(chunk); }); previous.on('end', function () { self.closedSource(self.previous.streamInfo); if(self._sources.length) { self.prepareNextSource(); } else { self.end(); } }); previous.on('error', function (e) { self.error(e); }); return this; }; /** * @see GenericWorker.resume */ ZipFileWorker.prototype.resume = function () { if(!GenericWorker.prototype.resume.call(this)) { return false; } if (!this.previous && this._sources.length) { this.prepareNextSource(); return true; } if (!this.previous && !this._sources.length && !this.generatedError) { this.end(); return true; } }; /** * @see GenericWorker.error */ ZipFileWorker.prototype.error = function (e) { var sources = this._sources; if(!GenericWorker.prototype.error.call(this, e)) { return false; } for(var i = 0; i < sources.length; i++) { try { sources[i].error(e); } catch(e) { // the `error` exploded, nothing to do } } return true; }; /** * @see GenericWorker.lock */ ZipFileWorker.prototype.lock = function () { GenericWorker.prototype.lock.call(this); var sources = this._sources; for(var i = 0; i < sources.length; i++) { sources[i].lock(); } }; module.exports = ZipFileWorker; },{"../crc32":4,"../signature":20,"../stream/GenericWorker":25,"../utf8":28,"../utils":29}],9:[function(require,module,exports){ var compressions = require('../compressions'); var ZipFileWorker = require('./ZipFileWorker'); /** * Find the compression to use. * @param {String} fileCompression the compression defined at the file level, if any. * @param {String} zipCompression the compression defined at the load() level. * @return {Object} the compression object to use. */ var getCompression = function (fileCompression, zipCompression) { var compressionName = fileCompression || zipCompression; var compression = compressions[compressionName]; if (!compression) { throw new Error(compressionName + " is not a valid compression method !"); } return compression; }; /** * Create a worker to generate a zip file. * @param {JSZip} zip the JSZip instance at the right root level. * @param {Object} options to generate the zip file. * @param {String} comment the comment to use. */ exports.generateWorker = function (zip, options, comment) { var zipFileWorker = new ZipFileWorker(options.streamFiles, comment, options.platform, options.encodeFileName); var entriesCount = 0; try { zip.forEach(function (relativePath, file) { entriesCount++; var compression = getCompression(file.options.compression, options.compression); var compressionOptions = file.options.compressionOptions || options.compressionOptions || {}; var dir = file.dir, date = file.date; file._compressWorker(compression, compressionOptions) .withStreamInfo("file", { name : relativePath, dir : dir, date : date, comment : file.comment || "", unixPermissions : file.unixPermissions, dosPermissions : file.dosPermissions }) .pipe(zipFileWorker); }); zipFileWorker.entriesCount = entriesCount; } catch (e) { zipFileWorker.error(e); } return zipFileWorker; }; },{"../compressions":3,"./ZipFileWorker":8}],10:[function(require,module,exports){ /** * Representation a of zip file in js * @constructor */ function JSZip() { // if this constructor is used without `new`, it adds `new` before itself: if(!(this instanceof JSZip)) { return new JSZip(); } if(arguments.length) { throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide."); } // object containing the files : // { // "folder/" : {...}, // "folder/data.txt" : {...} // } this.files = {}; this.comment = null; // Where we are in the hierarchy this.root = ""; this.clone = function() { var newObj = new JSZip(); for (var i in this) { if (typeof this[i] !== "function") { newObj[i] = this[i]; } } return newObj; }; } JSZip.prototype = require('./object'); JSZip.prototype.loadAsync = require('./load'); JSZip.support = require('./support'); JSZip.defaults = require('./defaults'); JSZip.loadAsync = function (content, options) { return new JSZip().loadAsync(content, options); }; JSZip.external = require("./external"); module.exports = JSZip; },{"./defaults":5,"./external":6,"./load":11,"./object":13,"./support":27}],11:[function(require,module,exports){ var utils = require('./utils'); var external = require("./external"); var utf8 = require('./utf8'); var utils = require('./utils'); var ZipEntries = require('./zipEntries'); var Crc32Probe = require('./stream/Crc32Probe'); var nodejsUtils = require("./nodejsUtils"); /** * Check the CRC32 of an entry. * @param {ZipEntry} zipEntry the zip entry to check. * @return {Promise} the result. */ function checkEntryCRC32(zipEntry) { return new external.Promise(function (resolve, reject) { var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe()); worker.on("error", function (e) { reject(e); }) .on("end", function () { if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) { reject(new Error("Corrupted zip : CRC32 mismatch")); } else { resolve(); } }) .resume(); }); } module.exports = function(data, options) { var zip = this; options = utils.extend(options || {}, { base64: false, checkCRC32: false, optimizedBinaryString: false, createFolders: false, decodeFileName: utf8.utf8decode }); if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { return external.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")); } return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64) .then(function(data) { var zipEntries = new ZipEntries(options); zipEntries.load(data); return zipEntries; }).then(function checkCRC32(zipEntries) { var promises = [external.Promise.resolve(zipEntries)]; var files = zipEntries.files; if (options.checkCRC32) { for (var i = 0; i < files.length; i++) { promises.push(checkEntryCRC32(files[i])); } } return external.Promise.all(promises); }).then(function addFiles(results) { var zipEntries = results.shift(); var files = zipEntries.files; for (var i = 0; i < files.length; i++) { var input = files[i]; zip.file(input.fileNameStr, input.decompressed, { binary: true, optimizedBinaryString: true, date: input.date, dir: input.dir, comment : input.fileCommentStr.length ? input.fileCommentStr : null, unixPermissions : input.unixPermissions, dosPermissions : input.dosPermissions, createFolders: options.createFolders }); } if (zipEntries.zipComment.length) { zip.comment = zipEntries.zipComment; } return zip; }); }; },{"./external":6,"./nodejsUtils":12,"./stream/Crc32Probe":22,"./utf8":28,"./utils":29,"./zipEntries":30}],12:[function(require,module,exports){ (function (Buffer){ module.exports = { /** * True if this is running in Nodejs, will be undefined in a browser. * In a browser, browserify won't include this file and the whole module * will be resolved an empty object. */ isNode : typeof Buffer !== "undefined", /** * Create a new nodejs Buffer. * @param {Object} data the data to pass to the constructor. * @param {String} encoding the encoding to use. * @return {Buffer} a new Buffer. */ newBuffer : function(data, encoding){ return new Buffer(data, encoding); }, /** * Find out if an object is a Buffer. * @param {Object} b the object to test. * @return {Boolean} true if the object is a Buffer, false otherwise. */ isBuffer : function(b){ return Buffer.isBuffer(b); }, isStream : function (obj) { return obj && typeof obj.on === "function" && typeof obj.pause === "function" && typeof obj.resume === "function"; } }; }).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)); },{}],13:[function(require,module,exports){ var utf8 = require('./utf8'); var utils = require('./utils'); var GenericWorker = require('./stream/GenericWorker'); var StreamHelper = require('./stream/StreamHelper'); var defaults = require('./defaults'); var CompressedObject = require('./compressedObject'); var ZipObject = require('./zipObject'); var generate = require("./generate"); var nodejsUtils = require("./nodejsUtils"); var NodejsStreamInputAdapter = require("./nodejs/NodejsStreamInputAdapter"); /** * Add a file in the current folder. * @private * @param {string} name the name of the file * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data of the file * @param {Object} o the options of the file * @return {Object} the new file. */ var fileAdd = function(name, data, o) { // be sure sub folders exist var dataType = utils.getTypeOf(data), parent; /* * Correct options. */ o = utils.extend(o || {}, defaults); o.date = o.date || new Date(); if (o.compression !== null) { o.compression = o.compression.toUpperCase(); } if (typeof o.unixPermissions === "string") { o.unixPermissions = parseInt(o.unixPermissions, 8); } // UNX_IFDIR 0040000 see zipinfo.c if (o.unixPermissions && (o.unixPermissions & 0x4000)) { o.dir = true; } // Bit 4 Directory if (o.dosPermissions && (o.dosPermissions & 0x0010)) { o.dir = true; } if (o.dir) { name = forceTrailingSlash(name); } if (o.createFolders && (parent = parentFolder(name))) { folderAdd.call(this, parent, true); } var isUnicodeString = dataType === "string" && o.binary === false && o.base64 === false; o.binary = !isUnicodeString; var isCompressedEmpty = (data instanceof CompressedObject) && data.uncompressedSize === 0; if (isCompressedEmpty || o.dir || !data || data.length === 0) { o.base64 = false; o.binary = true; data = ""; o.compression = "STORE"; dataType = "string"; } /* * Convert content to fit. */ var zipObjectContent = null; if (data instanceof CompressedObject || data instanceof GenericWorker) { zipObjectContent = data; } else if (nodejsUtils.isNode && nodejsUtils.isStream(data)) { zipObjectContent = new NodejsStreamInputAdapter(name, data); } else { zipObjectContent = utils.prepareContent(name, data, o.binary, o.optimizedBinaryString, o.base64); } var object = new ZipObject(name, zipObjectContent, o); this.files[name] = object; /* TODO: we can't throw an exception because we have async promises (we can have a promise of a Date() for example) but returning a promise is useless because file(name, data) returns the JSZip object for chaining. Should we break that to allow the user to catch the error ? return external.Promise.resolve(zipObjectContent) .then(function () { return object; }); */ }; /** * Find the parent folder of the path. * @private * @param {string} path the path to use * @return {string} the parent folder, or "" */ var parentFolder = function (path) { if (path.slice(-1) === '/') { path = path.substring(0, path.length - 1); } var lastSlash = path.lastIndexOf('/'); return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; }; /** * Returns the path with a slash at the end. * @private * @param {String} path the path to check. * @return {String} the path with a trailing slash. */ var forceTrailingSlash = function(path) { // Check the name ends with a / if (path.slice(-1) !== "/") { path += "/"; // IE doesn't like substr(-1) } return path; }; /** * Add a (sub) folder in the current folder. * @private * @param {string} name the folder's name * @param {boolean=} [createFolders] If true, automatically create sub * folders. Defaults to false. * @return {Object} the new folder. */ var folderAdd = function(name, createFolders) { createFolders = (typeof createFolders !== 'undefined') ? createFolders : defaults.createFolders; name = forceTrailingSlash(name); // Does this folder already exist? if (!this.files[name]) { fileAdd.call(this, name, null, { dir: true, createFolders: createFolders }); } return this.files[name]; }; /** * Cross-window, cross-Node-context regular expression detection * @param {Object} object Anything * @return {Boolean} true if the object is a regular expression, * false otherwise */ function isRegExp(object) { return Object.prototype.toString.call(object) === "[object RegExp]"; } // return the actual prototype of JSZip var out = { /** * @see loadAsync */ load: function() { throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); }, /** * Call a callback function for each entry at this folder level. * @param {Function} cb the callback function: * function (relativePath, file) {...} * It takes 2 arguments : the relative path and the file. */ forEach: function(cb) { var filename, relativePath, file; for (filename in this.files) { if (!this.files.hasOwnProperty(filename)) { continue; } file = this.files[filename]; relativePath = filename.slice(this.root.length, filename.length); if (relativePath && filename.slice(0, this.root.length) === this.root) { // the file is in the current root cb(relativePath, file); // TODO reverse the parameters ? need to be clean AND consistent with the filter search fn... } } }, /** * Filter nested files/folders with the specified function. * @param {Function} search the predicate to use : * function (relativePath, file) {...} * It takes 2 arguments : the relative path and the file. * @return {Array} An array of matching elements. */ filter: function(search) { var result = []; this.forEach(function (relativePath, entry) { if (search(relativePath, entry)) { // the file matches the function result.push(entry); } }); return result; }, /** * Add a file to the zip file, or search a file. * @param {string|RegExp} name The name of the file to add (if data is defined), * the name of the file to find (if no data) or a regex to match files. * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded * @param {Object} o File options * @return {JSZip|Object|Array} this JSZip object (when adding a file), * a file (when searching by string) or an array of files (when searching by regex). */ file: function(name, data, o) { if (arguments.length === 1) { if (isRegExp(name)) { var regexp = name; return this.filter(function(relativePath, file) { return !file.dir && regexp.test(relativePath); }); } else { // text var obj = this.files[this.root + name]; if (obj && !obj.dir) { return obj; } else { return null; } } } else { // more than one argument : we have data ! name = this.root + name; fileAdd.call(this, name, data, o); } return this; }, /** * Add a directory to the zip file, or search. * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. */ folder: function(arg) { if (!arg) { return this; } if (isRegExp(arg)) { return this.filter(function(relativePath, file) { return file.dir && arg.test(relativePath); }); } // else, name is a new folder var name = this.root + arg; var newFolder = folderAdd.call(this, name); // Allow chaining by returning a new object with this folder as the root var ret = this.clone(); ret.root = newFolder.name; return ret; }, /** * Delete a file, or a directory and all sub-files, from the zip * @param {string} name the name of the file to delete * @return {JSZip} this JSZip object */ remove: function(name) { name = this.root + name; var file = this.files[name]; if (!file) { // Look for any folders if (name.slice(-1) !== "/") { name += "/"; } file = this.files[name]; } if (file && !file.dir) { // file delete this.files[name]; } else { // maybe a folder, delete recursively var kids = this.filter(function(relativePath, file) { return file.name.slice(0, name.length) === name; }); for (var i = 0; i < kids.length; i++) { delete this.files[kids[i].name]; } } return this; }, /** * Generate the complete zip file * @param {Object} options the options to generate the zip file : * - compression, "STORE" by default. * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file */ generate: function(options) { throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); }, /** * Generate the complete zip file as an internal stream. * @param {Object} options the options to generate the zip file : * - compression, "STORE" by default. * - type, "base64" by default. Values are : string, base64, uint8array, arraybuffer, blob. * @return {StreamHelper} the streamed zip file. */ generateInternalStream: function(options) { var worker, opts = {}; try { opts = utils.extend(options || {}, { streamFiles: false, compression: "STORE", compressionOptions : null, type: "", platform: "DOS", comment: null, mimeType: 'application/zip', encodeFileName: utf8.utf8encode }); opts.type = opts.type.toLowerCase(); opts.compression = opts.compression.toUpperCase(); // "binarystring" is prefered but the internals use "string". if(opts.type === "binarystring") { opts.type = "string"; } if (!opts.type) { throw new Error("No output type specified."); } utils.checkSupport(opts.type); // accept nodejs `process.platform` if( options.platform === 'darwin' || options.platform === 'freebsd' || options.platform === 'linux' || options.platform === 'sunos' ) { options.platform = "UNIX"; } if (options.platform === 'win32') { options.platform = "DOS"; } var comment = opts.comment || this.comment || ""; worker = generate.generateWorker(this, opts, comment); } catch (e) { worker = new GenericWorker("error"); worker.error(e); } return new StreamHelper(worker, opts.type || "string", opts.mimeType); }, /** * Generate the complete zip file asynchronously. * @see generateInternalStream */ generateAsync: function(options, onUpdate) { return this.generateInternalStream(options).accumulate(onUpdate); }, /** * Generate the complete zip file asynchronously. * @see generateInternalStream */ generateNodeStream: function(options, onUpdate) { options = options || {}; if (!options.type) { options.type = "nodebuffer"; } return this.generateInternalStream(options).toNodejsStream(onUpdate); } }; module.exports = out; },{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":35,"./nodejsUtils":12,"./stream/GenericWorker":25,"./stream/StreamHelper":26,"./utf8":28,"./utils":29,"./zipObject":32}],14:[function(require,module,exports){ var DataReader = require('./DataReader'); var utils = require('../utils'); function ArrayReader(data) { DataReader.call(this, data); for(var i = 0; i < this.data.length; i++) { data[i] = data[i] & 0xFF; } } utils.inherits(ArrayReader, DataReader); /** * @see DataReader.byteAt */ ArrayReader.prototype.byteAt = function(i) { return this.data[this.zero + i]; }; /** * @see DataReader.lastIndexOfSignature */ ArrayReader.prototype.lastIndexOfSignature = function(sig) { var sig0 = sig.charCodeAt(0), sig1 = sig.charCodeAt(1), sig2 = sig.charCodeAt(2), sig3 = sig.charCodeAt(3); for (var i = this.length - 4; i >= 0; --i) { if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { return i - this.zero; } } return -1; }; /** * @see DataReader.readAndCheckSignature */ ArrayReader.prototype.readAndCheckSignature = function (sig) { var sig0 = sig.charCodeAt(0), sig1 = sig.charCodeAt(1), sig2 = sig.charCodeAt(2), sig3 = sig.charCodeAt(3), data = this.readData(4); return sig0 === data[0] && sig1 === data[1] && sig2 === data[2] && sig3 === data[3]; }; /** * @see DataReader.readData */ ArrayReader.prototype.readData = function(size) { this.checkOffset(size); if(size === 0) { return []; } var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = ArrayReader; },{"../utils":29,"./DataReader":15}],15:[function(require,module,exports){ var utils = require('../utils'); function DataReader(data) { this.data = data; // type : see implementation this.length = data.length; this.index = 0; this.zero = 0; } DataReader.prototype = { /** * Check that the offset will not go too far. * @param {string} offset the additional offset to check. * @throws {Error} an Error if the offset is out of bounds. */ checkOffset: function(offset) { this.checkIndex(this.index + offset); }, /** * Check that the specifed index will not be too far. * @param {string} newIndex the index to check. * @throws {Error} an Error if the index is out of bounds. */ checkIndex: function(newIndex) { if (this.length < this.zero + newIndex || newIndex < 0) { throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?"); } }, /** * Change the index. * @param {number} newIndex The new index. * @throws {Error} if the new index is out of the data. */ setIndex: function(newIndex) { this.checkIndex(newIndex); this.index = newIndex; }, /** * Skip the next n bytes. * @param {number} n the number of bytes to skip. * @throws {Error} if the new index is out of the data. */ skip: function(n) { this.setIndex(this.index + n); }, /** * Get the byte at the specified index. * @param {number} i the index to use. * @return {number} a byte. */ byteAt: function(i) { // see implementations }, /** * Get the next number with a given byte size. * @param {number} size the number of bytes to read. * @return {number} the corresponding number. */ readInt: function(size) { var result = 0, i; this.checkOffset(size); for (i = this.index + size - 1; i >= this.index; i--) { result = (result << 8) + this.byteAt(i); } this.index += size; return result; }, /** * Get the next string with a given byte size. * @param {number} size the number of bytes to read. * @return {string} the corresponding string. */ readString: function(size) { return utils.transformTo("string", this.readData(size)); }, /** * Get raw data without conversion, bytes. * @param {number} size the number of bytes to read. * @return {Object} the raw data, implementation specific. */ readData: function(size) { // see implementations }, /** * Find the last occurence of a zip signature (4 bytes). * @param {string} sig the signature to find. * @return {number} the index of the last occurence, -1 if not found. */ lastIndexOfSignature: function(sig) { // see implementations }, /** * Read the signature (4 bytes) at the current position and compare it with sig. * @param {string} sig the expected signature * @return {boolean} true if the signature matches, false otherwise. */ readAndCheckSignature: function(sig) { // see implementations }, /** * Get the next date. * @return {Date} the date. */ readDate: function() { var dostime = this.readInt(4); return new Date(Date.UTC( ((dostime >> 25) & 0x7f) + 1980, // year ((dostime >> 21) & 0x0f) - 1, // month (dostime >> 16) & 0x1f, // day (dostime >> 11) & 0x1f, // hour (dostime >> 5) & 0x3f, // minute (dostime & 0x1f) << 1)); // second } }; module.exports = DataReader; },{"../utils":29}],16:[function(require,module,exports){ var Uint8ArrayReader = require('./Uint8ArrayReader'); var utils = require('../utils'); function NodeBufferReader(data) { Uint8ArrayReader.call(this, data); } utils.inherits(NodeBufferReader, Uint8ArrayReader); /** * @see DataReader.readData */ NodeBufferReader.prototype.readData = function(size) { this.checkOffset(size); var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = NodeBufferReader; },{"../utils":29,"./Uint8ArrayReader":18}],17:[function(require,module,exports){ var DataReader = require('./DataReader'); var utils = require('../utils'); function StringReader(data) { DataReader.call(this, data); } utils.inherits(StringReader, DataReader); /** * @see DataReader.byteAt */ StringReader.prototype.byteAt = function(i) { return this.data.charCodeAt(this.zero + i); }; /** * @see DataReader.lastIndexOfSignature */ StringReader.prototype.lastIndexOfSignature = function(sig) { return this.data.lastIndexOf(sig) - this.zero; }; /** * @see DataReader.readAndCheckSignature */ StringReader.prototype.readAndCheckSignature = function (sig) { var data = this.readData(4); return sig === data; }; /** * @see DataReader.readData */ StringReader.prototype.readData = function(size) { this.checkOffset(size); // this will work because the constructor applied the "& 0xff" mask. var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = StringReader; },{"../utils":29,"./DataReader":15}],18:[function(require,module,exports){ var ArrayReader = require('./ArrayReader'); var utils = require('../utils'); function Uint8ArrayReader(data) { ArrayReader.call(this, data); } utils.inherits(Uint8ArrayReader, ArrayReader); /** * @see DataReader.readData */ Uint8ArrayReader.prototype.readData = function(size) { this.checkOffset(size); if(size === 0) { // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of []. return new Uint8Array(0); } var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = Uint8ArrayReader; },{"../utils":29,"./ArrayReader":14}],19:[function(require,module,exports){ var utils = require('../utils'); var support = require('../support'); var ArrayReader = require('./ArrayReader'); var StringReader = require('./StringReader'); var NodeBufferReader = require('./NodeBufferReader'); var Uint8ArrayReader = require('./Uint8ArrayReader'); /** * Create a reader adapted to the data. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data to read. * @return {DataReader} the data reader. */ module.exports = function (data) { var type = utils.getTypeOf(data); utils.checkSupport(type); if (type === "string" && !support.uint8array) { return new StringReader(data); } if (type === "nodebuffer") { return new NodeBufferReader(data); } if (support.uint8array) { return new Uint8ArrayReader(utils.transformTo("uint8array", data)); } return new ArrayReader(utils.transformTo("array", data)); }; // vim: set shiftwidth=4 softtabstop=4: },{"../support":27,"../utils":29,"./ArrayReader":14,"./NodeBufferReader":16,"./StringReader":17,"./Uint8ArrayReader":18}],20:[function(require,module,exports){ exports.LOCAL_FILE_HEADER = "PK\x03\x04"; exports.CENTRAL_FILE_HEADER = "PK\x01\x02"; exports.CENTRAL_DIRECTORY_END = "PK\x05\x06"; exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07"; exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06"; exports.DATA_DESCRIPTOR = "PK\x07\x08"; },{}],21:[function(require,module,exports){ var GenericWorker = require('./GenericWorker'); var utils = require('../utils'); /** * A worker which convert chunks to a specified type. * @constructor * @param {String} destType the destination type. */ function ConvertWorker(destType) { GenericWorker.call(this, "ConvertWorker to " + destType); this.destType = destType; } utils.inherits(ConvertWorker, GenericWorker); /** * @see GenericWorker.processChunk */ ConvertWorker.prototype.processChunk = function (chunk) { this.push({ data : utils.transformTo(this.destType, chunk.data), meta : chunk.meta }); }; module.exports = ConvertWorker; },{"../utils":29,"./GenericWorker":25}],22:[function(require,module,exports){ var GenericWorker = require('./GenericWorker'); var crc32 = require('../crc32'); var utils = require('../utils'); /** * A worker which calculate the crc32 of the data flowing through. * @constructor */ function Crc32Probe() { GenericWorker.call(this, "Crc32Probe"); } utils.inherits(Crc32Probe, GenericWorker); /** * @see GenericWorker.processChunk */ Crc32Probe.prototype.processChunk = function (chunk) { this.streamInfo.crc32 = crc32(chunk.data, this.streamInfo.crc32 || 0); this.push(chunk); }; module.exports = Crc32Probe; },{"../crc32":4,"../utils":29,"./GenericWorker":25}],23:[function(require,module,exports){ var utils = require('../utils'); var GenericWorker = require('./GenericWorker'); /** * A worker which calculate the total length of the data flowing through. * @constructor * @param {String} propName the name used to expose the length */ function DataLengthProbe(propName) { GenericWorker.call(this, "DataLengthProbe for " + propName); this.propName = propName; this.withStreamInfo(propName, 0); } utils.inherits(DataLengthProbe, GenericWorker); /** * @see GenericWorker.processChunk */ DataLengthProbe.prototype.processChunk = function (chunk) { if(chunk) { var length = this.streamInfo[this.propName] || 0; this.streamInfo[this.propName] = length + chunk.data.length; } GenericWorker.prototype.processChunk.call(this, chunk); }; module.exports = DataLengthProbe; },{"../utils":29,"./GenericWorker":25}],24:[function(require,module,exports){ var utils = require('../utils'); var GenericWorker = require('./GenericWorker'); // the size of the generated chunks // TODO expose this as a public variable var DEFAULT_BLOCK_SIZE = 16 * 1024; /** * A worker that reads a content and emits chunks. * @constructor * @param {Promise} dataP the promise of the data to split */ function DataWorker(dataP) { GenericWorker.call(this, "DataWorker"); var self = this; this.dataIsReady = false; this.index = 0; this.max = 0; this.data = null; this.type = ""; this._tickScheduled = false; dataP.then(function (data) { self.dataIsReady = true; self.data = data; self.max = data && data.length || 0; self.type = utils.getTypeOf(data); if(!self.isPaused) { self._tickAndRepeat(); } }, function (e) { self.error(e); }); } utils.inherits(DataWorker, GenericWorker); /** * @see GenericWorker.cleanUp */ DataWorker.prototype.cleanUp = function () { GenericWorker.prototype.cleanUp.call(this); this.data = null; }; /** * @see GenericWorker.resume */ DataWorker.prototype.resume = function () { if(!GenericWorker.prototype.resume.call(this)) { return false; } if (!this._tickScheduled && this.dataIsReady) { this._tickScheduled = true; utils.delay(this._tickAndRepeat, [], this); } return true; }; /** * Trigger a tick a schedule an other call to this function. */ DataWorker.prototype._tickAndRepeat = function() { this._tickScheduled = false; if(this.isPaused || this.isFinished) { return; } this._tick(); if(!this.isFinished) { utils.delay(this._tickAndRepeat, [], this); this._tickScheduled = true; } }; /** * Read and push a chunk. */ DataWorker.prototype._tick = function() { if(this.isPaused || this.isFinished) { return false; } var size = DEFAULT_BLOCK_SIZE; var data = null, nextIndex = Math.min(this.max, this.index + size); if (this.index >= this.max) { // EOF return this.end(); } else { switch(this.type) { case "string": data = this.data.substring(this.index, nextIndex); break; case "uint8array": data = this.data.subarray(this.index, nextIndex); break; case "array": case "nodebuffer": data = this.data.slice(this.index, nextIndex); break; } this.index = nextIndex; return this.push({ data : data, meta : { percent : this.max ? this.index / this.max * 100 : 0 } }); } }; module.exports = DataWorker; },{"../utils":29,"./GenericWorker":25}],25:[function(require,module,exports){ /** * A worker that does nothing but passing chunks to the next one. This is like * a nodejs stream but with some differences. On the good side : * - it works on IE 6-9 without any issue / polyfill * - it weights less than the full dependencies bundled with browserify * - it forwards errors (no need to declare an error handler EVERYWHERE) * * A chunk is an object with 2 attributes : `meta` and `data`. The former is an * object containing anything (`percent` for example), see each worker for more * details. The latter is the real data (String, Uint8Array, etc). * * @constructor * @param {String} name the name of the stream (mainly used for debugging purposes) */ function GenericWorker(name) { // the name of the worker this.name = name || "default"; // an object containing metadata about the workers chain this.streamInfo = {}; // an error which happened when the worker was paused this.generatedError = null; // an object containing metadata to be merged by this worker into the general metadata this.extraStreamInfo = {}; // true if the stream is paused (and should not do anything), false otherwise this.isPaused = true; // true if the stream is finished (and should not do anything), false otherwise this.isFinished = false; // true if the stream is locked to prevent further structure updates (pipe), false otherwise this.isLocked = false; // the event listeners this._listeners = { 'data':[], 'end':[], 'error':[] }; // the previous worker, if any this.previous = null; } GenericWorker.prototype = { /** * Push a chunk to the next workers. * @param {Object} chunk the chunk to push */ push : function (chunk) { this.emit("data", chunk); }, /** * End the stream. * @return {Boolean} true if this call ended the worker, false otherwise. */ end : function () { if (this.isFinished) { return false; } this.flush(); try { this.emit("end"); this.cleanUp(); this.isFinished = true; } catch (e) { this.emit("error", e); } return true; }, /** * End the stream with an error. * @param {Error} e the error which caused the premature end. * @return {Boolean} true if this call ended the worker with an error, false otherwise. */ error : function (e) { if (this.isFinished) { return false; } if(this.isPaused) { this.generatedError = e; } else { this.isFinished = true; this.emit("error", e); // in the workers chain exploded in the middle of the chain, // the error event will go downward but we also need to notify // workers upward that there has been an error. if(this.previous) { this.previous.error(e); } this.cleanUp(); } return true; }, /** * Add a callback on an event. * @param {String} name the name of the event (data, end, error) * @param {Function} listener the function to call when the event is triggered * @return {GenericWorker} the current object for chainability */ on : function (name, listener) { this._listeners[name].push(listener); return this; }, /** * Clean any references when a worker is ending. */ cleanUp : function () { this.streamInfo = this.generatedError = this.extraStreamInfo = null; this._listeners = []; }, /** * Trigger an event. This will call registered callback with the provided arg. * @param {String} name the name of the event (data, end, error) * @param {Object} arg the argument to call the callback with. */ emit : function (name, arg) { if (this._listeners[name]) { for(var i = 0; i < this._listeners[name].length; i++) { this._listeners[name][i].call(this, arg); } } }, /** * Chain a worker with an other. * @param {Worker} next the worker receiving events from the current one. * @return {worker} the next worker for chainability */ pipe : function (next) { return next.registerPrevious(this); }, /** * Same as `pipe` in the other direction. * Using an API with `pipe(next)` is very easy. * Implementing the API with the point of view of the next one registering * a source is easier, see the ZipFileWorker. * @param {Worker} previous the previous worker, sending events to this one * @return {Worker} the current worker for chainability */ registerPrevious : function (previous) { if (this.isLocked) { throw new Error("The stream '" + this + "' has already been used."); } // sharing the streamInfo... this.streamInfo = previous.streamInfo; // ... and adding our own bits this.mergeStreamInfo(); this.previous = previous; var self = this; previous.on('data', function (chunk) { self.processChunk(chunk); }); previous.on('end', function () { self.end(); }); previous.on('error', function (e) { self.error(e); }); return this; }, /** * Pause the stream so it doesn't send events anymore. * @return {Boolean} true if this call paused the worker, false otherwise. */ pause : function () { if(this.isPaused || this.isFinished) { return false; } this.isPaused = true; if(this.previous) { this.previous.pause(); } return true; }, /** * Resume a paused stream. * @return {Boolean} true if this call resumed the worker, false otherwise. */ resume : function () { if(!this.isPaused || this.isFinished) { return false; } this.isPaused = false; // if true, the worker tried to resume but failed var withError = false; if(this.generatedError) { this.error(this.generatedError); withError = true; } if(this.previous) { this.previous.resume(); } return !withError; }, /** * Flush any remaining bytes as the stream is ending. */ flush : function () {}, /** * Process a chunk. This is usually the method overridden. * @param {Object} chunk the chunk to process. */ processChunk : function(chunk) { this.push(chunk); }, /** * Add a key/value to be added in the workers chain streamInfo once activated. * @param {String} key the key to use * @param {Object} value the associated value * @return {Worker} the current worker for chainability */ withStreamInfo : function (key, value) { this.extraStreamInfo[key] = value; this.mergeStreamInfo(); return this; }, /** * Merge this worker's streamInfo into the chain's streamInfo. */ mergeStreamInfo : function () { for(var key in this.extraStreamInfo) { if (!this.extraStreamInfo.hasOwnProperty(key)) { continue; } this.streamInfo[key] = this.extraStreamInfo[key]; } }, /** * Lock the stream to prevent further updates on the workers chain. * After calling this method, all calls to pipe will fail. */ lock: function () { if (this.isLocked) { throw new Error("The stream '" + this + "' has already been used."); } this.isLocked = true; if (this.previous) { this.previous.lock(); } }, /** * * Pretty print the workers chain. */ toString : function () { var me = "Worker " + this.name; if (this.previous) { return this.previous + " -> " + me; } else { return me; } } }; module.exports = GenericWorker; },{}],26:[function(require,module,exports){ (function (Buffer){ var utils = require('../utils'); var ConvertWorker = require('./ConvertWorker'); var GenericWorker = require('./GenericWorker'); var base64 = require('../base64'); var NodejsStreamOutputAdapter = require('../nodejs/NodejsStreamOutputAdapter'); var external = require("../external"); /** * Apply the final transformation of the data. If the user wants a Blob for * example, it's easier to work with an U8intArray and finally do the * ArrayBuffer/Blob conversion. * @param {String} type the name of the final type * @param {String|Uint8Array|Buffer} content the content to transform * @param {String} mimeType the mime type of the content, if applicable. * @return {String|Uint8Array|ArrayBuffer|Buffer|Blob} the content in the right format. */ function transformZipOutput(type, content, mimeType) { switch(type) { case "blob" : return utils.newBlob(utils.transformTo("arraybuffer", content), mimeType); case "base64" : return base64.encode(content); default : return utils.transformTo(type, content); } } /** * Concatenate an array of data of the given type. * @param {String} type the type of the data in the given array. * @param {Array} dataArray the array containing the data chunks to concatenate * @return {String|Uint8Array|Buffer} the concatenated data * @throws Error if the asked type is unsupported */ function concat (type, dataArray) { var i, index = 0, res = null, totalLength = 0; for(i = 0; i < dataArray.length; i++) { totalLength += dataArray[i].length; } switch(type) { case "string": return dataArray.join(""); case "array": return Array.prototype.concat.apply([], dataArray); case "uint8array": res = new Uint8Array(totalLength); for(i = 0; i < dataArray.length; i++) { res.set(dataArray[i], index); index += dataArray[i].length; } return res; case "nodebuffer": return Buffer.concat(dataArray); default: throw new Error("concat : unsupported type '" + type + "'"); } } /** * Listen a StreamHelper, accumulate its content and concatenate it into a * complete block. * @param {StreamHelper} helper the helper to use. * @param {Function} updateCallback a callback called on each update. Called * with one arg : * - the metadata linked to the update received. * @return Promise the promise for the accumulation. */ function accumulate(helper, updateCallback) { return new external.Promise(function (resolve, reject){ var dataArray = []; var chunkType = helper._internalType, resultType = helper._outputType, mimeType = helper._mimeType; helper .on('data', function (data, meta) { dataArray.push(data); if(updateCallback) { updateCallback(meta); } }) .on('error', function(err) { dataArray = []; reject(err); }) .on('end', function (){ try { var result = transformZipOutput(resultType, concat(chunkType, dataArray), mimeType); resolve(result); } catch (e) { reject(e); } dataArray = []; }) .resume(); }); } /** * An helper to easily use workers outside of JSZip. * @constructor * @param {Worker} worker the worker to wrap * @param {String} outputType the type of data expected by the use * @param {String} mimeType the mime type of the content, if applicable. */ function StreamHelper(worker, outputType, mimeType) { var internalType = outputType; switch(outputType) { case "blob": case "arraybuffer": internalType = "uint8array"; break; case "base64": internalType = "string"; break; } try { // the type used internally this._internalType = internalType; // the type used to output results this._outputType = outputType; // the mime type this._mimeType = mimeType; utils.checkSupport(internalType); this._worker = worker.pipe(new ConvertWorker(internalType)); // the last workers can be rewired without issues but we need to // prevent any updates on previous workers. worker.lock(); } catch(e) { this._worker = new GenericWorker("error"); this._worker.error(e); } } StreamHelper.prototype = { /** * Listen a StreamHelper, accumulate its content and concatenate it into a * complete block. * @param {Function} updateCb the update callback. * @return Promise the promise for the accumulation. */ accumulate : function (updateCb) { return accumulate(this, updateCb); }, /** * Add a listener on an event triggered on a stream. * @param {String} evt the name of the event * @param {Function} fn the listener * @return {StreamHelper} the current helper. */ on : function (evt, fn) { var self = this; if(evt === "data") { this._worker.on(evt, function (chunk) { fn.call(self, chunk.data, chunk.meta); }); } else { this._worker.on(evt, function () { utils.delay(fn, arguments, self); }); } return this; }, /** * Resume the flow of chunks. * @return {StreamHelper} the current helper. */ resume : function () { utils.delay(this._worker.resume, [], this._worker); return this; }, /** * Pause the flow of chunks. * @return {StreamHelper} the current helper. */ pause : function () { this._worker.pause(); return this; }, /** * Return a nodejs stream for this helper. * @param {Function} updateCb the update callback. * @return {NodejsStreamOutputAdapter} the nodejs stream. */ toNodejsStream : function (updateCb) { utils.checkSupport("nodestream"); if (this._outputType !== "nodebuffer") { // an object stream containing blob/arraybuffer/uint8array/string // is strange and I don't know if it would be useful. // I you find this comment and have a good usecase, please open a // bug report ! throw new Error(this._outputType + " is not supported by this method"); } return new NodejsStreamOutputAdapter(this, { objectMode : this._outputType !== "nodebuffer" }, updateCb); } }; module.exports = StreamHelper; }).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)); },{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":35,"../utils":29,"./ConvertWorker":21,"./GenericWorker":25}],27:[function(require,module,exports){ (function (Buffer){ exports.base64 = true; exports.array = true; exports.string = true; exports.arraybuffer = typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"; exports.nodebuffer = typeof Buffer !== "undefined"; // contains true if JSZip can read/generate Uint8Array, false otherwise. exports.uint8array = typeof Uint8Array !== "undefined"; if (typeof ArrayBuffer === "undefined") { exports.blob = false; } else { var buffer = new ArrayBuffer(0); try { exports.blob = new Blob([buffer], { type: "application/zip" }).size === 0; } catch (e) { try { var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; var builder = new Builder(); builder.append(buffer); exports.blob = builder.getBlob('application/zip').size === 0; } catch (e) { exports.blob = false; } } } exports.nodestream = !!require("./nodejs/NodejsStreamOutputAdapter").prototype; }).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)); },{"./nodejs/NodejsStreamOutputAdapter":35}],28:[function(require,module,exports){ var utils = require('./utils'); var support = require('./support'); var nodejsUtils = require('./nodejsUtils'); var GenericWorker = require('./stream/GenericWorker'); /** * The following functions come from pako, from pako/lib/utils/strings * released under the MIT license, see pako https://github.com/nodeca/pako/ */ // Table with utf8 lengths (calculated by first byte of sequence) // Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, // because max possible codepoint is 0x10ffff var _utf8len = new Array(256); for (var i=0; i<256; i++) { _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); } _utf8len[254]=_utf8len[254]=1; // Invalid sequence start // convert string to array (typed, when possible) var string2buf = function (str) { var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; // count binary size for (m_pos = 0; m_pos < str_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { c2 = str.charCodeAt(m_pos+1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; } // allocate buffer if (support.uint8array) { buf = new Uint8Array(buf_len); } else { buf = new Array(buf_len); } // convert for (i=0, m_pos = 0; i < buf_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { c2 = str.charCodeAt(m_pos+1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } if (c < 0x80) { /* one byte */ buf[i++] = c; } else if (c < 0x800) { /* two bytes */ buf[i++] = 0xC0 | (c >>> 6); buf[i++] = 0x80 | (c & 0x3f); } else if (c < 0x10000) { /* three bytes */ buf[i++] = 0xE0 | (c >>> 12); buf[i++] = 0x80 | (c >>> 6 & 0x3f); buf[i++] = 0x80 | (c & 0x3f); } else { /* four bytes */ buf[i++] = 0xf0 | (c >>> 18); buf[i++] = 0x80 | (c >>> 12 & 0x3f); buf[i++] = 0x80 | (c >>> 6 & 0x3f); buf[i++] = 0x80 | (c & 0x3f); } } return buf; }; // Calculate max possible position in utf8 buffer, // that will not break sequence. If that's not possible // - (very small limits) return max size as is. // // buf[] - utf8 bytes array // max - length limit (mandatory); var utf8border = function(buf, max) { var pos; max = max || buf.length; if (max > buf.length) { max = buf.length; } // go back from last position, until start of sequence found pos = max-1; while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } // Fuckup - very small and broken sequence, // return max, because we should return something anyway. if (pos < 0) { return max; } // If we came to start of buffer - that means vuffer is too small, // return max too. if (pos === 0) { return max; } return (pos + _utf8len[buf[pos]] > max) ? pos : max; }; // convert array to string var buf2string = function (buf) { var i, out, c, c_len; var len = buf.length; // Reserve max possible length (2 words per char) // NB: by unknown reasons, Array is significantly faster for // String.fromCharCode.apply than Uint16Array. var utf16buf = new Array(len*2); for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } // apply mask on first byte c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; // join the rest while (c_len > 1 && i < len) { c = (c << 6) | (buf[i++] & 0x3f); c_len--; } // terminated by end of string? if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } if (c < 0x10000) { utf16buf[out++] = c; } else { c -= 0x10000; utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); utf16buf[out++] = 0xdc00 | (c & 0x3ff); } } // shrinkBuf(utf16buf, out) if (utf16buf.length !== out) { if(utf16buf.subarray) { utf16buf = utf16buf.subarray(0, out); } else { utf16buf.length = out; } } // return String.fromCharCode.apply(null, utf16buf); return utils.applyFromCharCode(utf16buf); }; // That's all for the pako functions. /** * Transform a javascript string into an array (typed if possible) of bytes, * UTF-8 encoded. * @param {String} str the string to encode * @return {Array|Uint8Array|Buffer} the UTF-8 encoded string. */ exports.utf8encode = function utf8encode(str) { if (support.nodebuffer) { return nodejsUtils.newBuffer(str, "utf-8"); } return string2buf(str); }; /** * Transform a bytes array (or a representation) representing an UTF-8 encoded * string into a javascript string. * @param {Array|Uint8Array|Buffer} buf the data de decode * @return {String} the decoded string. */ exports.utf8decode = function utf8decode(buf) { if (support.nodebuffer) { return utils.transformTo("nodebuffer", buf).toString("utf-8"); } buf = utils.transformTo(support.uint8array ? "uint8array" : "array", buf); return buf2string(buf); }; /** * A worker to decode utf8 encoded binary chunks into string chunks. * @constructor */ function Utf8DecodeWorker() { GenericWorker.call(this, "utf-8 decode"); // the last bytes if a chunk didn't end with a complete codepoint. this.leftOver = null; } utils.inherits(Utf8DecodeWorker, GenericWorker); /** * @see GenericWorker.processChunk */ Utf8DecodeWorker.prototype.processChunk = function (chunk) { var data = utils.transformTo(support.uint8array ? "uint8array" : "array", chunk.data); // 1st step, re-use what's left of the previous chunk if (this.leftOver && this.leftOver.length) { if(support.uint8array) { var previousData = data; data = new Uint8Array(previousData.length + this.leftOver.length); data.set(this.leftOver, 0); data.set(previousData, this.leftOver.length); } else { data = this.leftOver.concat(data); } this.leftOver = null; } var nextBoundary = utf8border(data); var usableData = data; if (nextBoundary !== data.length) { if (support.uint8array) { usableData = data.subarray(0, nextBoundary); this.leftOver = data.subarray(nextBoundary, data.length); } else { usableData = data.slice(0, nextBoundary); this.leftOver = data.slice(nextBoundary, data.length); } } this.push({ data : exports.utf8decode(usableData), meta : chunk.meta }); }; /** * @see GenericWorker.flush */ Utf8DecodeWorker.prototype.flush = function () { if(this.leftOver && this.leftOver.length) { this.push({ data : exports.utf8decode(this.leftOver), meta : {} }); this.leftOver = null; } }; exports.Utf8DecodeWorker = Utf8DecodeWorker; /** * A worker to endcode string chunks into utf8 encoded binary chunks. * @constructor */ function Utf8EncodeWorker() { GenericWorker.call(this, "utf-8 encode"); } utils.inherits(Utf8EncodeWorker, GenericWorker); /** * @see GenericWorker.processChunk */ Utf8EncodeWorker.prototype.processChunk = function (chunk) { this.push({ data : exports.utf8encode(chunk.data), meta : chunk.meta }); }; exports.Utf8EncodeWorker = Utf8EncodeWorker; },{"./nodejsUtils":12,"./stream/GenericWorker":25,"./support":27,"./utils":29}],29:[function(require,module,exports){ var support = require('./support'); var base64 = require('./base64'); var nodejsUtils = require('./nodejsUtils'); var asap = require('asap'); var external = require("./external"); /** * Convert a string that pass as a "binary string": it should represent a byte * array but may have > 255 char codes. Be sure to take only the first byte * and returns the byte array. * @param {String} str the string to transform. * @return {Array|Uint8Array} the string in a binary format. */ function string2binary(str) { var result = null; if (support.uint8array) { result = new Uint8Array(str.length); } else { result = new Array(str.length); } return stringToArrayLike(str, result); } /** * Create a new blob with the given content and the given type. * @param {String|ArrayBuffer} part the content to put in the blob. DO NOT use * an Uint8Array because the stock browser of android 4 won't accept it (it * will be silently converted to a string, "[object Uint8Array]"). * @param {String} type the mime type of the blob. * @return {Blob} the created blob. */ exports.newBlob = function(part, type) { exports.checkSupport("blob"); try { // Blob constructor return new Blob([part], { type: type }); } catch (e) { try { // deprecated, browser only, old way var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; var builder = new Builder(); builder.append(part); return builder.getBlob(type); } catch (e) { // well, fuck ?! throw new Error("Bug : can't construct the Blob."); } } }; /** * The identity function. * @param {Object} input the input. * @return {Object} the same input. */ function identity(input) { return input; } /** * Fill in an array with a string. * @param {String} str the string to use. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to fill in (will be mutated). * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated array. */ function stringToArrayLike(str, array) { for (var i = 0; i < str.length; ++i) { array[i] = str.charCodeAt(i) & 0xFF; } return array; } /** * An helper for the function arrayLikeToString. * This contains static informations and functions that * can be optimized by the browser JIT compiler. */ var arrayToStringHelper = { /** * Transform an array of int into a string, chunk by chunk. * See the performances notes on arrayLikeToString. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. * @param {String} type the type of the array. * @param {Integer} chunk the chunk size. * @return {String} the resulting string. * @throws Error if the chunk is too big for the stack. */ stringifyByChunk: function(array, type, chunk) { var result = [], k = 0, len = array.length; // shortcut if (len <= chunk) { return String.fromCharCode.apply(null, array); } while (k < len) { if (type === "array" || type === "nodebuffer") { result.push(String.fromCharCode.apply(null, array.slice(k, Math.min(k + chunk, len)))); } else { result.push(String.fromCharCode.apply(null, array.subarray(k, Math.min(k + chunk, len)))); } k += chunk; } return result.join(""); }, /** * Call String.fromCharCode on every item in the array. * This is the naive implementation, which generate A LOT of intermediate string. * This should be used when everything else fail. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. * @return {String} the result. */ stringifyByChar: function(array){ var resultStr = ""; for(var i = 0; i < array.length; i++) { resultStr += String.fromCharCode(array[i]); } return resultStr; }, applyCanBeUsed : { /** * true if the browser accepts to use String.fromCharCode on Uint8Array */ uint8array : (function () { try { return support.uint8array && String.fromCharCode.apply(null, new Uint8Array(1)).length === 1; } catch (e) { return false; } })(), /** * true if the browser accepts to use String.fromCharCode on nodejs Buffer. */ nodebuffer : (function () { try { return support.nodebuffer && String.fromCharCode.apply(null, nodejsUtils.newBuffer(1)).length === 1; } catch (e) { return false; } })() } }; /** * Transform an array-like object to a string. * @param {Array|ArrayBuffer|Uint8Array|Buffer} array the array to transform. * @return {String} the result. */ function arrayLikeToString(array) { // Performances notes : // -------------------- // String.fromCharCode.apply(null, array) is the fastest, see // see http://jsperf.com/converting-a-uint8array-to-a-string/2 // but the stack is limited (and we can get huge arrays !). // // result += String.fromCharCode(array[i]); generate too many strings ! // // This code is inspired by http://jsperf.com/arraybuffer-to-string-apply-performance/2 // TODO : we now have workers that split the work. Do we still need that ? var chunk = 65536, type = exports.getTypeOf(array), canUseApply = true; if (type === "uint8array") { canUseApply = arrayToStringHelper.applyCanBeUsed.uint8array; } else if (type === "nodebuffer") { canUseApply = arrayToStringHelper.applyCanBeUsed.nodebuffer; } if (canUseApply) { while (chunk > 1) { try { return arrayToStringHelper.stringifyByChunk(array, type, chunk); } catch (e) { chunk = Math.floor(chunk / 2); } } } // no apply or chunk error : slow and painful algorithm // default browser on android 4.* return arrayToStringHelper.stringifyByChar(array); } exports.applyFromCharCode = arrayLikeToString; /** * Copy the data from an array-like to an other array-like. * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayFrom the origin array. * @param {Array|ArrayBuffer|Uint8Array|Buffer} arrayTo the destination array which will be mutated. * @return {Array|ArrayBuffer|Uint8Array|Buffer} the updated destination array. */ function arrayLikeToArrayLike(arrayFrom, arrayTo) { for (var i = 0; i < arrayFrom.length; i++) { arrayTo[i] = arrayFrom[i]; } return arrayTo; } // a matrix containing functions to transform everything into everything. var transform = {}; // string to ? transform["string"] = { "string": identity, "array": function(input) { return stringToArrayLike(input, new Array(input.length)); }, "arraybuffer": function(input) { return transform["string"]["uint8array"](input).buffer; }, "uint8array": function(input) { return stringToArrayLike(input, new Uint8Array(input.length)); }, "nodebuffer": function(input) { return stringToArrayLike(input, nodejsUtils.newBuffer(input.length)); } }; // array to ? transform["array"] = { "string": arrayLikeToString, "array": identity, "arraybuffer": function(input) { return (new Uint8Array(input)).buffer; }, "uint8array": function(input) { return new Uint8Array(input); }, "nodebuffer": function(input) { return nodejsUtils.newBuffer(input); } }; // arraybuffer to ? transform["arraybuffer"] = { "string": function(input) { return arrayLikeToString(new Uint8Array(input)); }, "array": function(input) { return arrayLikeToArrayLike(new Uint8Array(input), new Array(input.byteLength)); }, "arraybuffer": identity, "uint8array": function(input) { return new Uint8Array(input); }, "nodebuffer": function(input) { return nodejsUtils.newBuffer(new Uint8Array(input)); } }; // uint8array to ? transform["uint8array"] = { "string": arrayLikeToString, "array": function(input) { return arrayLikeToArrayLike(input, new Array(input.length)); }, "arraybuffer": function(input) { return input.buffer; }, "uint8array": identity, "nodebuffer": function(input) { return nodejsUtils.newBuffer(input); } }; // nodebuffer to ? transform["nodebuffer"] = { "string": arrayLikeToString, "array": function(input) { return arrayLikeToArrayLike(input, new Array(input.length)); }, "arraybuffer": function(input) { return transform["nodebuffer"]["uint8array"](input).buffer; }, "uint8array": function(input) { return arrayLikeToArrayLike(input, new Uint8Array(input.length)); }, "nodebuffer": identity }; /** * Transform an input into any type. * The supported output type are : string, array, uint8array, arraybuffer, nodebuffer. * If no output type is specified, the unmodified input will be returned. * @param {String} outputType the output type. * @param {String|Array|ArrayBuffer|Uint8Array|Buffer} input the input to convert. * @throws {Error} an Error if the browser doesn't support the requested output type. */ exports.transformTo = function(outputType, input) { if (!input) { // undefined, null, etc // an empty string won't harm. input = ""; } if (!outputType) { return input; } exports.checkSupport(outputType); var inputType = exports.getTypeOf(input); var result = transform[inputType][outputType](input); return result; }; /** * Return the type of the input. * The type will be in a format valid for JSZip.utils.transformTo : string, array, uint8array, arraybuffer. * @param {Object} input the input to identify. * @return {String} the (lowercase) type of the input. */ exports.getTypeOf = function(input) { if (typeof input === "string") { return "string"; } if (Object.prototype.toString.call(input) === "[object Array]") { return "array"; } if (support.nodebuffer && nodejsUtils.isBuffer(input)) { return "nodebuffer"; } if (support.uint8array && input instanceof Uint8Array) { return "uint8array"; } if (support.arraybuffer && input instanceof ArrayBuffer) { return "arraybuffer"; } }; /** * Throw an exception if the type is not supported. * @param {String} type the type to check. * @throws {Error} an Error if the browser doesn't support the requested type. */ exports.checkSupport = function(type) { var supported = support[type.toLowerCase()]; if (!supported) { throw new Error(type + " is not supported by this platform"); } }; exports.MAX_VALUE_16BITS = 65535; exports.MAX_VALUE_32BITS = -1; // well, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" is parsed as -1 /** * Prettify a string read as binary. * @param {string} str the string to prettify. * @return {string} a pretty string. */ exports.pretty = function(str) { var res = '', code, i; for (i = 0; i < (str || "").length; i++) { code = str.charCodeAt(i); res += '\\x' + (code < 16 ? "0" : "") + code.toString(16).toUpperCase(); } return res; }; /** * Defer the call of a function. * @param {Function} callback the function to call asynchronously. * @param {Array} args the arguments to give to the callback. */ exports.delay = function(callback, args, self) { asap(function () { callback.apply(self || null, args || []); }); }; /** * Extends a prototype with an other, without calling a constructor with * side effects. Inspired by nodejs' `utils.inherits` * @param {Function} ctor the constructor to augment * @param {Function} superCtor the parent constructor to use */ exports.inherits = function (ctor, superCtor) { var Obj = function() {}; Obj.prototype = superCtor.prototype; ctor.prototype = new Obj(); }; /** * Merge the objects passed as parameters into a new one. * @private * @param {...Object} var_args All objects to merge. * @return {Object} a new object with the data of the others. */ exports.extend = function() { var result = {}, i, attr; for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers for (attr in arguments[i]) { if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { result[attr] = arguments[i][attr]; } } } return result; }; /** * Transform arbitrary content into a Promise. * @param {String} name a name for the content being processed. * @param {Object} inputData the content to process. * @param {Boolean} isBinary true if the content is not an unicode string * @param {Boolean} isOptimizedBinaryString true if the string content only has one byte per character. * @param {Boolean} isBase64 true if the string content is encoded with base64. * @return {Promise} a promise in a format usable by JSZip. */ exports.prepareContent = function(name, inputData, isBinary, isOptimizedBinaryString, isBase64) { var promise = null; if (support.blob && inputData instanceof Blob && typeof FileReader !== "undefined") { promise = new external.Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function(e) { resolve(e.target.result); }; reader.onerror = function(e) { reject(e.target.error); }; reader.readAsArrayBuffer(inputData); }); } else { // if data is already a promise, this flatten it. promise = external.Promise.resolve(inputData); } return promise.then(function(data) { var dataType = exports.getTypeOf(data); if (!dataType) { return external.Promise.reject( new Error("The data of '" + name + "' is in an unsupported format !") ); } // special case : it's way easier to work with Uint8Array than with ArrayBuffer if (dataType === "arraybuffer") { data = exports.transformTo("uint8array", data); } else if (dataType === "string") { if (isBase64) { data = base64.decode(data); } else if (isBinary) { // optimizedBinaryString === true means that the file has already been filtered with a 0xFF mask if (isOptimizedBinaryString !== true) { // this is a string, not in a base64 format. // Be sure that this is a correct "binary string" data = string2binary(data); } } } return data; }); }; },{"./base64":1,"./external":6,"./nodejsUtils":12,"./support":27,"asap":33}],30:[function(require,module,exports){ var readerFor = require('./reader/readerFor'); var utils = require('./utils'); var sig = require('./signature'); var ZipEntry = require('./zipEntry'); require('./utf8'); var support = require('./support'); // class ZipEntries {{{ /** * All the entries in the zip file. * @constructor * @param {Object} loadOptions Options for loading the stream. */ function ZipEntries(loadOptions) { this.files = []; this.loadOptions = loadOptions; } ZipEntries.prototype = { /** * Check that the reader is on the speficied signature. * @param {string} expectedSignature the expected signature. * @throws {Error} if it is an other signature. */ checkSignature: function(expectedSignature) { if (!this.reader.readAndCheckSignature(expectedSignature)) { this.reader.index -= 4; var signature = this.reader.readString(4); throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")"); } }, /** * Check if the given signature is at the given index. * @param {number} askedIndex the index to check. * @param {string} expectedSignature the signature to expect. * @return {boolean} true if the signature is here, false otherwise. */ isSignature: function(askedIndex, expectedSignature) { var currentIndex = this.reader.index; this.reader.setIndex(askedIndex); var signature = this.reader.readString(4); var result = signature === expectedSignature; this.reader.setIndex(currentIndex); return result; }, /** * Read the end of the central directory. */ readBlockEndOfCentral: function() { this.diskNumber = this.reader.readInt(2); this.diskWithCentralDirStart = this.reader.readInt(2); this.centralDirRecordsOnThisDisk = this.reader.readInt(2); this.centralDirRecords = this.reader.readInt(2); this.centralDirSize = this.reader.readInt(4); this.centralDirOffset = this.reader.readInt(4); this.zipCommentLength = this.reader.readInt(2); // warning : the encoding depends of the system locale // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. // On a windows machine, this field is encoded with the localized windows code page. var zipComment = this.reader.readData(this.zipCommentLength); var decodeParamType = support.uint8array ? "uint8array" : "array"; // To get consistent behavior with the generation part, we will assume that // this is utf8 encoded unless specified otherwise. var decodeContent = utils.transformTo(decodeParamType, zipComment); this.zipComment = this.loadOptions.decodeFileName(decodeContent); }, /** * Read the end of the Zip 64 central directory. * Not merged with the method readEndOfCentral : * The end of central can coexist with its Zip64 brother, * I don't want to read the wrong number of bytes ! */ readBlockZip64EndOfCentral: function() { this.zip64EndOfCentralSize = this.reader.readInt(8); this.reader.skip(4); // this.versionMadeBy = this.reader.readString(2); // this.versionNeeded = this.reader.readInt(2); this.diskNumber = this.reader.readInt(4); this.diskWithCentralDirStart = this.reader.readInt(4); this.centralDirRecordsOnThisDisk = this.reader.readInt(8); this.centralDirRecords = this.reader.readInt(8); this.centralDirSize = this.reader.readInt(8); this.centralDirOffset = this.reader.readInt(8); this.zip64ExtensibleData = {}; var extraDataSize = this.zip64EndOfCentralSize - 44, index = 0, extraFieldId, extraFieldLength, extraFieldValue; while (index < extraDataSize) { extraFieldId = this.reader.readInt(2); extraFieldLength = this.reader.readInt(4); extraFieldValue = this.reader.readData(extraFieldLength); this.zip64ExtensibleData[extraFieldId] = { id: extraFieldId, length: extraFieldLength, value: extraFieldValue }; } }, /** * Read the end of the Zip 64 central directory locator. */ readBlockZip64EndOfCentralLocator: function() { this.diskWithZip64CentralDirStart = this.reader.readInt(4); this.relativeOffsetEndOfZip64CentralDir = this.reader.readInt(8); this.disksCount = this.reader.readInt(4); if (this.disksCount > 1) { throw new Error("Multi-volumes zip are not supported"); } }, /** * Read the local files, based on the offset read in the central part. */ readLocalFiles: function() { var i, file; for (i = 0; i < this.files.length; i++) { file = this.files[i]; this.reader.setIndex(file.localHeaderOffset); this.checkSignature(sig.LOCAL_FILE_HEADER); file.readLocalPart(this.reader); file.handleUTF8(); file.processAttributes(); } }, /** * Read the central directory. */ readCentralDir: function() { var file; this.reader.setIndex(this.centralDirOffset); while (this.reader.readAndCheckSignature(sig.CENTRAL_FILE_HEADER)) { file = new ZipEntry({ zip64: this.zip64 }, this.loadOptions); file.readCentralPart(this.reader); this.files.push(file); } if (this.centralDirRecords !== this.files.length) { if (this.centralDirRecords !== 0 && this.files.length === 0) { // We expected some records but couldn't find ANY. // This is really suspicious, as if something went wrong. throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length); } } }, /** * Read the end of central directory. */ readEndOfCentral: function() { var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END); if (offset < 0) { // Check if the content is a truncated zip or complete garbage. // A "LOCAL_FILE_HEADER" is not required at the beginning (auto // extractible zip for example) but it can give a good hint. // If an ajax request was used without responseType, we will also // get unreadable data. var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER); if (isGarbage) { throw new Error("Can't find end of central directory : is this a zip file ? " + "If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html"); } else { throw new Error("Corrupted zip : can't find end of central directory"); } } this.reader.setIndex(offset); var endOfCentralDirOffset = offset; this.checkSignature(sig.CENTRAL_DIRECTORY_END); this.readBlockEndOfCentral(); /* extract from the zip spec : 4) If one of the fields in the end of central directory record is too small to hold required data, the field should be set to -1 (0xFFFF or 0xFFFFFFFF) and the ZIP64 format record should be created. 5) The end of central directory record and the Zip64 end of central directory locator record must reside on the same disk when splitting or spanning an archive. */ if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) { this.zip64 = true; /* Warning : the zip64 extension is supported, but ONLY if the 64bits integer read from the zip file can fit into a 32bits integer. This cannot be solved : Javascript represents all numbers as 64-bit double precision IEEE 754 floating point numbers. So, we have 53bits for integers and bitwise operations treat everything as 32bits. see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators and http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf section 8.5 */ // should look for a zip64 EOCD locator offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); if (offset < 0) { throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator"); } this.reader.setIndex(offset); this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); this.readBlockZip64EndOfCentralLocator(); // now the zip64 EOCD record if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) { // console.warn("ZIP64 end of central directory not where expected."); this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); if (this.relativeOffsetEndOfZip64CentralDir < 0) { throw new Error("Corrupted zip : can't find the ZIP64 end of central directory"); } } this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); this.readBlockZip64EndOfCentral(); } var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize; if (this.zip64) { expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize; } var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset; if (extraBytes > 0) { // console.warn(extraBytes, "extra bytes at beginning or within zipfile"); if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) ; else { // the offset is wrong, update the "zero" of the reader // this happens if data has been prepended (crx files for example) this.reader.zero = extraBytes; } } else if (extraBytes < 0) { throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes."); } }, prepareReader: function(data) { this.reader = readerFor(data); }, /** * Read a zip file and create ZipEntries. * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file. */ load: function(data) { this.prepareReader(data); this.readEndOfCentral(); this.readCentralDir(); this.readLocalFiles(); } }; // }}} end of ZipEntries module.exports = ZipEntries; },{"./reader/readerFor":19,"./signature":20,"./support":27,"./utf8":28,"./utils":29,"./zipEntry":31}],31:[function(require,module,exports){ var readerFor = require('./reader/readerFor'); var utils = require('./utils'); var CompressedObject = require('./compressedObject'); var crc32fn = require('./crc32'); var utf8 = require('./utf8'); var compressions = require('./compressions'); var support = require('./support'); var MADE_BY_DOS = 0x00; var MADE_BY_UNIX = 0x03; /** * Find a compression registered in JSZip. * @param {string} compressionMethod the method magic to find. * @return {Object|null} the JSZip compression object, null if none found. */ var findCompression = function(compressionMethod) { for (var method in compressions) { if (!compressions.hasOwnProperty(method)) { continue; } if (compressions[method].magic === compressionMethod) { return compressions[method]; } } return null; }; // class ZipEntry {{{ /** * An entry in the zip file. * @constructor * @param {Object} options Options of the current file. * @param {Object} loadOptions Options for loading the stream. */ function ZipEntry(options, loadOptions) { this.options = options; this.loadOptions = loadOptions; } ZipEntry.prototype = { /** * say if the file is encrypted. * @return {boolean} true if the file is encrypted, false otherwise. */ isEncrypted: function() { // bit 1 is set return (this.bitFlag & 0x0001) === 0x0001; }, /** * say if the file has utf-8 filename/comment. * @return {boolean} true if the filename/comment is in utf-8, false otherwise. */ useUTF8: function() { // bit 11 is set return (this.bitFlag & 0x0800) === 0x0800; }, /** * Read the local part of a zip file and add the info in this object. * @param {DataReader} reader the reader to use. */ readLocalPart: function(reader) { var compression, localExtraFieldsLength; // we already know everything from the central dir ! // If the central dir data are false, we are doomed. // On the bright side, the local part is scary : zip64, data descriptors, both, etc. // The less data we get here, the more reliable this should be. // Let's skip the whole header and dash to the data ! reader.skip(22); // in some zip created on windows, the filename stored in the central dir contains \ instead of /. // Strangely, the filename here is OK. // I would love to treat these zip files as corrupted (see http://www.info-zip.org/FAQ.html#backslashes // or APPNOTE#4.4.17.1, "All slashes MUST be forward slashes '/'") but there are a lot of bad zip generators... // Search "unzip mismatching "local" filename continuing with "central" filename version" on // the internet. // // I think I see the logic here : the central directory is used to display // content and the local directory is used to extract the files. Mixing / and \ // may be used to display \ to windows users and use / when extracting the files. // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394 this.fileNameLength = reader.readInt(2); localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir // the fileName is stored as binary data, the handleUTF8 method will take care of the encoding. this.fileName = reader.readData(this.fileNameLength); reader.skip(localExtraFieldsLength); if (this.compressedSize === -1 || this.uncompressedSize === -1) { throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory " + "(compressedSize === -1 || uncompressedSize === -1)"); } compression = findCompression(this.compressionMethod); if (compression === null) { // no compression found throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")"); } this.decompressed = new CompressedObject(this.compressedSize, this.uncompressedSize, this.crc32, compression, reader.readData(this.compressedSize)); }, /** * Read the central part of a zip file and add the info in this object. * @param {DataReader} reader the reader to use. */ readCentralPart: function(reader) { this.versionMadeBy = reader.readInt(2); reader.skip(2); // this.versionNeeded = reader.readInt(2); this.bitFlag = reader.readInt(2); this.compressionMethod = reader.readString(2); this.date = reader.readDate(); this.crc32 = reader.readInt(4); this.compressedSize = reader.readInt(4); this.uncompressedSize = reader.readInt(4); var fileNameLength = reader.readInt(2); this.extraFieldsLength = reader.readInt(2); this.fileCommentLength = reader.readInt(2); this.diskNumberStart = reader.readInt(2); this.internalFileAttributes = reader.readInt(2); this.externalFileAttributes = reader.readInt(4); this.localHeaderOffset = reader.readInt(4); if (this.isEncrypted()) { throw new Error("Encrypted zip are not supported"); } // will be read in the local part, see the comments there reader.skip(fileNameLength); this.readExtraFields(reader); this.parseZIP64ExtraField(reader); this.fileComment = reader.readData(this.fileCommentLength); }, /** * Parse the external file attributes and get the unix/dos permissions. */ processAttributes: function () { this.unixPermissions = null; this.dosPermissions = null; var madeBy = this.versionMadeBy >> 8; // Check if we have the DOS directory flag set. // We look for it in the DOS and UNIX permissions // but some unknown platform could set it as a compatibility flag. this.dir = this.externalFileAttributes & 0x0010 ? true : false; if(madeBy === MADE_BY_DOS) { // first 6 bits (0 to 5) this.dosPermissions = this.externalFileAttributes & 0x3F; } if(madeBy === MADE_BY_UNIX) { this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF; // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8); } // fail safe : if the name ends with a / it probably means a folder if (!this.dir && this.fileNameStr.slice(-1) === '/') { this.dir = true; } }, /** * Parse the ZIP64 extra field and merge the info in the current ZipEntry. * @param {DataReader} reader the reader to use. */ parseZIP64ExtraField: function(reader) { if (!this.extraFields[0x0001]) { return; } // should be something, preparing the extra reader var extraReader = readerFor(this.extraFields[0x0001].value); // I really hope that these 64bits integer can fit in 32 bits integer, because js // won't let us have more. if (this.uncompressedSize === utils.MAX_VALUE_32BITS) { this.uncompressedSize = extraReader.readInt(8); } if (this.compressedSize === utils.MAX_VALUE_32BITS) { this.compressedSize = extraReader.readInt(8); } if (this.localHeaderOffset === utils.MAX_VALUE_32BITS) { this.localHeaderOffset = extraReader.readInt(8); } if (this.diskNumberStart === utils.MAX_VALUE_32BITS) { this.diskNumberStart = extraReader.readInt(4); } }, /** * Read the central part of a zip file and add the info in this object. * @param {DataReader} reader the reader to use. */ readExtraFields: function(reader) { var end = reader.index + this.extraFieldsLength, extraFieldId, extraFieldLength, extraFieldValue; if (!this.extraFields) { this.extraFields = {}; } while (reader.index < end) { extraFieldId = reader.readInt(2); extraFieldLength = reader.readInt(2); extraFieldValue = reader.readData(extraFieldLength); this.extraFields[extraFieldId] = { id: extraFieldId, length: extraFieldLength, value: extraFieldValue }; } }, /** * Apply an UTF8 transformation if needed. */ handleUTF8: function() { var decodeParamType = support.uint8array ? "uint8array" : "array"; if (this.useUTF8()) { this.fileNameStr = utf8.utf8decode(this.fileName); this.fileCommentStr = utf8.utf8decode(this.fileComment); } else { var upath = this.findExtraFieldUnicodePath(); if (upath !== null) { this.fileNameStr = upath; } else { // ASCII text or unsupported code page var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName); this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray); } var ucomment = this.findExtraFieldUnicodeComment(); if (ucomment !== null) { this.fileCommentStr = ucomment; } else { // ASCII text or unsupported code page var commentByteArray = utils.transformTo(decodeParamType, this.fileComment); this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray); } } }, /** * Find the unicode path declared in the extra field, if any. * @return {String} the unicode path, null otherwise. */ findExtraFieldUnicodePath: function() { var upathField = this.extraFields[0x7075]; if (upathField) { var extraReader = readerFor(upathField.value); // wrong version if (extraReader.readInt(1) !== 1) { return null; } // the crc of the filename changed, this field is out of date. if (crc32fn(this.fileName) !== extraReader.readInt(4)) { return null; } return utf8.utf8decode(extraReader.readData(upathField.length - 5)); } return null; }, /** * Find the unicode comment declared in the extra field, if any. * @return {String} the unicode comment, null otherwise. */ findExtraFieldUnicodeComment: function() { var ucommentField = this.extraFields[0x6375]; if (ucommentField) { var extraReader = readerFor(ucommentField.value); // wrong version if (extraReader.readInt(1) !== 1) { return null; } // the crc of the comment changed, this field is out of date. if (crc32fn(this.fileComment) !== extraReader.readInt(4)) { return null; } return utf8.utf8decode(extraReader.readData(ucommentField.length - 5)); } return null; } }; module.exports = ZipEntry; },{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":19,"./support":27,"./utf8":28,"./utils":29}],32:[function(require,module,exports){ var StreamHelper = require('./stream/StreamHelper'); var DataWorker = require('./stream/DataWorker'); var utf8 = require('./utf8'); var CompressedObject = require('./compressedObject'); var GenericWorker = require('./stream/GenericWorker'); /** * A simple object representing a file in the zip file. * @constructor * @param {string} name the name of the file * @param {String|ArrayBuffer|Uint8Array|Buffer} data the data * @param {Object} options the options of the file */ var ZipObject = function(name, data, options) { this.name = name; this.dir = options.dir; this.date = options.date; this.comment = options.comment; this.unixPermissions = options.unixPermissions; this.dosPermissions = options.dosPermissions; this._data = data; this._dataBinary = options.binary; // keep only the compression this.options = { compression : options.compression, compressionOptions : options.compressionOptions }; }; ZipObject.prototype = { /** * Create an internal stream for the content of this object. * @param {String} type the type of each chunk. * @return StreamHelper the stream. */ internalStream: function (type) { var outputType = type.toLowerCase(); var askUnicodeString = outputType === "string" || outputType === "text"; if (outputType === "binarystring" || outputType === "text") { outputType = "string"; } var result = this._decompressWorker(); var isUnicodeString = !this._dataBinary; if (isUnicodeString && !askUnicodeString) { result = result.pipe(new utf8.Utf8EncodeWorker()); } if (!isUnicodeString && askUnicodeString) { result = result.pipe(new utf8.Utf8DecodeWorker()); } return new StreamHelper(result, outputType, ""); }, /** * Prepare the content in the asked type. * @param {String} type the type of the result. * @param {Function} onUpdate a function to call on each internal update. * @return Promise the promise of the result. */ async: function (type, onUpdate) { return this.internalStream(type).accumulate(onUpdate); }, /** * Prepare the content as a nodejs stream. * @param {String} type the type of each chunk. * @param {Function} onUpdate a function to call on each internal update. * @return Stream the stream. */ nodeStream: function (type, onUpdate) { return this.internalStream(type || "nodebuffer").toNodejsStream(onUpdate); }, /** * Return a worker for the compressed content. * @private * @param {Object} compression the compression object to use. * @param {Object} compressionOptions the options to use when compressing. * @return Worker the worker. */ _compressWorker: function (compression, compressionOptions) { if ( this._data instanceof CompressedObject && this._data.compression.magic === compression.magic ) { return this._data.getCompressedWorker(); } else { var result = this._decompressWorker(); if(!this._dataBinary) { result = result.pipe(new utf8.Utf8EncodeWorker()); } return CompressedObject.createWorkerFrom(result, compression, compressionOptions); } }, /** * Return a worker for the decompressed content. * @private * @return Worker the worker. */ _decompressWorker : function () { if (this._data instanceof CompressedObject) { return this._data.getContentWorker(); } else if (this._data instanceof GenericWorker) { return this._data; } else { return new DataWorker(this._data); } } }; var removedMethods = ["asText", "asBinary", "asNodeBuffer", "asUint8Array", "asArrayBuffer"]; var removedFn = function () { throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide."); }; for(var i = 0; i < removedMethods.length; i++) { ZipObject.prototype[removedMethods[i]] = removedFn; } module.exports = ZipObject; },{"./compressedObject":2,"./stream/DataWorker":24,"./stream/GenericWorker":25,"./stream/StreamHelper":26,"./utf8":28}],33:[function(require,module,exports){ // rawAsap provides everything we need except exception management. var rawAsap = require("./raw"); // RawTasks are recycled to reduce GC churn. var freeTasks = []; // We queue errors to ensure they are thrown in right order (FIFO). // Array-as-queue is good enough here, since we are just dealing with exceptions. var pendingErrors = []; var requestErrorThrow = rawAsap.makeRequestCallFromTimer(throwFirstError); function throwFirstError() { if (pendingErrors.length) { throw pendingErrors.shift(); } } /** * Calls a task as soon as possible after returning, in its own event, with priority * over other events like animation, reflow, and repaint. An error thrown from an * event will not interrupt, nor even substantially slow down the processing of * other events, but will be rather postponed to a lower priority event. * @param {{call}} task A callable object, typically a function that takes no * arguments. */ module.exports = asap; function asap(task) { var rawTask; if (freeTasks.length) { rawTask = freeTasks.pop(); } else { rawTask = new RawTask(); } rawTask.task = task; rawAsap(rawTask); } // We wrap tasks with recyclable task objects. A task object implements // `call`, just like a function. function RawTask() { this.task = null; } // The sole purpose of wrapping the task is to catch the exception and recycle // the task object after its single use. RawTask.prototype.call = function () { try { this.task.call(); } catch (error) { if (asap.onerror) { // This hook exists purely for testing purposes. // Its name will be periodically randomized to break any code that // depends on its existence. asap.onerror(error); } else { // In a web browser, exceptions are not fatal. However, to avoid // slowing down the queue of pending tasks, we rethrow the error in a // lower priority turn. pendingErrors.push(error); requestErrorThrow(); } } finally { this.task = null; freeTasks[freeTasks.length] = this; } }; },{"./raw":34}],34:[function(require,module,exports){ (function (global){ // Use the fastest means possible to execute a task in its own turn, with // priority over other events including IO, animation, reflow, and redraw // events in browsers. // // An exception thrown by a task will permanently interrupt the processing of // subsequent tasks. The higher level `asap` function ensures that if an // exception is thrown by a task, that the task queue will continue flushing as // soon as possible, but if you use `rawAsap` directly, you are responsible to // either ensure that no exceptions are thrown from your task, or to manually // call `rawAsap.requestFlush` if an exception is thrown. module.exports = rawAsap; function rawAsap(task) { if (!queue.length) { requestFlush(); } // Equivalent to push, but avoids a function call. queue[queue.length] = task; } var queue = []; // `requestFlush` is an implementation-specific method that attempts to kick // off a `flush` event as quickly as possible. `flush` will attempt to exhaust // the event queue before yielding to the browser's own event loop. var requestFlush; // The position of the next task to execute in the task queue. This is // preserved between calls to `flush` so that it can be resumed if // a task throws an exception. var index = 0; // If a task schedules additional tasks recursively, the task queue can grow // unbounded. To prevent memory exhaustion, the task queue will periodically // truncate already-completed tasks. var capacity = 1024; // The flush function processes all tasks that have been scheduled with // `rawAsap` unless and until one of those tasks throws an exception. // If a task throws an exception, `flush` ensures that its state will remain // consistent and will resume where it left off when called again. // However, `flush` does not make any arrangements to be called again if an // exception is thrown. function flush() { while (index < queue.length) { var currentIndex = index; // Advance the index before calling the task. This ensures that we will // begin flushing on the next task the task throws an error. index = index + 1; queue[currentIndex].call(); // Prevent leaking memory for long chains of recursive calls to `asap`. // If we call `asap` within tasks scheduled by `asap`, the queue will // grow, but to avoid an O(n) walk for every task we execute, we don't // shift tasks off the queue after they have been executed. // Instead, we periodically shift 1024 tasks off the queue. if (index > capacity) { // Manually shift all values starting at the index back to the // beginning of the queue. for (var scan = 0, newLength = queue.length - index; scan < newLength; scan++) { queue[scan] = queue[scan + index]; } queue.length -= index; index = 0; } } queue.length = 0; index = 0; } // `requestFlush` is implemented using a strategy based on data collected from // every available SauceLabs Selenium web driver worker at time of writing. // https://docs.google.com/spreadsheets/d/1mG-5UYGup5qxGdEMWkhP6BWCz053NUb2E1QoUTU16uA/edit#gid=783724593 // Safari 6 and 6.1 for desktop, iPad, and iPhone are the only browsers that // have WebKitMutationObserver but not un-prefixed MutationObserver. // Must use `global` instead of `window` to work in both frames and web // workers. `global` is a provision of Browserify, Mr, Mrs, or Mop. var BrowserMutationObserver = global.MutationObserver || global.WebKitMutationObserver; // MutationObservers are desirable because they have high priority and work // reliably everywhere they are implemented. // They are implemented in all modern browsers. // // - Android 4-4.3 // - Chrome 26-34 // - Firefox 14-29 // - Internet Explorer 11 // - iPad Safari 6-7.1 // - iPhone Safari 7-7.1 // - Safari 6-7 if (typeof BrowserMutationObserver === "function") { requestFlush = makeRequestCallFromMutationObserver(flush); // MessageChannels are desirable because they give direct access to the HTML // task queue, are implemented in Internet Explorer 10, Safari 5.0-1, and Opera // 11-12, and in web workers in many engines. // Although message channels yield to any queued rendering and IO tasks, they // would be better than imposing the 4ms delay of timers. // However, they do not work reliably in Internet Explorer or Safari. // Internet Explorer 10 is the only browser that has setImmediate but does // not have MutationObservers. // Although setImmediate yields to the browser's renderer, it would be // preferrable to falling back to setTimeout since it does not have // the minimum 4ms penalty. // Unfortunately there appears to be a bug in Internet Explorer 10 Mobile (and // Desktop to a lesser extent) that renders both setImmediate and // MessageChannel useless for the purposes of ASAP. // https://github.com/kriskowal/q/issues/396 // Timers are implemented universally. // We fall back to timers in workers in most engines, and in foreground // contexts in the following browsers. // However, note that even this simple case requires nuances to operate in a // broad spectrum of browsers. // // - Firefox 3-13 // - Internet Explorer 6-9 // - iPad Safari 4.3 // - Lynx 2.8.7 } else { requestFlush = makeRequestCallFromTimer(flush); } // `requestFlush` requests that the high priority event queue be flushed as // soon as possible. // This is useful to prevent an error thrown in a task from stalling the event // queue if the exception handled by Node.js’s // `process.on("uncaughtException")` or by a domain. rawAsap.requestFlush = requestFlush; // To request a high priority event, we induce a mutation observer by toggling // the text of a text node between "1" and "-1". function makeRequestCallFromMutationObserver(callback) { var toggle = 1; var observer = new BrowserMutationObserver(callback); var node = document.createTextNode(""); observer.observe(node, {characterData: true}); return function requestCall() { toggle = -toggle; node.data = toggle; }; } // The message channel technique was discovered by Malte Ubl and was the // original foundation for this library. // http://www.nonblocking.io/2011/06/windownexttick.html // Safari 6.0.5 (at least) intermittently fails to create message ports on a // page's first load. Thankfully, this version of Safari supports // MutationObservers, so we don't need to fall back in that case. // function makeRequestCallFromMessageChannel(callback) { // var channel = new MessageChannel(); // channel.port1.onmessage = callback; // return function requestCall() { // channel.port2.postMessage(0); // }; // } // For reasons explained above, we are also unable to use `setImmediate` // under any circumstances. // Even if we were, there is another bug in Internet Explorer 10. // It is not sufficient to assign `setImmediate` to `requestFlush` because // `setImmediate` must be called *by name* and therefore must be wrapped in a // closure. // Never forget. // function makeRequestCallFromSetImmediate(callback) { // return function requestCall() { // setImmediate(callback); // }; // } // Safari 6.0 has a problem where timers will get lost while the user is // scrolling. This problem does not impact ASAP because Safari 6.0 supports // mutation observers, so that implementation is used instead. // However, if we ever elect to use timers in Safari, the prevalent work-around // is to add a scroll event listener that calls for a flush. // `setTimeout` does not call the passed callback if the delay is less than // approximately 7 in web workers in Firefox 8 through 18, and sometimes not // even then. function makeRequestCallFromTimer(callback) { return function requestCall() { // We dispatch a timeout with a specified delay of 0 for engines that // can reliably accommodate that request. This will usually be snapped // to a 4 milisecond delay, but once we're flushing, there's no delay // between events. var timeoutHandle = setTimeout(handleTimer, 0); // However, since this timer gets frequently dropped in Firefox // workers, we enlist an interval handle that will try to fire // an event 20 times per second until it succeeds. var intervalHandle = setInterval(handleTimer, 50); function handleTimer() { // Whichever timer succeeds will cancel both timers and // execute the callback. clearTimeout(timeoutHandle); clearInterval(intervalHandle); callback(); } }; } // This is for `asap.js` only. // Its name will be periodically randomized to break any code that depends on // its existence. rawAsap.makeRequestCallFromTimer = makeRequestCallFromTimer; // ASAP was originally a nextTick shim included in Q. This was factored out // into this ASAP package. It was later adapted to RSVP which made further // amendments. These decisions, particularly to marginalize MessageChannel and // to capture the MutationObserver implementation in a closure, were integrated // back into ASAP proper. // https://github.com/tildeio/rsvp.js/blob/cddf7232546a9cf858524b75cde6f9edf72620a7/lib/rsvp/asap.js }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); },{}],35:[function(require,module,exports){ },{}],36:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = setTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; clearTimeout(timeout); } process.nextTick = function (fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { setTimeout(drainQueue, 0); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],37:[function(require,module,exports){ (function (process,global){ /*! * @overview es6-promise - a tiny implementation of Promises/A+. * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) * @license Licensed under MIT license * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE * @version 3.0.2 */ (function() { function lib$es6$promise$utils$$objectOrFunction(x) { return typeof x === 'function' || (typeof x === 'object' && x !== null); } function lib$es6$promise$utils$$isFunction(x) { return typeof x === 'function'; } function lib$es6$promise$utils$$isMaybeThenable(x) { return typeof x === 'object' && x !== null; } var lib$es6$promise$utils$$_isArray; if (!Array.isArray) { lib$es6$promise$utils$$_isArray = function (x) { return Object.prototype.toString.call(x) === '[object Array]'; }; } else { lib$es6$promise$utils$$_isArray = Array.isArray; } var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray; var lib$es6$promise$asap$$len = 0; var lib$es6$promise$asap$$vertxNext; var lib$es6$promise$asap$$customSchedulerFn; var lib$es6$promise$asap$$asap = function asap(callback, arg) { lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback; lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg; lib$es6$promise$asap$$len += 2; if (lib$es6$promise$asap$$len === 2) { // If len is 2, that means that we need to schedule an async flush. // If additional callbacks are queued before the queue is flushed, they // will be processed by this flush that we are scheduling. if (lib$es6$promise$asap$$customSchedulerFn) { lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush); } else { lib$es6$promise$asap$$scheduleFlush(); } } }; function lib$es6$promise$asap$$setScheduler(scheduleFn) { lib$es6$promise$asap$$customSchedulerFn = scheduleFn; } function lib$es6$promise$asap$$setAsap(asapFn) { lib$es6$promise$asap$$asap = asapFn; } var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined; var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {}; var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver; var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]'; // test for web worker but not in IE10 var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; // node function lib$es6$promise$asap$$useNextTick() { // node version 0.10.x displays a deprecation warning when nextTick is used recursively // see https://github.com/cujojs/when/issues/410 for details return function() { process.nextTick(lib$es6$promise$asap$$flush); }; } // vertx function lib$es6$promise$asap$$useVertxTimer() { return function() { lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush); }; } function lib$es6$promise$asap$$useMutationObserver() { var iterations = 0; var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush); var node = document.createTextNode(''); observer.observe(node, { characterData: true }); return function() { node.data = (iterations = ++iterations % 2); }; } // web worker function lib$es6$promise$asap$$useMessageChannel() { var channel = new MessageChannel(); channel.port1.onmessage = lib$es6$promise$asap$$flush; return function () { channel.port2.postMessage(0); }; } function lib$es6$promise$asap$$useSetTimeout() { return function() { setTimeout(lib$es6$promise$asap$$flush, 1); }; } var lib$es6$promise$asap$$queue = new Array(1000); function lib$es6$promise$asap$$flush() { for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) { var callback = lib$es6$promise$asap$$queue[i]; var arg = lib$es6$promise$asap$$queue[i+1]; callback(arg); lib$es6$promise$asap$$queue[i] = undefined; lib$es6$promise$asap$$queue[i+1] = undefined; } lib$es6$promise$asap$$len = 0; } function lib$es6$promise$asap$$attemptVertx() { try { var r = require; var vertx = r('vertx'); lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext; return lib$es6$promise$asap$$useVertxTimer(); } catch(e) { return lib$es6$promise$asap$$useSetTimeout(); } } var lib$es6$promise$asap$$scheduleFlush; // Decide what async method to use to triggering processing of queued callbacks: if (lib$es6$promise$asap$$isNode) { lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick(); } else if (lib$es6$promise$asap$$BrowserMutationObserver) { lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver(); } else if (lib$es6$promise$asap$$isWorker) { lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel(); } else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') { lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertx(); } else { lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout(); } function lib$es6$promise$$internal$$noop() {} var lib$es6$promise$$internal$$PENDING = void 0; var lib$es6$promise$$internal$$FULFILLED = 1; var lib$es6$promise$$internal$$REJECTED = 2; var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject(); function lib$es6$promise$$internal$$selfFulfillment() { return new TypeError("You cannot resolve a promise with itself"); } function lib$es6$promise$$internal$$cannotReturnOwn() { return new TypeError('A promises callback cannot return that same promise.'); } function lib$es6$promise$$internal$$getThen(promise) { try { return promise.then; } catch(error) { lib$es6$promise$$internal$$GET_THEN_ERROR.error = error; return lib$es6$promise$$internal$$GET_THEN_ERROR; } } function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { try { then.call(value, fulfillmentHandler, rejectionHandler); } catch(e) { return e; } } function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) { lib$es6$promise$asap$$asap(function(promise) { var sealed = false; var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) { if (sealed) { return; } sealed = true; if (thenable !== value) { lib$es6$promise$$internal$$resolve(promise, value); } else { lib$es6$promise$$internal$$fulfill(promise, value); } }, function(reason) { if (sealed) { return; } sealed = true; lib$es6$promise$$internal$$reject(promise, reason); }, 'Settle: ' + (promise._label || ' unknown promise')); if (!sealed && error) { sealed = true; lib$es6$promise$$internal$$reject(promise, error); } }, promise); } function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) { if (thenable._state === lib$es6$promise$$internal$$FULFILLED) { lib$es6$promise$$internal$$fulfill(promise, thenable._result); } else if (thenable._state === lib$es6$promise$$internal$$REJECTED) { lib$es6$promise$$internal$$reject(promise, thenable._result); } else { lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) { lib$es6$promise$$internal$$resolve(promise, value); }, function(reason) { lib$es6$promise$$internal$$reject(promise, reason); }); } } function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) { if (maybeThenable.constructor === promise.constructor) { lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable); } else { var then = lib$es6$promise$$internal$$getThen(maybeThenable); if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) { lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error); } else if (then === undefined) { lib$es6$promise$$internal$$fulfill(promise, maybeThenable); } else if (lib$es6$promise$utils$$isFunction(then)) { lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then); } else { lib$es6$promise$$internal$$fulfill(promise, maybeThenable); } } } function lib$es6$promise$$internal$$resolve(promise, value) { if (promise === value) { lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFulfillment()); } else if (lib$es6$promise$utils$$objectOrFunction(value)) { lib$es6$promise$$internal$$handleMaybeThenable(promise, value); } else { lib$es6$promise$$internal$$fulfill(promise, value); } } function lib$es6$promise$$internal$$publishRejection(promise) { if (promise._onerror) { promise._onerror(promise._result); } lib$es6$promise$$internal$$publish(promise); } function lib$es6$promise$$internal$$fulfill(promise, value) { if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } promise._result = value; promise._state = lib$es6$promise$$internal$$FULFILLED; if (promise._subscribers.length !== 0) { lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise); } } function lib$es6$promise$$internal$$reject(promise, reason) { if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; } promise._state = lib$es6$promise$$internal$$REJECTED; promise._result = reason; lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise); } function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) { var subscribers = parent._subscribers; var length = subscribers.length; parent._onerror = null; subscribers[length] = child; subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment; subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection; if (length === 0 && parent._state) { lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent); } } function lib$es6$promise$$internal$$publish(promise) { var subscribers = promise._subscribers; var settled = promise._state; if (subscribers.length === 0) { return; } var child, callback, detail = promise._result; for (var i = 0; i < subscribers.length; i += 3) { child = subscribers[i]; callback = subscribers[i + settled]; if (child) { lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail); } else { callback(detail); } } promise._subscribers.length = 0; } function lib$es6$promise$$internal$$ErrorObject() { this.error = null; } var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject(); function lib$es6$promise$$internal$$tryCatch(callback, detail) { try { return callback(detail); } catch(e) { lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e; return lib$es6$promise$$internal$$TRY_CATCH_ERROR; } } function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) { var hasCallback = lib$es6$promise$utils$$isFunction(callback), value, error, succeeded, failed; if (hasCallback) { value = lib$es6$promise$$internal$$tryCatch(callback, detail); if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) { failed = true; error = value.error; value = null; } else { succeeded = true; } if (promise === value) { lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn()); return; } } else { value = detail; succeeded = true; } if (promise._state !== lib$es6$promise$$internal$$PENDING) ; else if (hasCallback && succeeded) { lib$es6$promise$$internal$$resolve(promise, value); } else if (failed) { lib$es6$promise$$internal$$reject(promise, error); } else if (settled === lib$es6$promise$$internal$$FULFILLED) { lib$es6$promise$$internal$$fulfill(promise, value); } else if (settled === lib$es6$promise$$internal$$REJECTED) { lib$es6$promise$$internal$$reject(promise, value); } } function lib$es6$promise$$internal$$initializePromise(promise, resolver) { try { resolver(function resolvePromise(value){ lib$es6$promise$$internal$$resolve(promise, value); }, function rejectPromise(reason) { lib$es6$promise$$internal$$reject(promise, reason); }); } catch(e) { lib$es6$promise$$internal$$reject(promise, e); } } function lib$es6$promise$enumerator$$Enumerator(Constructor, input) { var enumerator = this; enumerator._instanceConstructor = Constructor; enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop); if (enumerator._validateInput(input)) { enumerator._input = input; enumerator.length = input.length; enumerator._remaining = input.length; enumerator._init(); if (enumerator.length === 0) { lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); } else { enumerator.length = enumerator.length || 0; enumerator._enumerate(); if (enumerator._remaining === 0) { lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result); } } } else { lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError()); } } lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) { return lib$es6$promise$utils$$isArray(input); }; lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() { return new Error('Array Methods must be provided an Array'); }; lib$es6$promise$enumerator$$Enumerator.prototype._init = function() { this._result = new Array(this.length); }; var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator; lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() { var enumerator = this; var length = enumerator.length; var promise = enumerator.promise; var input = enumerator._input; for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { enumerator._eachEntry(input[i], i); } }; lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { var enumerator = this; var c = enumerator._instanceConstructor; if (lib$es6$promise$utils$$isMaybeThenable(entry)) { if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) { entry._onerror = null; enumerator._settledAt(entry._state, i, entry._result); } else { enumerator._willSettleAt(c.resolve(entry), i); } } else { enumerator._remaining--; enumerator._result[i] = entry; } }; lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { var enumerator = this; var promise = enumerator.promise; if (promise._state === lib$es6$promise$$internal$$PENDING) { enumerator._remaining--; if (state === lib$es6$promise$$internal$$REJECTED) { lib$es6$promise$$internal$$reject(promise, value); } else { enumerator._result[i] = value; } } if (enumerator._remaining === 0) { lib$es6$promise$$internal$$fulfill(promise, enumerator._result); } }; lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { var enumerator = this; lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) { enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value); }, function(reason) { enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason); }); }; function lib$es6$promise$promise$all$$all(entries) { return new lib$es6$promise$enumerator$$default(this, entries).promise; } var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all; function lib$es6$promise$promise$race$$race(entries) { /*jshint validthis:true */ var Constructor = this; var promise = new Constructor(lib$es6$promise$$internal$$noop); if (!lib$es6$promise$utils$$isArray(entries)) { lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.')); return promise; } var length = entries.length; function onFulfillment(value) { lib$es6$promise$$internal$$resolve(promise, value); } function onRejection(reason) { lib$es6$promise$$internal$$reject(promise, reason); } for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) { lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); } return promise; } var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race; function lib$es6$promise$promise$resolve$$resolve(object) { /*jshint validthis:true */ var Constructor = this; if (object && typeof object === 'object' && object.constructor === Constructor) { return object; } var promise = new Constructor(lib$es6$promise$$internal$$noop); lib$es6$promise$$internal$$resolve(promise, object); return promise; } var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve; function lib$es6$promise$promise$reject$$reject(reason) { /*jshint validthis:true */ var Constructor = this; var promise = new Constructor(lib$es6$promise$$internal$$noop); lib$es6$promise$$internal$$reject(promise, reason); return promise; } var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject; var lib$es6$promise$promise$$counter = 0; function lib$es6$promise$promise$$needsResolver() { throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); } function lib$es6$promise$promise$$needsNew() { throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); } var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise; /** Promise objects represent the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its `then` method, which registers callbacks to receive either a promise's eventual value or the reason why the promise cannot be fulfilled. Terminology ----------- - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - `thenable` is an object or function that defines a `then` method. - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - `exception` is a value that is thrown using the throw statement. - `reason` is a value that indicates why a promise was rejected. - `settled` the final resting state of a promise, fulfilled or rejected. A promise can be in one of three states: pending, fulfilled, or rejected. Promises that are fulfilled have a fulfillment value and are in the fulfilled state. Promises that are rejected have a rejection reason and are in the rejected state. A fulfillment value is never a thenable. Promises can also be said to *resolve* a value. If this value is also a promise, then the original promise's settled state will match the value's settled state. So a promise that *resolves* a promise that rejects will itself reject, and a promise that *resolves* a promise that fulfills will itself fulfill. Basic Usage: ------------ ```js var promise = new Promise(function(resolve, reject) { // on success resolve(value); // on failure reject(reason); }); promise.then(function(value) { // on fulfillment }, function(reason) { // on rejection }); ``` Advanced Usage: --------------- Promises shine when abstracting away asynchronous interactions such as `XMLHttpRequest`s. ```js function getJSON(url) { return new Promise(function(resolve, reject){ var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.onreadystatechange = handler; xhr.responseType = 'json'; xhr.setRequestHeader('Accept', 'application/json'); xhr.send(); function handler() { if (this.readyState === this.DONE) { if (this.status === 200) { resolve(this.response); } else { reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); } } }; }); } getJSON('/posts.json').then(function(json) { // on fulfillment }, function(reason) { // on rejection }); ``` Unlike callbacks, promises are great composable primitives. ```js Promise.all([ getJSON('/posts'), getJSON('/comments') ]).then(function(values){ values[0] // => postsJSON values[1] // => commentsJSON return values; }); ``` @class Promise @param {function} resolver Useful for tooling. @constructor */ function lib$es6$promise$promise$$Promise(resolver) { this._id = lib$es6$promise$promise$$counter++; this._state = undefined; this._result = undefined; this._subscribers = []; if (lib$es6$promise$$internal$$noop !== resolver) { if (!lib$es6$promise$utils$$isFunction(resolver)) { lib$es6$promise$promise$$needsResolver(); } if (!(this instanceof lib$es6$promise$promise$$Promise)) { lib$es6$promise$promise$$needsNew(); } lib$es6$promise$$internal$$initializePromise(this, resolver); } } lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default; lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default; lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default; lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default; lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler; lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap; lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap; lib$es6$promise$promise$$Promise.prototype = { constructor: lib$es6$promise$promise$$Promise, /** The primary way of interacting with a promise is through its `then` method, which registers callbacks to receive either a promise's eventual value or the reason why the promise cannot be fulfilled. ```js findUser().then(function(user){ // user is available }, function(reason){ // user is unavailable, and you are given the reason why }); ``` Chaining -------- The return value of `then` is itself a promise. This second, 'downstream' promise is resolved with the return value of the first promise's fulfillment or rejection handler, or rejected if the handler throws an exception. ```js findUser().then(function (user) { return user.name; }, function (reason) { return 'default name'; }).then(function (userName) { // If `findUser` fulfilled, `userName` will be the user's name, otherwise it // will be `'default name'` }); findUser().then(function (user) { throw new Error('Found user, but still unhappy'); }, function (reason) { throw new Error('`findUser` rejected and we're unhappy'); }).then(function (value) { // never reached }, function (reason) { // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. }); ``` If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. ```js findUser().then(function (user) { throw new PedagogicalException('Upstream error'); }).then(function (value) { // never reached }).then(function (value) { // never reached }, function (reason) { // The `PedgagocialException` is propagated all the way down to here }); ``` Assimilation ------------ Sometimes the value you want to propagate to a downstream promise can only be retrieved asynchronously. This can be achieved by returning a promise in the fulfillment or rejection handler. The downstream promise will then be pending until the returned promise is settled. This is called *assimilation*. ```js findUser().then(function (user) { return findCommentsByAuthor(user); }).then(function (comments) { // The user's comments are now available }); ``` If the assimliated promise rejects, then the downstream promise will also reject. ```js findUser().then(function (user) { return findCommentsByAuthor(user); }).then(function (comments) { // If `findCommentsByAuthor` fulfills, we'll have the value here }, function (reason) { // If `findCommentsByAuthor` rejects, we'll have the reason here }); ``` Simple Example -------------- Synchronous Example ```javascript var result; try { result = findResult(); // success } catch(reason) { // failure } ``` Errback Example ```js findResult(function(result, err){ if (err) { // failure } else { // success } }); ``` Promise Example; ```javascript findResult().then(function(result){ // success }, function(reason){ // failure }); ``` Advanced Example -------------- Synchronous Example ```javascript var author, books; try { author = findAuthor(); books = findBooksByAuthor(author); // success } catch(reason) { // failure } ``` Errback Example ```js function foundBooks(books) { } function failure(reason) { } findAuthor(function(author, err){ if (err) { failure(err); // failure } else { try { findBoooksByAuthor(author, function(books, err) { if (err) { failure(err); } else { try { foundBooks(books); } catch(reason) { failure(reason); } } }); } catch(error) { failure(err); } // success } }); ``` Promise Example; ```javascript findAuthor(). then(findBooksByAuthor). then(function(books){ // found books }).catch(function(reason){ // something went wrong }); ``` @method then @param {Function} onFulfilled @param {Function} onRejected Useful for tooling. @return {Promise} */ then: function(onFulfillment, onRejection) { var parent = this; var state = parent._state; if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) { return this; } var child = new this.constructor(lib$es6$promise$$internal$$noop); var result = parent._result; if (state) { var callback = arguments[state - 1]; lib$es6$promise$asap$$asap(function(){ lib$es6$promise$$internal$$invokeCallback(state, child, callback, result); }); } else { lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection); } return child; }, /** `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same as the catch block of a try/catch statement. ```js function findAuthor(){ throw new Error('couldn't find that author'); } // synchronous try { findAuthor(); } catch(reason) { // something went wrong } // async with promises findAuthor().catch(function(reason){ // something went wrong }); ``` @method catch @param {Function} onRejection Useful for tooling. @return {Promise} */ 'catch': function(onRejection) { return this.then(null, onRejection); } }; function lib$es6$promise$polyfill$$polyfill() { var local; if (typeof global !== 'undefined') { local = global; } else if (typeof self !== 'undefined') { local = self; } else { try { local = Function('return this')(); } catch (e) { throw new Error('polyfill failed because global object is unavailable in this environment'); } } var P = local.Promise; if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) { return; } local.Promise = lib$es6$promise$promise$$default; } var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill; var lib$es6$promise$umd$$ES6Promise = { 'Promise': lib$es6$promise$promise$$default, 'polyfill': lib$es6$promise$polyfill$$default }; /* global define:true module:true window: true */ if (typeof module !== 'undefined' && module['exports']) { module['exports'] = lib$es6$promise$umd$$ES6Promise; } else if (typeof this !== 'undefined') { this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise; } lib$es6$promise$polyfill$$default(); }).call(this); }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}); },{"_process":36}],38:[function(require,module,exports){ var assign = require('./lib/utils/common').assign; var deflate = require('./lib/deflate'); var inflate = require('./lib/inflate'); var constants = require('./lib/zlib/constants'); var pako = {}; assign(pako, deflate, inflate, constants); module.exports = pako; },{"./lib/deflate":39,"./lib/inflate":40,"./lib/utils/common":41,"./lib/zlib/constants":44}],39:[function(require,module,exports){ var zlib_deflate = require('./zlib/deflate'); var utils = require('./utils/common'); var strings = require('./utils/strings'); var msg = require('./zlib/messages'); var ZStream = require('./zlib/zstream'); var toString = Object.prototype.toString; /* Public constants ==========================================================*/ /* ===========================================================================*/ var Z_NO_FLUSH = 0; var Z_FINISH = 4; var Z_OK = 0; var Z_STREAM_END = 1; var Z_SYNC_FLUSH = 2; var Z_DEFAULT_COMPRESSION = -1; var Z_DEFAULT_STRATEGY = 0; var Z_DEFLATED = 8; /* ===========================================================================*/ /** * class Deflate * * Generic JS-style wrapper for zlib calls. If you don't need * streaming behaviour - use more simple functions: [[deflate]], * [[deflateRaw]] and [[gzip]]. **/ /* internal * Deflate.chunks -> Array * * Chunks of output data, if [[Deflate#onData]] not overriden. **/ /** * Deflate.result -> Uint8Array|Array * * Compressed result, generated by default [[Deflate#onData]] * and [[Deflate#onEnd]] handlers. Filled after you push last chunk * (call [[Deflate#push]] with `Z_FINISH` / `true` param) or if you * push a chunk with explicit flush (call [[Deflate#push]] with * `Z_SYNC_FLUSH` param). **/ /** * Deflate.err -> Number * * Error code after deflate finished. 0 (Z_OK) on success. * You will not need it in real life, because deflate errors * are possible only on wrong options or bad `onData` / `onEnd` * custom handlers. **/ /** * Deflate.msg -> String * * Error message, if [[Deflate.err]] != 0 **/ /** * new Deflate(options) * - options (Object): zlib deflate options. * * Creates new deflator instance with specified params. Throws exception * on bad params. Supported options: * * - `level` * - `windowBits` * - `memLevel` * - `strategy` * - `dictionary` * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information on these. * * Additional options, for internal needs: * * - `chunkSize` - size of generated data chunks (16K by default) * - `raw` (Boolean) - do raw deflate * - `gzip` (Boolean) - create gzip wrapper * - `to` (String) - if equal to 'string', then result will be "binary string" * (each char code [0..255]) * - `header` (Object) - custom header for gzip * - `text` (Boolean) - true if compressed data believed to be text * - `time` (Number) - modification time, unix timestamp * - `os` (Number) - operation system code * - `extra` (Array) - array of bytes with extra data (max 65536) * - `name` (String) - file name (binary string) * - `comment` (String) - comment (binary string) * - `hcrc` (Boolean) - true if header crc should be added * * ##### Example: * * ```javascript * var pako = require('pako') * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); * * var deflate = new pako.Deflate({ level: 3}); * * deflate.push(chunk1, false); * deflate.push(chunk2, true); // true -> last chunk * * if (deflate.err) { throw new Error(deflate.err); } * * console.log(deflate.result); * ``` **/ function Deflate(options) { if (!(this instanceof Deflate)) return new Deflate(options); this.options = utils.assign({ level: Z_DEFAULT_COMPRESSION, method: Z_DEFLATED, chunkSize: 16384, windowBits: 15, memLevel: 8, strategy: Z_DEFAULT_STRATEGY, to: '' }, options || {}); var opt = this.options; if (opt.raw && (opt.windowBits > 0)) { opt.windowBits = -opt.windowBits; } else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { opt.windowBits += 16; } this.err = 0; // error code, if happens (0 = Z_OK) this.msg = ''; // error message this.ended = false; // used to avoid multiple onEnd() calls this.chunks = []; // chunks of compressed data this.strm = new ZStream(); this.strm.avail_out = 0; var status = zlib_deflate.deflateInit2( this.strm, opt.level, opt.method, opt.windowBits, opt.memLevel, opt.strategy ); if (status !== Z_OK) { throw new Error(msg[status]); } if (opt.header) { zlib_deflate.deflateSetHeader(this.strm, opt.header); } if (opt.dictionary) { var dict; // Convert data if needed if (typeof opt.dictionary === 'string') { // If we need to compress text, change encoding to utf8. dict = strings.string2buf(opt.dictionary); } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { dict = new Uint8Array(opt.dictionary); } else { dict = opt.dictionary; } status = zlib_deflate.deflateSetDictionary(this.strm, dict); if (status !== Z_OK) { throw new Error(msg[status]); } this._dict_set = true; } } /** * Deflate#push(data[, mode]) -> Boolean * - data (Uint8Array|Array|ArrayBuffer|String): input data. Strings will be * converted to utf8 byte sequence. * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. * * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with * new compressed chunks. Returns `true` on success. The last data block must have * mode Z_FINISH (or `true`). That will flush internal pending buffers and call * [[Deflate#onEnd]]. For interim explicit flushes (without ending the stream) you * can use mode Z_SYNC_FLUSH, keeping the compression context. * * On fail call [[Deflate#onEnd]] with error code and return false. * * We strongly recommend to use `Uint8Array` on input for best speed (output * array format is detected automatically). Also, don't skip last param and always * use the same type in your code (boolean or number). That will improve JS speed. * * For regular `Array`-s make sure all elements are [0..255]. * * ##### Example * * ```javascript * push(chunk, false); // push one of data chunks * ... * push(chunk, true); // push last chunk * ``` **/ Deflate.prototype.push = function (data, mode) { var strm = this.strm; var chunkSize = this.options.chunkSize; var status, _mode; if (this.ended) { return false; } _mode = (mode === ~~mode) ? mode : ((mode === true) ? Z_FINISH : Z_NO_FLUSH); // Convert data if needed if (typeof data === 'string') { // If we need to compress text, change encoding to utf8. strm.input = strings.string2buf(data); } else if (toString.call(data) === '[object ArrayBuffer]') { strm.input = new Uint8Array(data); } else { strm.input = data; } strm.next_in = 0; strm.avail_in = strm.input.length; do { if (strm.avail_out === 0) { strm.output = new utils.Buf8(chunkSize); strm.next_out = 0; strm.avail_out = chunkSize; } status = zlib_deflate.deflate(strm, _mode); /* no bad return value */ if (status !== Z_STREAM_END && status !== Z_OK) { this.onEnd(status); this.ended = true; return false; } if (strm.avail_out === 0 || (strm.avail_in === 0 && (_mode === Z_FINISH || _mode === Z_SYNC_FLUSH))) { if (this.options.to === 'string') { this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out))); } else { this.onData(utils.shrinkBuf(strm.output, strm.next_out)); } } } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END); // Finalize on the last chunk. if (_mode === Z_FINISH) { status = zlib_deflate.deflateEnd(this.strm); this.onEnd(status); this.ended = true; return status === Z_OK; } // callback interim results if Z_SYNC_FLUSH. if (_mode === Z_SYNC_FLUSH) { this.onEnd(Z_OK); strm.avail_out = 0; return true; } return true; }; /** * Deflate#onData(chunk) -> Void * - chunk (Uint8Array|Array|String): ouput data. Type of array depends * on js engine support. When string output requested, each chunk * will be string. * * By default, stores data blocks in `chunks[]` property and glue * those in `onEnd`. Override this handler, if you need another behaviour. **/ Deflate.prototype.onData = function (chunk) { this.chunks.push(chunk); }; /** * Deflate#onEnd(status) -> Void * - status (Number): deflate status. 0 (Z_OK) on success, * other if not. * * Called once after you tell deflate that the input stream is * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH) * or if an error happened. By default - join collected chunks, * free memory and fill `results` / `err` properties. **/ Deflate.prototype.onEnd = function (status) { // On success - join if (status === Z_OK) { if (this.options.to === 'string') { this.result = this.chunks.join(''); } else { this.result = utils.flattenChunks(this.chunks); } } this.chunks = []; this.err = status; this.msg = this.strm.msg; }; /** * deflate(data[, options]) -> Uint8Array|Array|String * - data (Uint8Array|Array|String): input data to compress. * - options (Object): zlib deflate options. * * Compress `data` with deflate algorithm and `options`. * * Supported options are: * * - level * - windowBits * - memLevel * - strategy * - dictionary * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information on these. * * Sugar (options): * * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify * negative windowBits implicitly. * - `to` (String) - if equal to 'string', then result will be "binary string" * (each char code [0..255]) * * ##### Example: * * ```javascript * var pako = require('pako') * , data = Uint8Array([1,2,3,4,5,6,7,8,9]); * * console.log(pako.deflate(data)); * ``` **/ function deflate(input, options) { var deflator = new Deflate(options); deflator.push(input, true); // That will never happens, if you don't cheat with options :) if (deflator.err) { throw deflator.msg; } return deflator.result; } /** * deflateRaw(data[, options]) -> Uint8Array|Array|String * - data (Uint8Array|Array|String): input data to compress. * - options (Object): zlib deflate options. * * The same as [[deflate]], but creates raw data, without wrapper * (header and adler32 crc). **/ function deflateRaw(input, options) { options = options || {}; options.raw = true; return deflate(input, options); } /** * gzip(data[, options]) -> Uint8Array|Array|String * - data (Uint8Array|Array|String): input data to compress. * - options (Object): zlib deflate options. * * The same as [[deflate]], but create gzip wrapper instead of * deflate one. **/ function gzip(input, options) { options = options || {}; options.gzip = true; return deflate(input, options); } exports.Deflate = Deflate; exports.deflate = deflate; exports.deflateRaw = deflateRaw; exports.gzip = gzip; },{"./utils/common":41,"./utils/strings":42,"./zlib/deflate":46,"./zlib/messages":51,"./zlib/zstream":53}],40:[function(require,module,exports){ var zlib_inflate = require('./zlib/inflate'); var utils = require('./utils/common'); var strings = require('./utils/strings'); var c = require('./zlib/constants'); var msg = require('./zlib/messages'); var ZStream = require('./zlib/zstream'); var GZheader = require('./zlib/gzheader'); var toString = Object.prototype.toString; /** * class Inflate * * Generic JS-style wrapper for zlib calls. If you don't need * streaming behaviour - use more simple functions: [[inflate]] * and [[inflateRaw]]. **/ /* internal * inflate.chunks -> Array * * Chunks of output data, if [[Inflate#onData]] not overriden. **/ /** * Inflate.result -> Uint8Array|Array|String * * Uncompressed result, generated by default [[Inflate#onData]] * and [[Inflate#onEnd]] handlers. Filled after you push last chunk * (call [[Inflate#push]] with `Z_FINISH` / `true` param) or if you * push a chunk with explicit flush (call [[Inflate#push]] with * `Z_SYNC_FLUSH` param). **/ /** * Inflate.err -> Number * * Error code after inflate finished. 0 (Z_OK) on success. * Should be checked if broken data possible. **/ /** * Inflate.msg -> String * * Error message, if [[Inflate.err]] != 0 **/ /** * new Inflate(options) * - options (Object): zlib inflate options. * * Creates new inflator instance with specified params. Throws exception * on bad params. Supported options: * * - `windowBits` * - `dictionary` * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information on these. * * Additional options, for internal needs: * * - `chunkSize` - size of generated data chunks (16K by default) * - `raw` (Boolean) - do raw inflate * - `to` (String) - if equal to 'string', then result will be converted * from utf8 to utf16 (javascript) string. When string output requested, * chunk length can differ from `chunkSize`, depending on content. * * By default, when no options set, autodetect deflate/gzip data format via * wrapper header. * * ##### Example: * * ```javascript * var pako = require('pako') * , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9]) * , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]); * * var inflate = new pako.Inflate({ level: 3}); * * inflate.push(chunk1, false); * inflate.push(chunk2, true); // true -> last chunk * * if (inflate.err) { throw new Error(inflate.err); } * * console.log(inflate.result); * ``` **/ function Inflate(options) { if (!(this instanceof Inflate)) return new Inflate(options); this.options = utils.assign({ chunkSize: 16384, windowBits: 0, to: '' }, options || {}); var opt = this.options; // Force window size for `raw` data, if not set directly, // because we have no header for autodetect. if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { opt.windowBits = -opt.windowBits; if (opt.windowBits === 0) { opt.windowBits = -15; } } // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate if ((opt.windowBits >= 0) && (opt.windowBits < 16) && !(options && options.windowBits)) { opt.windowBits += 32; } // Gzip header has no info about windows size, we can do autodetect only // for deflate. So, if window size not set, force it to max when gzip possible if ((opt.windowBits > 15) && (opt.windowBits < 48)) { // bit 3 (16) -> gzipped data // bit 4 (32) -> autodetect gzip/deflate if ((opt.windowBits & 15) === 0) { opt.windowBits |= 15; } } this.err = 0; // error code, if happens (0 = Z_OK) this.msg = ''; // error message this.ended = false; // used to avoid multiple onEnd() calls this.chunks = []; // chunks of compressed data this.strm = new ZStream(); this.strm.avail_out = 0; var status = zlib_inflate.inflateInit2( this.strm, opt.windowBits ); if (status !== c.Z_OK) { throw new Error(msg[status]); } this.header = new GZheader(); zlib_inflate.inflateGetHeader(this.strm, this.header); } /** * Inflate#push(data[, mode]) -> Boolean * - data (Uint8Array|Array|ArrayBuffer|String): input data * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. * * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with * new output chunks. Returns `true` on success. The last data block must have * mode Z_FINISH (or `true`). That will flush internal pending buffers and call * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you * can use mode Z_SYNC_FLUSH, keeping the decompression context. * * On fail call [[Inflate#onEnd]] with error code and return false. * * We strongly recommend to use `Uint8Array` on input for best speed (output * format is detected automatically). Also, don't skip last param and always * use the same type in your code (boolean or number). That will improve JS speed. * * For regular `Array`-s make sure all elements are [0..255]. * * ##### Example * * ```javascript * push(chunk, false); // push one of data chunks * ... * push(chunk, true); // push last chunk * ``` **/ Inflate.prototype.push = function (data, mode) { var strm = this.strm; var chunkSize = this.options.chunkSize; var dictionary = this.options.dictionary; var status, _mode; var next_out_utf8, tail, utf8str; var dict; // Flag to properly process Z_BUF_ERROR on testing inflate call // when we check that all output data was flushed. var allowBufError = false; if (this.ended) { return false; } _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH); // Convert data if needed if (typeof data === 'string') { // Only binary strings can be decompressed on practice strm.input = strings.binstring2buf(data); } else if (toString.call(data) === '[object ArrayBuffer]') { strm.input = new Uint8Array(data); } else { strm.input = data; } strm.next_in = 0; strm.avail_in = strm.input.length; do { if (strm.avail_out === 0) { strm.output = new utils.Buf8(chunkSize); strm.next_out = 0; strm.avail_out = chunkSize; } status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH); /* no bad return value */ if (status === c.Z_NEED_DICT && dictionary) { // Convert data if needed if (typeof dictionary === 'string') { dict = strings.string2buf(dictionary); } else if (toString.call(dictionary) === '[object ArrayBuffer]') { dict = new Uint8Array(dictionary); } else { dict = dictionary; } status = zlib_inflate.inflateSetDictionary(this.strm, dict); } if (status === c.Z_BUF_ERROR && allowBufError === true) { status = c.Z_OK; allowBufError = false; } if (status !== c.Z_STREAM_END && status !== c.Z_OK) { this.onEnd(status); this.ended = true; return false; } if (strm.next_out) { if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH))) { if (this.options.to === 'string') { next_out_utf8 = strings.utf8border(strm.output, strm.next_out); tail = strm.next_out - next_out_utf8; utf8str = strings.buf2string(strm.output, next_out_utf8); // move tail strm.next_out = tail; strm.avail_out = chunkSize - tail; if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); } this.onData(utf8str); } else { this.onData(utils.shrinkBuf(strm.output, strm.next_out)); } } } // When no more input data, we should check that internal inflate buffers // are flushed. The only way to do it when avail_out = 0 - run one more // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR. // Here we set flag to process this error properly. // // NOTE. Deflate does not return error in this case and does not needs such // logic. if (strm.avail_in === 0 && strm.avail_out === 0) { allowBufError = true; } } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END); if (status === c.Z_STREAM_END) { _mode = c.Z_FINISH; } // Finalize on the last chunk. if (_mode === c.Z_FINISH) { status = zlib_inflate.inflateEnd(this.strm); this.onEnd(status); this.ended = true; return status === c.Z_OK; } // callback interim results if Z_SYNC_FLUSH. if (_mode === c.Z_SYNC_FLUSH) { this.onEnd(c.Z_OK); strm.avail_out = 0; return true; } return true; }; /** * Inflate#onData(chunk) -> Void * - chunk (Uint8Array|Array|String): ouput data. Type of array depends * on js engine support. When string output requested, each chunk * will be string. * * By default, stores data blocks in `chunks[]` property and glue * those in `onEnd`. Override this handler, if you need another behaviour. **/ Inflate.prototype.onData = function (chunk) { this.chunks.push(chunk); }; /** * Inflate#onEnd(status) -> Void * - status (Number): inflate status. 0 (Z_OK) on success, * other if not. * * Called either after you tell inflate that the input stream is * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH) * or if an error happened. By default - join collected chunks, * free memory and fill `results` / `err` properties. **/ Inflate.prototype.onEnd = function (status) { // On success - join if (status === c.Z_OK) { if (this.options.to === 'string') { // Glue & convert here, until we teach pako to send // utf8 alligned strings to onData this.result = this.chunks.join(''); } else { this.result = utils.flattenChunks(this.chunks); } } this.chunks = []; this.err = status; this.msg = this.strm.msg; }; /** * inflate(data[, options]) -> Uint8Array|Array|String * - data (Uint8Array|Array|String): input data to decompress. * - options (Object): zlib inflate options. * * Decompress `data` with inflate/ungzip and `options`. Autodetect * format via wrapper header by default. That's why we don't provide * separate `ungzip` method. * * Supported options are: * * - windowBits * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information. * * Sugar (options): * * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify * negative windowBits implicitly. * - `to` (String) - if equal to 'string', then result will be converted * from utf8 to utf16 (javascript) string. When string output requested, * chunk length can differ from `chunkSize`, depending on content. * * * ##### Example: * * ```javascript * var pako = require('pako') * , input = pako.deflate([1,2,3,4,5,6,7,8,9]) * , output; * * try { * output = pako.inflate(input); * } catch (err) * console.log(err); * } * ``` **/ function inflate(input, options) { var inflator = new Inflate(options); inflator.push(input, true); // That will never happens, if you don't cheat with options :) if (inflator.err) { throw inflator.msg; } return inflator.result; } /** * inflateRaw(data[, options]) -> Uint8Array|Array|String * - data (Uint8Array|Array|String): input data to decompress. * - options (Object): zlib inflate options. * * The same as [[inflate]], but creates raw data, without wrapper * (header and adler32 crc). **/ function inflateRaw(input, options) { options = options || {}; options.raw = true; return inflate(input, options); } /** * ungzip(data[, options]) -> Uint8Array|Array|String * - data (Uint8Array|Array|String): input data to decompress. * - options (Object): zlib inflate options. * * Just shortcut to [[inflate]], because it autodetects format * by header.content. Done for convenience. **/ exports.Inflate = Inflate; exports.inflate = inflate; exports.inflateRaw = inflateRaw; exports.ungzip = inflate; },{"./utils/common":41,"./utils/strings":42,"./zlib/constants":44,"./zlib/gzheader":47,"./zlib/inflate":49,"./zlib/messages":51,"./zlib/zstream":53}],41:[function(require,module,exports){ var TYPED_OK = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Int32Array !== 'undefined'); exports.assign = function (obj /*from1, from2, from3, ...*/) { var sources = Array.prototype.slice.call(arguments, 1); while (sources.length) { var source = sources.shift(); if (!source) { continue; } if (typeof source !== 'object') { throw new TypeError(source + 'must be non-object'); } for (var p in source) { if (source.hasOwnProperty(p)) { obj[p] = source[p]; } } } return obj; }; // reduce buffer size, avoiding mem copy exports.shrinkBuf = function (buf, size) { if (buf.length === size) { return buf; } if (buf.subarray) { return buf.subarray(0, size); } buf.length = size; return buf; }; var fnTyped = { arraySet: function (dest, src, src_offs, len, dest_offs) { if (src.subarray && dest.subarray) { dest.set(src.subarray(src_offs, src_offs + len), dest_offs); return; } // Fallback to ordinary array for (var i = 0; i < len; i++) { dest[dest_offs + i] = src[src_offs + i]; } }, // Join array of chunks to single array. flattenChunks: function (chunks) { var i, l, len, pos, chunk, result; // calculate data length len = 0; for (i = 0, l = chunks.length; i < l; i++) { len += chunks[i].length; } // join chunks result = new Uint8Array(len); pos = 0; for (i = 0, l = chunks.length; i < l; i++) { chunk = chunks[i]; result.set(chunk, pos); pos += chunk.length; } return result; } }; var fnUntyped = { arraySet: function (dest, src, src_offs, len, dest_offs) { for (var i = 0; i < len; i++) { dest[dest_offs + i] = src[src_offs + i]; } }, // Join array of chunks to single array. flattenChunks: function (chunks) { return [].concat.apply([], chunks); } }; // Enable/Disable typed arrays use, for testing // exports.setTyped = function (on) { if (on) { exports.Buf8 = Uint8Array; exports.Buf16 = Uint16Array; exports.Buf32 = Int32Array; exports.assign(exports, fnTyped); } else { exports.Buf8 = Array; exports.Buf16 = Array; exports.Buf32 = Array; exports.assign(exports, fnUntyped); } }; exports.setTyped(TYPED_OK); },{}],42:[function(require,module,exports){ var utils = require('./common'); // Quick check if we can use fast array to bin string conversion // // - apply(Array) can fail on Android 2.2 // - apply(Uint8Array) can fail on iOS 5.1 Safary // var STR_APPLY_OK = true; var STR_APPLY_UIA_OK = true; try { String.fromCharCode.apply(null, [ 0 ]); } catch (__) { STR_APPLY_OK = false; } try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } // Table with utf8 lengths (calculated by first byte of sequence) // Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, // because max possible codepoint is 0x10ffff var _utf8len = new utils.Buf8(256); for (var q = 0; q < 256; q++) { _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); } _utf8len[254] = _utf8len[254] = 1; // Invalid sequence start // convert string to array (typed, when possible) exports.string2buf = function (str) { var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; // count binary size for (m_pos = 0; m_pos < str_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { c2 = str.charCodeAt(m_pos + 1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; } // allocate buffer buf = new utils.Buf8(buf_len); // convert for (i = 0, m_pos = 0; i < buf_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { c2 = str.charCodeAt(m_pos + 1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } if (c < 0x80) { /* one byte */ buf[i++] = c; } else if (c < 0x800) { /* two bytes */ buf[i++] = 0xC0 | (c >>> 6); buf[i++] = 0x80 | (c & 0x3f); } else if (c < 0x10000) { /* three bytes */ buf[i++] = 0xE0 | (c >>> 12); buf[i++] = 0x80 | (c >>> 6 & 0x3f); buf[i++] = 0x80 | (c & 0x3f); } else { /* four bytes */ buf[i++] = 0xf0 | (c >>> 18); buf[i++] = 0x80 | (c >>> 12 & 0x3f); buf[i++] = 0x80 | (c >>> 6 & 0x3f); buf[i++] = 0x80 | (c & 0x3f); } } return buf; }; // Helper (used in 2 places) function buf2binstring(buf, len) { // use fallback for big arrays to avoid stack overflow if (len < 65537) { if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) { return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len)); } } var result = ''; for (var i = 0; i < len; i++) { result += String.fromCharCode(buf[i]); } return result; } // Convert byte array to binary string exports.buf2binstring = function (buf) { return buf2binstring(buf, buf.length); }; // Convert binary string (typed, when possible) exports.binstring2buf = function (str) { var buf = new utils.Buf8(str.length); for (var i = 0, len = buf.length; i < len; i++) { buf[i] = str.charCodeAt(i); } return buf; }; // convert array to string exports.buf2string = function (buf, max) { var i, out, c, c_len; var len = max || buf.length; // Reserve max possible length (2 words per char) // NB: by unknown reasons, Array is significantly faster for // String.fromCharCode.apply than Uint16Array. var utf16buf = new Array(len * 2); for (out = 0, i = 0; i < len;) { c = buf[i++]; // quick process ascii if (c < 0x80) { utf16buf[out++] = c; continue; } c_len = _utf8len[c]; // skip 5 & 6 byte codes if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } // apply mask on first byte c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; // join the rest while (c_len > 1 && i < len) { c = (c << 6) | (buf[i++] & 0x3f); c_len--; } // terminated by end of string? if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } if (c < 0x10000) { utf16buf[out++] = c; } else { c -= 0x10000; utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); utf16buf[out++] = 0xdc00 | (c & 0x3ff); } } return buf2binstring(utf16buf, out); }; // Calculate max possible position in utf8 buffer, // that will not break sequence. If that's not possible // - (very small limits) return max size as is. // // buf[] - utf8 bytes array // max - length limit (mandatory); exports.utf8border = function (buf, max) { var pos; max = max || buf.length; if (max > buf.length) { max = buf.length; } // go back from last position, until start of sequence found pos = max - 1; while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } // Fuckup - very small and broken sequence, // return max, because we should return something anyway. if (pos < 0) { return max; } // If we came to start of buffer - that means vuffer is too small, // return max too. if (pos === 0) { return max; } return (pos + _utf8len[buf[pos]] > max) ? pos : max; }; },{"./common":41}],43:[function(require,module,exports){ // Note: adler32 takes 12% for level 0 and 2% for level 6. // It doesn't worth to make additional optimizationa as in original. // Small size is preferable. function adler32(adler, buf, len, pos) { var s1 = (adler & 0xffff) |0, s2 = ((adler >>> 16) & 0xffff) |0, n = 0; while (len !== 0) { // Set limit ~ twice less than 5552, to keep // s2 in 31-bits, because we force signed ints. // in other case %= will fail. n = len > 2000 ? 2000 : len; len -= n; do { s1 = (s1 + buf[pos++]) |0; s2 = (s2 + s1) |0; } while (--n); s1 %= 65521; s2 %= 65521; } return (s1 | (s2 << 16)) |0; } module.exports = adler32; },{}],44:[function(require,module,exports){ module.exports = { /* Allowed flush values; see deflate() and inflate() below for details */ Z_NO_FLUSH: 0, Z_PARTIAL_FLUSH: 1, Z_SYNC_FLUSH: 2, Z_FULL_FLUSH: 3, Z_FINISH: 4, Z_BLOCK: 5, Z_TREES: 6, /* Return codes for the compression/decompression functions. Negative values * are errors, positive values are used for special but normal events. */ Z_OK: 0, Z_STREAM_END: 1, Z_NEED_DICT: 2, Z_ERRNO: -1, Z_STREAM_ERROR: -2, Z_DATA_ERROR: -3, //Z_MEM_ERROR: -4, Z_BUF_ERROR: -5, //Z_VERSION_ERROR: -6, /* compression levels */ Z_NO_COMPRESSION: 0, Z_BEST_SPEED: 1, Z_BEST_COMPRESSION: 9, Z_DEFAULT_COMPRESSION: -1, Z_FILTERED: 1, Z_HUFFMAN_ONLY: 2, Z_RLE: 3, Z_FIXED: 4, Z_DEFAULT_STRATEGY: 0, /* Possible values of the data_type field (though see inflate()) */ Z_BINARY: 0, Z_TEXT: 1, //Z_ASCII: 1, // = Z_TEXT (deprecated) Z_UNKNOWN: 2, /* The deflate compression method */ Z_DEFLATED: 8 //Z_NULL: null // Use -1 or null inline, depending on var type }; },{}],45:[function(require,module,exports){ // Note: we can't get significant speed boost here. // So write code to minimize size - no pregenerated tables // and array tools dependencies. // Use ordinary array, since untyped makes no boost here function makeTable() { var c, table = []; for (var n = 0; n < 256; n++) { c = n; for (var k = 0; k < 8; k++) { c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } table[n] = c; } return table; } // Create table on load. Just 255 signed longs. Not a problem. var crcTable = makeTable(); function crc32(crc, buf, len, pos) { var t = crcTable, end = pos + len; crc ^= -1; for (var i = pos; i < end; i++) { crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; } return (crc ^ (-1)); // >>> 0; } module.exports = crc32; },{}],46:[function(require,module,exports){ var utils = require('../utils/common'); var trees = require('./trees'); var adler32 = require('./adler32'); var crc32 = require('./crc32'); var msg = require('./messages'); /* Public constants ==========================================================*/ /* ===========================================================================*/ /* Allowed flush values; see deflate() and inflate() below for details */ var Z_NO_FLUSH = 0; var Z_PARTIAL_FLUSH = 1; //var Z_SYNC_FLUSH = 2; var Z_FULL_FLUSH = 3; var Z_FINISH = 4; var Z_BLOCK = 5; //var Z_TREES = 6; /* Return codes for the compression/decompression functions. Negative values * are errors, positive values are used for special but normal events. */ var Z_OK = 0; var Z_STREAM_END = 1; //var Z_NEED_DICT = 2; //var Z_ERRNO = -1; var Z_STREAM_ERROR = -2; var Z_DATA_ERROR = -3; //var Z_MEM_ERROR = -4; var Z_BUF_ERROR = -5; //var Z_VERSION_ERROR = -6; /* compression levels */ //var Z_NO_COMPRESSION = 0; //var Z_BEST_SPEED = 1; //var Z_BEST_COMPRESSION = 9; var Z_DEFAULT_COMPRESSION = -1; var Z_FILTERED = 1; var Z_HUFFMAN_ONLY = 2; var Z_RLE = 3; var Z_FIXED = 4; var Z_DEFAULT_STRATEGY = 0; /* Possible values of the data_type field (though see inflate()) */ //var Z_BINARY = 0; //var Z_TEXT = 1; //var Z_ASCII = 1; // = Z_TEXT var Z_UNKNOWN = 2; /* The deflate compression method */ var Z_DEFLATED = 8; /*============================================================================*/ var MAX_MEM_LEVEL = 9; /* Maximum value for memLevel in deflateInit2 */ var MAX_WBITS = 15; /* 32K LZ77 window */ var DEF_MEM_LEVEL = 8; var LENGTH_CODES = 29; /* number of length codes, not counting the special END_BLOCK code */ var LITERALS = 256; /* number of literal bytes 0..255 */ var L_CODES = LITERALS + 1 + LENGTH_CODES; /* number of Literal or Length codes, including the END_BLOCK code */ var D_CODES = 30; /* number of distance codes */ var BL_CODES = 19; /* number of codes used to transfer the bit lengths */ var HEAP_SIZE = 2 * L_CODES + 1; /* maximum heap size */ var MAX_BITS = 15; /* All codes must not exceed MAX_BITS bits */ var MIN_MATCH = 3; var MAX_MATCH = 258; var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); var PRESET_DICT = 0x20; var INIT_STATE = 42; var EXTRA_STATE = 69; var NAME_STATE = 73; var COMMENT_STATE = 91; var HCRC_STATE = 103; var BUSY_STATE = 113; var FINISH_STATE = 666; var BS_NEED_MORE = 1; /* block not completed, need more input or more output */ var BS_BLOCK_DONE = 2; /* block flush performed */ var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ var BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ var OS_CODE = 0x03; // Unix :) . Don't detect, use this default. function err(strm, errorCode) { strm.msg = msg[errorCode]; return errorCode; } function rank(f) { return ((f) << 1) - ((f) > 4 ? 9 : 0); } function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } /* ========================================================================= * Flush as much pending output as possible. All deflate() output goes * through this function so some applications may wish to modify it * to avoid allocating a large strm->output buffer and copying into it. * (See also read_buf()). */ function flush_pending(strm) { var s = strm.state; //_tr_flush_bits(s); var len = s.pending; if (len > strm.avail_out) { len = strm.avail_out; } if (len === 0) { return; } utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out); strm.next_out += len; s.pending_out += len; strm.total_out += len; strm.avail_out -= len; s.pending -= len; if (s.pending === 0) { s.pending_out = 0; } } function flush_block_only(s, last) { trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); s.block_start = s.strstart; flush_pending(s.strm); } function put_byte(s, b) { s.pending_buf[s.pending++] = b; } /* ========================================================================= * Put a short in the pending buffer. The 16-bit value is put in MSB order. * IN assertion: the stream state is correct and there is enough room in * pending_buf. */ function putShortMSB(s, b) { // put_byte(s, (Byte)(b >> 8)); // put_byte(s, (Byte)(b & 0xff)); s.pending_buf[s.pending++] = (b >>> 8) & 0xff; s.pending_buf[s.pending++] = b & 0xff; } /* =========================================================================== * Read a new buffer from the current input stream, update the adler32 * and total number of bytes read. All deflate() input goes through * this function so some applications may wish to modify it to avoid * allocating a large strm->input buffer and copying from it. * (See also flush_pending()). */ function read_buf(strm, buf, start, size) { var len = strm.avail_in; if (len > size) { len = size; } if (len === 0) { return 0; } strm.avail_in -= len; // zmemcpy(buf, strm->next_in, len); utils.arraySet(buf, strm.input, strm.next_in, len, start); if (strm.state.wrap === 1) { strm.adler = adler32(strm.adler, buf, len, start); } else if (strm.state.wrap === 2) { strm.adler = crc32(strm.adler, buf, len, start); } strm.next_in += len; strm.total_in += len; return len; } /* =========================================================================== * Set match_start to the longest match starting at the given string and * return its length. Matches shorter or equal to prev_length are discarded, * in which case the result is equal to prev_length and match_start is * garbage. * IN assertions: cur_match is the head of the hash chain for the current * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 * OUT assertion: the match length is not greater than s->lookahead. */ function longest_match(s, cur_match) { var chain_length = s.max_chain_length; /* max hash chain length */ var scan = s.strstart; /* current string */ var match; /* matched string */ var len; /* length of current match */ var best_len = s.prev_length; /* best match length so far */ var nice_match = s.nice_match; /* stop if match long enough */ var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; var _win = s.window; // shortcut var wmask = s.w_mask; var prev = s.prev; /* Stop when cur_match becomes <= limit. To simplify the code, * we prevent matches with the string of window index 0. */ var strend = s.strstart + MAX_MATCH; var scan_end1 = _win[scan + best_len - 1]; var scan_end = _win[scan + best_len]; /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. * It is easy to get rid of this optimization if necessary. */ // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); /* Do not waste too much time if we already have a good match: */ if (s.prev_length >= s.good_match) { chain_length >>= 2; } /* Do not look for matches beyond the end of the input. This is necessary * to make deflate deterministic. */ if (nice_match > s.lookahead) { nice_match = s.lookahead; } // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); do { // Assert(cur_match < s->strstart, "no future"); match = cur_match; /* Skip to next match if the match length cannot increase * or if the match length is less than 2. Note that the checks below * for insufficient lookahead only occur occasionally for performance * reasons. Therefore uninitialized memory will be accessed, and * conditional jumps will be made that depend on those values. * However the length of the match is limited to the lookahead, so * the output of deflate is not affected by the uninitialized values. */ if (_win[match + best_len] !== scan_end || _win[match + best_len - 1] !== scan_end1 || _win[match] !== _win[scan] || _win[++match] !== _win[scan + 1]) { continue; } /* The check at best_len-1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that * the hash keys are equal and that HASH_BITS >= 8. */ scan += 2; match++; // Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; * the 256th check will be made at strstart+258. */ do { /*jshint noempty:false*/ } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && scan < strend); // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); len = MAX_MATCH - (strend - scan); scan = strend - MAX_MATCH; if (len > best_len) { s.match_start = cur_match; best_len = len; if (len >= nice_match) { break; } scan_end1 = _win[scan + best_len - 1]; scan_end = _win[scan + best_len]; } } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); if (best_len <= s.lookahead) { return best_len; } return s.lookahead; } /* =========================================================================== * Fill the window when the lookahead becomes insufficient. * Updates strstart and lookahead. * * IN assertion: lookahead < MIN_LOOKAHEAD * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD * At least one byte has been read, or avail_in == 0; reads are * performed for at least two bytes (required for the zip translate_eol * option -- not supported here). */ function fill_window(s) { var _w_size = s.w_size; var p, n, m, more, str; //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); do { more = s.window_size - s.lookahead - s.strstart; // JS ints have 32 bit, block below not needed /* Deal with !@#$% 64K limit: */ //if (sizeof(int) <= 2) { // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { // more = wsize; // // } else if (more == (unsigned)(-1)) { // /* Very unlikely, but possible on 16 bit machine if // * strstart == 0 && lookahead == 1 (input done a byte at time) // */ // more--; // } //} /* If the window is almost full and there is insufficient lookahead, * move the upper half to the lower one to make room in the upper half. */ if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { utils.arraySet(s.window, s.window, _w_size, _w_size, 0); s.match_start -= _w_size; s.strstart -= _w_size; /* we now have strstart >= MAX_DIST */ s.block_start -= _w_size; /* Slide the hash table (could be avoided with 32 bit values at the expense of memory usage). We slide even when level == 0 to keep the hash table consistent if we switch back to level > 0 later. (Using level 0 permanently is not an optimal usage of zlib, so we don't care about this pathological case.) */ n = s.hash_size; p = n; do { m = s.head[--p]; s.head[p] = (m >= _w_size ? m - _w_size : 0); } while (--n); n = _w_size; p = n; do { m = s.prev[--p]; s.prev[p] = (m >= _w_size ? m - _w_size : 0); /* If n is not on any hash chain, prev[n] is garbage but * its value will never be used. */ } while (--n); more += _w_size; } if (s.strm.avail_in === 0) { break; } /* If there was no sliding: * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && * more == window_size - lookahead - strstart * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) * => more >= window_size - 2*WSIZE + 2 * In the BIG_MEM or MMAP case (not yet supported), * window_size == input_size + MIN_LOOKAHEAD && * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. * Otherwise, window_size == 2*WSIZE so more >= 2. * If there was sliding, more >= WSIZE. So in all cases, more >= 2. */ //Assert(more >= 2, "more < 2"); n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); s.lookahead += n; /* Initialize the hash value now that we have some input: */ if (s.lookahead + s.insert >= MIN_MATCH) { str = s.strstart - s.insert; s.ins_h = s.window[str]; /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask; //#if MIN_MATCH != 3 // Call update_hash() MIN_MATCH-3 more times //#endif while (s.insert) { /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; s.prev[str & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = str; str++; s.insert--; if (s.lookahead + s.insert < MIN_MATCH) { break; } } } /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, * but this is not important since only literal bytes will be emitted. */ } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); /* If the WIN_INIT bytes after the end of the current data have never been * written, then zero those bytes in order to avoid memory check reports of * the use of uninitialized (or uninitialised as Julian writes) bytes by * the longest match routines. Update the high water mark for the next * time through here. WIN_INIT is set to MAX_MATCH since the longest match * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. */ // if (s.high_water < s.window_size) { // var curr = s.strstart + s.lookahead; // var init = 0; // // if (s.high_water < curr) { // /* Previous high water mark below current data -- zero WIN_INIT // * bytes or up to end of window, whichever is less. // */ // init = s.window_size - curr; // if (init > WIN_INIT) // init = WIN_INIT; // zmemzero(s->window + curr, (unsigned)init); // s->high_water = curr + init; // } // else if (s->high_water < (ulg)curr + WIN_INIT) { // /* High water mark at or above current data, but below current data // * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up // * to end of window, whichever is less. // */ // init = (ulg)curr + WIN_INIT - s->high_water; // if (init > s->window_size - s->high_water) // init = s->window_size - s->high_water; // zmemzero(s->window + s->high_water, (unsigned)init); // s->high_water += init; // } // } // // Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, // "not enough room for search"); } /* =========================================================================== * Copy without compression as much as possible from the input stream, return * the current block state. * This function does not insert new strings in the dictionary since * uncompressible data is probably not useful. This function is used * only for the level=0 compression option. * NOTE: this function should be optimized to avoid extra copying from * window to pending_buf. */ function deflate_stored(s, flush) { /* Stored blocks are limited to 0xffff bytes, pending_buf is limited * to pending_buf_size, and each stored block has a 5 byte header: */ var max_block_size = 0xffff; if (max_block_size > s.pending_buf_size - 5) { max_block_size = s.pending_buf_size - 5; } /* Copy as much as possible from input to output: */ for (;;) { /* Fill the window as much as possible: */ if (s.lookahead <= 1) { //Assert(s->strstart < s->w_size+MAX_DIST(s) || // s->block_start >= (long)s->w_size, "slide too late"); // if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) || // s.block_start >= s.w_size)) { // throw new Error("slide too late"); // } fill_window(s); if (s.lookahead === 0 && flush === Z_NO_FLUSH) { return BS_NEED_MORE; } if (s.lookahead === 0) { break; } /* flush the current block */ } //Assert(s->block_start >= 0L, "block gone"); // if (s.block_start < 0) throw new Error("block gone"); s.strstart += s.lookahead; s.lookahead = 0; /* Emit a stored block if pending_buf will be full: */ var max_start = s.block_start + max_block_size; if (s.strstart === 0 || s.strstart >= max_start) { /* strstart == 0 is possible when wraparound on 16-bit machine */ s.lookahead = s.strstart - max_start; s.strstart = max_start; /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } /* Flush if we may have to slide, otherwise block_start may become * negative and the data will be gone: */ if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } } s.insert = 0; if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); if (s.strm.avail_out === 0) { return BS_FINISH_STARTED; } /***/ return BS_FINISH_DONE; } if (s.strstart > s.block_start) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } return BS_NEED_MORE; } /* =========================================================================== * Compress as much as possible from the input stream, return the current * block state. * This function does not perform lazy evaluation of matches and inserts * new strings in the dictionary only for unmatched strings or for short * matches. It is used only for the fast compression options. */ function deflate_fast(s, flush) { var hash_head; /* head of the hash chain */ var bflush; /* set if current block must be flushed */ for (;;) { /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes * for the next match, plus MIN_MATCH bytes to insert the * string following the next match. */ if (s.lookahead < MIN_LOOKAHEAD) { fill_window(s); if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { return BS_NEED_MORE; } if (s.lookahead === 0) { break; /* flush the current block */ } } /* Insert the string window[strstart .. strstart+2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = 0/*NIL*/; if (s.lookahead >= MIN_MATCH) { /*** INSERT_STRING(s, s.strstart, hash_head); ***/ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = s.strstart; /***/ } /* Find the longest match, discarding those <= prev_length. * At this point we have always match_length < MIN_MATCH */ if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { /* To simplify the code, we prevent matches with the string * of window index 0 (in particular we have to avoid a match * of the string with itself at the start of the input file). */ s.match_length = longest_match(s, hash_head); /* longest_match() sets match_start */ } if (s.match_length >= MIN_MATCH) { // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only /*** _tr_tally_dist(s, s.strstart - s.match_start, s.match_length - MIN_MATCH, bflush); ***/ bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); s.lookahead -= s.match_length; /* Insert new strings in the hash table only if the match length * is not too large. This saves time but degrades compression. */ if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { s.match_length--; /* string at strstart already in table */ do { s.strstart++; /*** INSERT_STRING(s, s.strstart, hash_head); ***/ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = s.strstart; /***/ /* strstart never exceeds WSIZE-MAX_MATCH, so there are * always MIN_MATCH bytes ahead. */ } while (--s.match_length !== 0); s.strstart++; } else { s.strstart += s.match_length; s.match_length = 0; s.ins_h = s.window[s.strstart]; /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask; //#if MIN_MATCH != 3 // Call UPDATE_HASH() MIN_MATCH-3 more times //#endif /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not * matter since it will be recomputed at next deflate call. */ } } else { /* No match, output a literal byte */ //Tracevv((stderr,"%c", s.window[s.strstart])); /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ bflush = trees._tr_tally(s, 0, s.window[s.strstart]); s.lookahead--; s.strstart++; } if (bflush) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } } s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); if (s.strm.avail_out === 0) { return BS_FINISH_STARTED; } /***/ return BS_FINISH_DONE; } if (s.last_lit) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } return BS_BLOCK_DONE; } /* =========================================================================== * Same as above, but achieves better compression. We use a lazy * evaluation for matches: a match is finally adopted only if there is * no better match at the next window position. */ function deflate_slow(s, flush) { var hash_head; /* head of hash chain */ var bflush; /* set if current block must be flushed */ var max_insert; /* Process the input block. */ for (;;) { /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes * for the next match, plus MIN_MATCH bytes to insert the * string following the next match. */ if (s.lookahead < MIN_LOOKAHEAD) { fill_window(s); if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) { return BS_NEED_MORE; } if (s.lookahead === 0) { break; } /* flush the current block */ } /* Insert the string window[strstart .. strstart+2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = 0/*NIL*/; if (s.lookahead >= MIN_MATCH) { /*** INSERT_STRING(s, s.strstart, hash_head); ***/ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = s.strstart; /***/ } /* Find the longest match, discarding those <= prev_length. */ s.prev_length = s.match_length; s.prev_match = s.match_start; s.match_length = MIN_MATCH - 1; if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { /* To simplify the code, we prevent matches with the string * of window index 0 (in particular we have to avoid a match * of the string with itself at the start of the input file). */ s.match_length = longest_match(s, hash_head); /* longest_match() sets match_start */ if (s.match_length <= 5 && (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { /* If prev_match is also MIN_MATCH, match_start is garbage * but we will ignore the current match anyway. */ s.match_length = MIN_MATCH - 1; } } /* If there was a match at the previous step and the current * match is not better, output the previous match: */ if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { max_insert = s.strstart + s.lookahead - MIN_MATCH; /* Do not insert strings in hash table beyond this. */ //check_match(s, s.strstart-1, s.prev_match, s.prev_length); /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH, bflush);***/ bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); /* Insert in hash table all strings up to the end of the match. * strstart-1 and strstart are already inserted. If there is not * enough lookahead, the last two strings are not inserted in * the hash table. */ s.lookahead -= s.prev_length - 1; s.prev_length -= 2; do { if (++s.strstart <= max_insert) { /*** INSERT_STRING(s, s.strstart, hash_head); ***/ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask; hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = s.strstart; /***/ } } while (--s.prev_length !== 0); s.match_available = 0; s.match_length = MIN_MATCH - 1; s.strstart++; if (bflush) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } } else if (s.match_available) { /* If there was no match at the previous position, output a * single literal. If there was a match but the current match * is longer, truncate the previous match to a single literal. */ //Tracevv((stderr,"%c", s->window[s->strstart-1])); /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); if (bflush) { /*** FLUSH_BLOCK_ONLY(s, 0) ***/ flush_block_only(s, false); /***/ } s.strstart++; s.lookahead--; if (s.strm.avail_out === 0) { return BS_NEED_MORE; } } else { /* There is no previous match to compare with, wait for * the next step to decide. */ s.match_available = 1; s.strstart++; s.lookahead--; } } //Assert (flush != Z_NO_FLUSH, "no flush?"); if (s.match_available) { //Tracevv((stderr,"%c", s->window[s->strstart-1])); /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); s.match_available = 0; } s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); if (s.strm.avail_out === 0) { return BS_FINISH_STARTED; } /***/ return BS_FINISH_DONE; } if (s.last_lit) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } return BS_BLOCK_DONE; } /* =========================================================================== * For Z_RLE, simply look for runs of bytes, generate matches only of distance * one. Do not maintain a hash table. (It will be regenerated if this run of * deflate switches away from Z_RLE.) */ function deflate_rle(s, flush) { var bflush; /* set if current block must be flushed */ var prev; /* byte at distance one to match */ var scan, strend; /* scan goes up to strend for length of run */ var _win = s.window; for (;;) { /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes * for the longest run, plus one for the unrolled loop. */ if (s.lookahead <= MAX_MATCH) { fill_window(s); if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) { return BS_NEED_MORE; } if (s.lookahead === 0) { break; } /* flush the current block */ } /* See how many times the previous byte repeats */ s.match_length = 0; if (s.lookahead >= MIN_MATCH && s.strstart > 0) { scan = s.strstart - 1; prev = _win[scan]; if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { strend = s.strstart + MAX_MATCH; do { /*jshint noempty:false*/ } while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] && scan < strend); s.match_length = MAX_MATCH - (strend - scan); if (s.match_length > s.lookahead) { s.match_length = s.lookahead; } } //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); } /* Emit match if have run of MIN_MATCH or longer, else emit literal */ if (s.match_length >= MIN_MATCH) { //check_match(s, s.strstart, s.strstart - 1, s.match_length); /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH); s.lookahead -= s.match_length; s.strstart += s.match_length; s.match_length = 0; } else { /* No match, output a literal byte */ //Tracevv((stderr,"%c", s->window[s->strstart])); /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ bflush = trees._tr_tally(s, 0, s.window[s.strstart]); s.lookahead--; s.strstart++; } if (bflush) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } } s.insert = 0; if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); if (s.strm.avail_out === 0) { return BS_FINISH_STARTED; } /***/ return BS_FINISH_DONE; } if (s.last_lit) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } return BS_BLOCK_DONE; } /* =========================================================================== * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. * (It will be regenerated if this run of deflate switches away from Huffman.) */ function deflate_huff(s, flush) { var bflush; /* set if current block must be flushed */ for (;;) { /* Make sure that we have a literal to write. */ if (s.lookahead === 0) { fill_window(s); if (s.lookahead === 0) { if (flush === Z_NO_FLUSH) { return BS_NEED_MORE; } break; /* flush the current block */ } } /* Output a literal byte */ s.match_length = 0; //Tracevv((stderr,"%c", s->window[s->strstart])); /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ bflush = trees._tr_tally(s, 0, s.window[s.strstart]); s.lookahead--; s.strstart++; if (bflush) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } } s.insert = 0; if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); if (s.strm.avail_out === 0) { return BS_FINISH_STARTED; } /***/ return BS_FINISH_DONE; } if (s.last_lit) { /*** FLUSH_BLOCK(s, 0); ***/ flush_block_only(s, false); if (s.strm.avail_out === 0) { return BS_NEED_MORE; } /***/ } return BS_BLOCK_DONE; } /* Values for max_lazy_match, good_match and max_chain_length, depending on * the desired pack level (0..9). The values given below have been tuned to * exclude worst case performance for pathological files. Better values may be * found for specific files. */ function Config(good_length, max_lazy, nice_length, max_chain, func) { this.good_length = good_length; this.max_lazy = max_lazy; this.nice_length = nice_length; this.max_chain = max_chain; this.func = func; } var configuration_table; configuration_table = [ /* good lazy nice chain */ new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ new Config(4, 5, 16, 8, deflate_fast), /* 2 */ new Config(4, 6, 32, 32, deflate_fast), /* 3 */ new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ new Config(8, 16, 32, 32, deflate_slow), /* 5 */ new Config(8, 16, 128, 128, deflate_slow), /* 6 */ new Config(8, 32, 128, 256, deflate_slow), /* 7 */ new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ ]; /* =========================================================================== * Initialize the "longest match" routines for a new zlib stream */ function lm_init(s) { s.window_size = 2 * s.w_size; /*** CLEAR_HASH(s); ***/ zero(s.head); // Fill with NIL (= 0); /* Set the default configuration parameters: */ s.max_lazy_match = configuration_table[s.level].max_lazy; s.good_match = configuration_table[s.level].good_length; s.nice_match = configuration_table[s.level].nice_length; s.max_chain_length = configuration_table[s.level].max_chain; s.strstart = 0; s.block_start = 0; s.lookahead = 0; s.insert = 0; s.match_length = s.prev_length = MIN_MATCH - 1; s.match_available = 0; s.ins_h = 0; } function DeflateState() { this.strm = null; /* pointer back to this zlib stream */ this.status = 0; /* as the name implies */ this.pending_buf = null; /* output still pending */ this.pending_buf_size = 0; /* size of pending_buf */ this.pending_out = 0; /* next pending byte to output to the stream */ this.pending = 0; /* nb of bytes in the pending buffer */ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ this.gzhead = null; /* gzip header information to write */ this.gzindex = 0; /* where in extra, name, or comment */ this.method = Z_DEFLATED; /* can only be DEFLATED */ this.last_flush = -1; /* value of flush param for previous deflate call */ this.w_size = 0; /* LZ77 window size (32K by default) */ this.w_bits = 0; /* log2(w_size) (8..16) */ this.w_mask = 0; /* w_size - 1 */ this.window = null; /* Sliding window. Input bytes are read into the second half of the window, * and move to the first half later to keep a dictionary of at least wSize * bytes. With this organization, matches are limited to a distance of * wSize-MAX_MATCH bytes, but this ensures that IO is always * performed with a length multiple of the block size. */ this.window_size = 0; /* Actual size of window: 2*wSize, except when the user input buffer * is directly used as sliding window. */ this.prev = null; /* Link to older string with same hash index. To limit the size of this * array to 64K, this link is maintained only for the last 32K strings. * An index in this array is thus a window index modulo 32K. */ this.head = null; /* Heads of the hash chains or NIL. */ this.ins_h = 0; /* hash index of string to be inserted */ this.hash_size = 0; /* number of elements in hash table */ this.hash_bits = 0; /* log2(hash_size) */ this.hash_mask = 0; /* hash_size-1 */ this.hash_shift = 0; /* Number of bits by which ins_h must be shifted at each input * step. It must be such that after MIN_MATCH steps, the oldest * byte no longer takes part in the hash key, that is: * hash_shift * MIN_MATCH >= hash_bits */ this.block_start = 0; /* Window position at the beginning of the current output block. Gets * negative when the window is moved backwards. */ this.match_length = 0; /* length of best match */ this.prev_match = 0; /* previous match */ this.match_available = 0; /* set if previous match exists */ this.strstart = 0; /* start of string to insert */ this.match_start = 0; /* start of matching string */ this.lookahead = 0; /* number of valid bytes ahead in window */ this.prev_length = 0; /* Length of the best match at previous step. Matches not greater than this * are discarded. This is used in the lazy match evaluation. */ this.max_chain_length = 0; /* To speed up deflation, hash chains are never searched beyond this * length. A higher limit improves compression ratio but degrades the * speed. */ this.max_lazy_match = 0; /* Attempt to find a better match only when the current match is strictly * smaller than this value. This mechanism is used only for compression * levels >= 4. */ // That's alias to max_lazy_match, don't use directly //this.max_insert_length = 0; /* Insert new strings in the hash table only if the match length is not * greater than this length. This saves time but degrades compression. * max_insert_length is used only for compression levels <= 3. */ this.level = 0; /* compression level (1..9) */ this.strategy = 0; /* favor or force Huffman coding*/ this.good_match = 0; /* Use a faster search when the previous match is longer than this */ this.nice_match = 0; /* Stop searching when current match exceeds this */ /* used by trees.c: */ /* Didn't use ct_data typedef below to suppress compiler warning */ // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ // Use flat array of DOUBLE size, with interleaved fata, // because JS does not support effective this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2); this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2); this.bl_tree = new utils.Buf16((2 * BL_CODES + 1) * 2); zero(this.dyn_ltree); zero(this.dyn_dtree); zero(this.bl_tree); this.l_desc = null; /* desc. for literal tree */ this.d_desc = null; /* desc. for distance tree */ this.bl_desc = null; /* desc. for bit length tree */ //ush bl_count[MAX_BITS+1]; this.bl_count = new utils.Buf16(MAX_BITS + 1); /* number of codes at each bit length for an optimal tree */ //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ this.heap = new utils.Buf16(2 * L_CODES + 1); /* heap used to build the Huffman trees */ zero(this.heap); this.heap_len = 0; /* number of elements in the heap */ this.heap_max = 0; /* element of largest frequency */ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. * The same heap array is used to build all trees. */ this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; zero(this.depth); /* Depth of each subtree used as tie breaker for trees of equal frequency */ this.l_buf = 0; /* buffer index for literals or lengths */ this.lit_bufsize = 0; /* Size of match buffer for literals/lengths. There are 4 reasons for * limiting lit_bufsize to 64K: * - frequencies can be kept in 16 bit counters * - if compression is not successful for the first block, all input * data is still in the window so we can still emit a stored block even * when input comes from standard input. (This can also be done for * all blocks if lit_bufsize is not greater than 32K.) * - if compression is not successful for a file smaller than 64K, we can * even emit a stored file instead of a stored block (saving 5 bytes). * This is applicable only for zip (not gzip or zlib). * - creating new Huffman trees less frequently may not provide fast * adaptation to changes in the input data statistics. (Take for * example a binary file with poorly compressible code followed by * a highly compressible string table.) Smaller buffer sizes give * fast adaptation but have of course the overhead of transmitting * trees more frequently. * - I can't count above 4 */ this.last_lit = 0; /* running index in l_buf */ this.d_buf = 0; /* Buffer index for distances. To simplify the code, d_buf and l_buf have * the same number of elements. To use different lengths, an extra flag * array would be necessary. */ this.opt_len = 0; /* bit length of current block with optimal trees */ this.static_len = 0; /* bit length of current block with static trees */ this.matches = 0; /* number of string matches in current block */ this.insert = 0; /* bytes at end of window left to insert */ this.bi_buf = 0; /* Output buffer. bits are inserted starting at the bottom (least * significant bits). */ this.bi_valid = 0; /* Number of valid bits in bi_buf. All bits above the last valid bit * are always zero. */ // Used for window memory init. We safely ignore it for JS. That makes // sense only for pointers and memory check tools. //this.high_water = 0; /* High water mark offset in window for initialized bytes -- bytes above * this are set to zero in order to avoid memory check warnings when * longest match routines access bytes past the input. This is then * updated to the new high water mark. */ } function deflateResetKeep(strm) { var s; if (!strm || !strm.state) { return err(strm, Z_STREAM_ERROR); } strm.total_in = strm.total_out = 0; strm.data_type = Z_UNKNOWN; s = strm.state; s.pending = 0; s.pending_out = 0; if (s.wrap < 0) { s.wrap = -s.wrap; /* was made negative by deflate(..., Z_FINISH); */ } s.status = (s.wrap ? INIT_STATE : BUSY_STATE); strm.adler = (s.wrap === 2) ? 0 // crc32(0, Z_NULL, 0) : 1; // adler32(0, Z_NULL, 0) s.last_flush = Z_NO_FLUSH; trees._tr_init(s); return Z_OK; } function deflateReset(strm) { var ret = deflateResetKeep(strm); if (ret === Z_OK) { lm_init(strm.state); } return ret; } function deflateSetHeader(strm, head) { if (!strm || !strm.state) { return Z_STREAM_ERROR; } if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; } strm.state.gzhead = head; return Z_OK; } function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { if (!strm) { // === Z_NULL return Z_STREAM_ERROR; } var wrap = 1; if (level === Z_DEFAULT_COMPRESSION) { level = 6; } if (windowBits < 0) { /* suppress zlib wrapper */ wrap = 0; windowBits = -windowBits; } else if (windowBits > 15) { wrap = 2; /* write gzip wrapper instead */ windowBits -= 16; } if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED || windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { return err(strm, Z_STREAM_ERROR); } if (windowBits === 8) { windowBits = 9; } /* until 256-byte window bug fixed */ var s = new DeflateState(); strm.state = s; s.strm = strm; s.wrap = wrap; s.gzhead = null; s.w_bits = windowBits; s.w_size = 1 << s.w_bits; s.w_mask = s.w_size - 1; s.hash_bits = memLevel + 7; s.hash_size = 1 << s.hash_bits; s.hash_mask = s.hash_size - 1; s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); s.window = new utils.Buf8(s.w_size * 2); s.head = new utils.Buf16(s.hash_size); s.prev = new utils.Buf16(s.w_size); // Don't need mem init magic for JS. //s.high_water = 0; /* nothing written to s->window yet */ s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ s.pending_buf_size = s.lit_bufsize * 4; s.pending_buf = new utils.Buf8(s.pending_buf_size); s.d_buf = s.lit_bufsize >> 1; s.l_buf = (1 + 2) * s.lit_bufsize; s.level = level; s.strategy = strategy; s.method = method; return deflateReset(strm); } function deflateInit(strm, level) { return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); } function deflate(strm, flush) { var old_flush, s; var beg, val; // for gzip header write only if (!strm || !strm.state || flush > Z_BLOCK || flush < 0) { return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR; } s = strm.state; if (!strm.output || (!strm.input && strm.avail_in !== 0) || (s.status === FINISH_STATE && flush !== Z_FINISH)) { return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR); } s.strm = strm; /* just in case */ old_flush = s.last_flush; s.last_flush = flush; /* Write the header */ if (s.status === INIT_STATE) { if (s.wrap === 2) { // GZIP header strm.adler = 0; //crc32(0L, Z_NULL, 0); put_byte(s, 31); put_byte(s, 139); put_byte(s, 8); if (!s.gzhead) { // s->gzhead == Z_NULL put_byte(s, 0); put_byte(s, 0); put_byte(s, 0); put_byte(s, 0); put_byte(s, 0); put_byte(s, s.level === 9 ? 2 : (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0)); put_byte(s, OS_CODE); s.status = BUSY_STATE; } else { put_byte(s, (s.gzhead.text ? 1 : 0) + (s.gzhead.hcrc ? 2 : 0) + (!s.gzhead.extra ? 0 : 4) + (!s.gzhead.name ? 0 : 8) + (!s.gzhead.comment ? 0 : 16) ); put_byte(s, s.gzhead.time & 0xff); put_byte(s, (s.gzhead.time >> 8) & 0xff); put_byte(s, (s.gzhead.time >> 16) & 0xff); put_byte(s, (s.gzhead.time >> 24) & 0xff); put_byte(s, s.level === 9 ? 2 : (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? 4 : 0)); put_byte(s, s.gzhead.os & 0xff); if (s.gzhead.extra && s.gzhead.extra.length) { put_byte(s, s.gzhead.extra.length & 0xff); put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); } if (s.gzhead.hcrc) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0); } s.gzindex = 0; s.status = EXTRA_STATE; } } else // DEFLATE header { var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8; var level_flags = -1; if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { level_flags = 0; } else if (s.level < 6) { level_flags = 1; } else if (s.level === 6) { level_flags = 2; } else { level_flags = 3; } header |= (level_flags << 6); if (s.strstart !== 0) { header |= PRESET_DICT; } header += 31 - (header % 31); s.status = BUSY_STATE; putShortMSB(s, header); /* Save the adler32 of the preset dictionary: */ if (s.strstart !== 0) { putShortMSB(s, strm.adler >>> 16); putShortMSB(s, strm.adler & 0xffff); } strm.adler = 1; // adler32(0L, Z_NULL, 0); } } //#ifdef GZIP if (s.status === EXTRA_STATE) { if (s.gzhead.extra/* != Z_NULL*/) { beg = s.pending; /* start of bytes to update crc */ while (s.gzindex < (s.gzhead.extra.length & 0xffff)) { if (s.pending === s.pending_buf_size) { if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } flush_pending(strm); beg = s.pending; if (s.pending === s.pending_buf_size) { break; } } put_byte(s, s.gzhead.extra[s.gzindex] & 0xff); s.gzindex++; } if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } if (s.gzindex === s.gzhead.extra.length) { s.gzindex = 0; s.status = NAME_STATE; } } else { s.status = NAME_STATE; } } if (s.status === NAME_STATE) { if (s.gzhead.name/* != Z_NULL*/) { beg = s.pending; /* start of bytes to update crc */ //int val; do { if (s.pending === s.pending_buf_size) { if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } flush_pending(strm); beg = s.pending; if (s.pending === s.pending_buf_size) { val = 1; break; } } // JS specific: little magic to add zero terminator to end of string if (s.gzindex < s.gzhead.name.length) { val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; } else { val = 0; } put_byte(s, val); } while (val !== 0); if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } if (val === 0) { s.gzindex = 0; s.status = COMMENT_STATE; } } else { s.status = COMMENT_STATE; } } if (s.status === COMMENT_STATE) { if (s.gzhead.comment/* != Z_NULL*/) { beg = s.pending; /* start of bytes to update crc */ //int val; do { if (s.pending === s.pending_buf_size) { if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } flush_pending(strm); beg = s.pending; if (s.pending === s.pending_buf_size) { val = 1; break; } } // JS specific: little magic to add zero terminator to end of string if (s.gzindex < s.gzhead.comment.length) { val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; } else { val = 0; } put_byte(s, val); } while (val !== 0); if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } if (val === 0) { s.status = HCRC_STATE; } } else { s.status = HCRC_STATE; } } if (s.status === HCRC_STATE) { if (s.gzhead.hcrc) { if (s.pending + 2 > s.pending_buf_size) { flush_pending(strm); } if (s.pending + 2 <= s.pending_buf_size) { put_byte(s, strm.adler & 0xff); put_byte(s, (strm.adler >> 8) & 0xff); strm.adler = 0; //crc32(0L, Z_NULL, 0); s.status = BUSY_STATE; } } else { s.status = BUSY_STATE; } } //#endif /* Flush as much pending output as possible */ if (s.pending !== 0) { flush_pending(strm); if (strm.avail_out === 0) { /* Since avail_out is 0, deflate will be called again with * more output space, but possibly with both pending and * avail_in equal to zero. There won't be anything to do, * but this is not an error situation so make sure we * return OK instead of BUF_ERROR at next call of deflate: */ s.last_flush = -1; return Z_OK; } /* Make sure there is something to do and avoid duplicate consecutive * flushes. For repeated and useless calls with Z_FINISH, we keep * returning Z_STREAM_END instead of Z_BUF_ERROR. */ } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && flush !== Z_FINISH) { return err(strm, Z_BUF_ERROR); } /* User must not provide more input after the first FINISH: */ if (s.status === FINISH_STATE && strm.avail_in !== 0) { return err(strm, Z_BUF_ERROR); } /* Start a new block or continue the current one. */ if (strm.avail_in !== 0 || s.lookahead !== 0 || (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) { var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) : (s.strategy === Z_RLE ? deflate_rle(s, flush) : configuration_table[s.level].func(s, flush)); if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { s.status = FINISH_STATE; } if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { if (strm.avail_out === 0) { s.last_flush = -1; /* avoid BUF_ERROR next call, see above */ } return Z_OK; /* If flush != Z_NO_FLUSH && avail_out == 0, the next call * of deflate should use the same flush parameter to make sure * that the flush is complete. So we don't have to output an * empty block here, this will be done at next call. This also * ensures that for a very small output buffer, we emit at most * one empty block. */ } if (bstate === BS_BLOCK_DONE) { if (flush === Z_PARTIAL_FLUSH) { trees._tr_align(s); } else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ trees._tr_stored_block(s, 0, 0, false); /* For a full flush, this empty block will be recognized * as a special marker by inflate_sync(). */ if (flush === Z_FULL_FLUSH) { /*** CLEAR_HASH(s); ***/ /* forget history */ zero(s.head); // Fill with NIL (= 0); if (s.lookahead === 0) { s.strstart = 0; s.block_start = 0; s.insert = 0; } } } flush_pending(strm); if (strm.avail_out === 0) { s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ return Z_OK; } } } //Assert(strm->avail_out > 0, "bug2"); //if (strm.avail_out <= 0) { throw new Error("bug2");} if (flush !== Z_FINISH) { return Z_OK; } if (s.wrap <= 0) { return Z_STREAM_END; } /* Write the trailer */ if (s.wrap === 2) { put_byte(s, strm.adler & 0xff); put_byte(s, (strm.adler >> 8) & 0xff); put_byte(s, (strm.adler >> 16) & 0xff); put_byte(s, (strm.adler >> 24) & 0xff); put_byte(s, strm.total_in & 0xff); put_byte(s, (strm.total_in >> 8) & 0xff); put_byte(s, (strm.total_in >> 16) & 0xff); put_byte(s, (strm.total_in >> 24) & 0xff); } else { putShortMSB(s, strm.adler >>> 16); putShortMSB(s, strm.adler & 0xffff); } flush_pending(strm); /* If avail_out is zero, the application will call deflate again * to flush the rest. */ if (s.wrap > 0) { s.wrap = -s.wrap; } /* write the trailer only once! */ return s.pending !== 0 ? Z_OK : Z_STREAM_END; } function deflateEnd(strm) { var status; if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { return Z_STREAM_ERROR; } status = strm.state.status; if (status !== INIT_STATE && status !== EXTRA_STATE && status !== NAME_STATE && status !== COMMENT_STATE && status !== HCRC_STATE && status !== BUSY_STATE && status !== FINISH_STATE ) { return err(strm, Z_STREAM_ERROR); } strm.state = null; return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; } /* ========================================================================= * Initializes the compression dictionary from the given byte * sequence without producing any compressed output. */ function deflateSetDictionary(strm, dictionary) { var dictLength = dictionary.length; var s; var str, n; var wrap; var avail; var next; var input; var tmpDict; if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { return Z_STREAM_ERROR; } s = strm.state; wrap = s.wrap; if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { return Z_STREAM_ERROR; } /* when using zlib wrappers, compute Adler-32 for provided dictionary */ if (wrap === 1) { /* adler32(strm->adler, dictionary, dictLength); */ strm.adler = adler32(strm.adler, dictionary, dictLength, 0); } s.wrap = 0; /* avoid computing Adler-32 in read_buf */ /* if dictionary would fill window, just replace the history */ if (dictLength >= s.w_size) { if (wrap === 0) { /* already empty otherwise */ /*** CLEAR_HASH(s); ***/ zero(s.head); // Fill with NIL (= 0); s.strstart = 0; s.block_start = 0; s.insert = 0; } /* use the tail */ // dictionary = dictionary.slice(dictLength - s.w_size); tmpDict = new utils.Buf8(s.w_size); utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0); dictionary = tmpDict; dictLength = s.w_size; } /* insert dictionary into window and hash */ avail = strm.avail_in; next = strm.next_in; input = strm.input; strm.avail_in = dictLength; strm.next_in = 0; strm.input = dictionary; fill_window(s); while (s.lookahead >= MIN_MATCH) { str = s.strstart; n = s.lookahead - (MIN_MATCH - 1); do { /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; s.prev[str & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = str; str++; } while (--n); s.strstart = str; s.lookahead = MIN_MATCH - 1; fill_window(s); } s.strstart += s.lookahead; s.block_start = s.strstart; s.insert = s.lookahead; s.lookahead = 0; s.match_length = s.prev_length = MIN_MATCH - 1; s.match_available = 0; strm.next_in = next; strm.input = input; strm.avail_in = avail; s.wrap = wrap; return Z_OK; } exports.deflateInit = deflateInit; exports.deflateInit2 = deflateInit2; exports.deflateReset = deflateReset; exports.deflateResetKeep = deflateResetKeep; exports.deflateSetHeader = deflateSetHeader; exports.deflate = deflate; exports.deflateEnd = deflateEnd; exports.deflateSetDictionary = deflateSetDictionary; exports.deflateInfo = 'pako deflate (from Nodeca project)'; /* Not implemented exports.deflateBound = deflateBound; exports.deflateCopy = deflateCopy; exports.deflateParams = deflateParams; exports.deflatePending = deflatePending; exports.deflatePrime = deflatePrime; exports.deflateTune = deflateTune; */ },{"../utils/common":41,"./adler32":43,"./crc32":45,"./messages":51,"./trees":52}],47:[function(require,module,exports){ function GZheader() { /* true if compressed data believed to be text */ this.text = 0; /* modification time */ this.time = 0; /* extra flags (not used when writing a gzip file) */ this.xflags = 0; /* operating system */ this.os = 0; /* pointer to extra field or Z_NULL if none */ this.extra = null; /* extra field length (valid if extra != Z_NULL) */ this.extra_len = 0; // Actually, we don't need it in JS, // but leave for few code modifications // // Setup limits is not necessary because in js we should not preallocate memory // for inflate use constant limit in 65536 bytes // /* space at extra (only when reading header) */ // this.extra_max = 0; /* pointer to zero-terminated file name or Z_NULL */ this.name = ''; /* space at name (only when reading header) */ // this.name_max = 0; /* pointer to zero-terminated comment or Z_NULL */ this.comment = ''; /* space at comment (only when reading header) */ // this.comm_max = 0; /* true if there was or will be a header crc */ this.hcrc = 0; /* true when done reading gzip header (not used when writing a gzip file) */ this.done = false; } module.exports = GZheader; },{}],48:[function(require,module,exports){ // See state defs from inflate.js var BAD = 30; /* got a data error -- remain here until reset */ var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ /* Decode literal, length, and distance codes and write out the resulting literal and match bytes until either not enough input or output is available, an end-of-block is encountered, or a data error is encountered. When large enough input and output buffers are supplied to inflate(), for example, a 16K input buffer and a 64K output buffer, more than 95% of the inflate execution time is spent in this routine. Entry assumptions: state.mode === LEN strm.avail_in >= 6 strm.avail_out >= 258 start >= strm.avail_out state.bits < 8 On return, state.mode is one of: LEN -- ran out of enough output space or enough available input TYPE -- reached end of block code, inflate() to interpret next block BAD -- error in block data Notes: - The maximum input bits used by a length/distance pair is 15 bits for the length code, 5 bits for the length extra, 15 bits for the distance code, and 13 bits for the distance extra. This totals 48 bits, or six bytes. Therefore if strm.avail_in >= 6, then there is enough input to avoid checking for available input while decoding. - The maximum bytes that a single length/distance pair can output is 258 bytes, which is the maximum length that can be coded. inflate_fast() requires strm.avail_out >= 258 for each loop to avoid checking for output space. */ module.exports = function inflate_fast(strm, start) { var state; var _in; /* local strm.input */ var last; /* have enough input while in < last */ var _out; /* local strm.output */ var beg; /* inflate()'s initial strm.output */ var end; /* while out < end, enough space available */ //#ifdef INFLATE_STRICT var dmax; /* maximum distance from zlib header */ //#endif var wsize; /* window size or zero if not using window */ var whave; /* valid bytes in the window */ var wnext; /* window write index */ // Use `s_window` instead `window`, avoid conflict with instrumentation tools var s_window; /* allocated sliding window, if wsize != 0 */ var hold; /* local strm.hold */ var bits; /* local strm.bits */ var lcode; /* local strm.lencode */ var dcode; /* local strm.distcode */ var lmask; /* mask for first level of length codes */ var dmask; /* mask for first level of distance codes */ var here; /* retrieved table entry */ var op; /* code bits, operation, extra bits, or */ /* window position, window bytes to copy */ var len; /* match length, unused bytes */ var dist; /* match distance */ var from; /* where to copy match from */ var from_source; var input, output; // JS specific, because we have no pointers /* copy state to local variables */ state = strm.state; //here = state.here; _in = strm.next_in; input = strm.input; last = _in + (strm.avail_in - 5); _out = strm.next_out; output = strm.output; beg = _out - (start - strm.avail_out); end = _out + (strm.avail_out - 257); //#ifdef INFLATE_STRICT dmax = state.dmax; //#endif wsize = state.wsize; whave = state.whave; wnext = state.wnext; s_window = state.window; hold = state.hold; bits = state.bits; lcode = state.lencode; dcode = state.distcode; lmask = (1 << state.lenbits) - 1; dmask = (1 << state.distbits) - 1; /* decode literals and length/distances until end-of-block or not enough input data or output space */ top: do { if (bits < 15) { hold += input[_in++] << bits; bits += 8; hold += input[_in++] << bits; bits += 8; } here = lcode[hold & lmask]; dolen: for (;;) { // Goto emulation op = here >>> 24/*here.bits*/; hold >>>= op; bits -= op; op = (here >>> 16) & 0xff/*here.op*/; if (op === 0) { /* literal */ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? // "inflate: literal '%c'\n" : // "inflate: literal 0x%02x\n", here.val)); output[_out++] = here & 0xffff/*here.val*/; } else if (op & 16) { /* length base */ len = here & 0xffff/*here.val*/; op &= 15; /* number of extra bits */ if (op) { if (bits < op) { hold += input[_in++] << bits; bits += 8; } len += hold & ((1 << op) - 1); hold >>>= op; bits -= op; } //Tracevv((stderr, "inflate: length %u\n", len)); if (bits < 15) { hold += input[_in++] << bits; bits += 8; hold += input[_in++] << bits; bits += 8; } here = dcode[hold & dmask]; dodist: for (;;) { // goto emulation op = here >>> 24/*here.bits*/; hold >>>= op; bits -= op; op = (here >>> 16) & 0xff/*here.op*/; if (op & 16) { /* distance base */ dist = here & 0xffff/*here.val*/; op &= 15; /* number of extra bits */ if (bits < op) { hold += input[_in++] << bits; bits += 8; if (bits < op) { hold += input[_in++] << bits; bits += 8; } } dist += hold & ((1 << op) - 1); //#ifdef INFLATE_STRICT if (dist > dmax) { strm.msg = 'invalid distance too far back'; state.mode = BAD; break top; } //#endif hold >>>= op; bits -= op; //Tracevv((stderr, "inflate: distance %u\n", dist)); op = _out - beg; /* max distance in output */ if (dist > op) { /* see if copy from window */ op = dist - op; /* distance back in window */ if (op > whave) { if (state.sane) { strm.msg = 'invalid distance too far back'; state.mode = BAD; break top; } // (!) This block is disabled in zlib defailts, // don't enable it for binary compatibility //#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR // if (len <= op - whave) { // do { // output[_out++] = 0; // } while (--len); // continue top; // } // len -= op - whave; // do { // output[_out++] = 0; // } while (--op > whave); // if (op === 0) { // from = _out - dist; // do { // output[_out++] = output[from++]; // } while (--len); // continue top; // } //#endif } from = 0; // window index from_source = s_window; if (wnext === 0) { /* very common case */ from += wsize - op; if (op < len) { /* some from window */ len -= op; do { output[_out++] = s_window[from++]; } while (--op); from = _out - dist; /* rest from output */ from_source = output; } } else if (wnext < op) { /* wrap around window */ from += wsize + wnext - op; op -= wnext; if (op < len) { /* some from end of window */ len -= op; do { output[_out++] = s_window[from++]; } while (--op); from = 0; if (wnext < len) { /* some from start of window */ op = wnext; len -= op; do { output[_out++] = s_window[from++]; } while (--op); from = _out - dist; /* rest from output */ from_source = output; } } } else { /* contiguous in window */ from += wnext - op; if (op < len) { /* some from window */ len -= op; do { output[_out++] = s_window[from++]; } while (--op); from = _out - dist; /* rest from output */ from_source = output; } } while (len > 2) { output[_out++] = from_source[from++]; output[_out++] = from_source[from++]; output[_out++] = from_source[from++]; len -= 3; } if (len) { output[_out++] = from_source[from++]; if (len > 1) { output[_out++] = from_source[from++]; } } } else { from = _out - dist; /* copy direct from output */ do { /* minimum length is three */ output[_out++] = output[from++]; output[_out++] = output[from++]; output[_out++] = output[from++]; len -= 3; } while (len > 2); if (len) { output[_out++] = output[from++]; if (len > 1) { output[_out++] = output[from++]; } } } } else if ((op & 64) === 0) { /* 2nd level distance code */ here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; continue dodist; } else { strm.msg = 'invalid distance code'; state.mode = BAD; break top; } break; // need to emulate goto via "continue" } } else if ((op & 64) === 0) { /* 2nd level length code */ here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; continue dolen; } else if (op & 32) { /* end-of-block */ //Tracevv((stderr, "inflate: end of block\n")); state.mode = TYPE; break top; } else { strm.msg = 'invalid literal/length code'; state.mode = BAD; break top; } break; // need to emulate goto via "continue" } } while (_in < last && _out < end); /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ len = bits >> 3; _in -= len; bits -= len << 3; hold &= (1 << bits) - 1; /* update state and return */ strm.next_in = _in; strm.next_out = _out; strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); state.hold = hold; state.bits = bits; return; }; },{}],49:[function(require,module,exports){ var utils = require('../utils/common'); var adler32 = require('./adler32'); var crc32 = require('./crc32'); var inflate_fast = require('./inffast'); var inflate_table = require('./inftrees'); var CODES = 0; var LENS = 1; var DISTS = 2; /* Public constants ==========================================================*/ /* ===========================================================================*/ /* Allowed flush values; see deflate() and inflate() below for details */ //var Z_NO_FLUSH = 0; //var Z_PARTIAL_FLUSH = 1; //var Z_SYNC_FLUSH = 2; //var Z_FULL_FLUSH = 3; var Z_FINISH = 4; var Z_BLOCK = 5; var Z_TREES = 6; /* Return codes for the compression/decompression functions. Negative values * are errors, positive values are used for special but normal events. */ var Z_OK = 0; var Z_STREAM_END = 1; var Z_NEED_DICT = 2; //var Z_ERRNO = -1; var Z_STREAM_ERROR = -2; var Z_DATA_ERROR = -3; var Z_MEM_ERROR = -4; var Z_BUF_ERROR = -5; //var Z_VERSION_ERROR = -6; /* The deflate compression method */ var Z_DEFLATED = 8; /* STATES ====================================================================*/ /* ===========================================================================*/ var HEAD = 1; /* i: waiting for magic header */ var FLAGS = 2; /* i: waiting for method and flags (gzip) */ var TIME = 3; /* i: waiting for modification time (gzip) */ var OS = 4; /* i: waiting for extra flags and operating system (gzip) */ var EXLEN = 5; /* i: waiting for extra length (gzip) */ var EXTRA = 6; /* i: waiting for extra bytes (gzip) */ var NAME = 7; /* i: waiting for end of file name (gzip) */ var COMMENT = 8; /* i: waiting for end of comment (gzip) */ var HCRC = 9; /* i: waiting for header crc (gzip) */ var DICTID = 10; /* i: waiting for dictionary check value */ var DICT = 11; /* waiting for inflateSetDictionary() call */ var TYPE = 12; /* i: waiting for type bits, including last-flag bit */ var TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */ var STORED = 14; /* i: waiting for stored size (length and complement) */ var COPY_ = 15; /* i/o: same as COPY below, but only first time in */ var COPY = 16; /* i/o: waiting for input or output to copy stored block */ var TABLE = 17; /* i: waiting for dynamic block table lengths */ var LENLENS = 18; /* i: waiting for code length code lengths */ var CODELENS = 19; /* i: waiting for length/lit and distance code lengths */ var LEN_ = 20; /* i: same as LEN below, but only first time in */ var LEN = 21; /* i: waiting for length/lit/eob code */ var LENEXT = 22; /* i: waiting for length extra bits */ var DIST = 23; /* i: waiting for distance code */ var DISTEXT = 24; /* i: waiting for distance extra bits */ var MATCH = 25; /* o: waiting for output space to copy string */ var LIT = 26; /* o: waiting for output space to write literal */ var CHECK = 27; /* i: waiting for 32-bit check value */ var LENGTH = 28; /* i: waiting for 32-bit length (gzip) */ var DONE = 29; /* finished check, done -- remain here until reset */ var BAD = 30; /* got a data error -- remain here until reset */ var MEM = 31; /* got an inflate() memory error -- remain here until reset */ var SYNC = 32; /* looking for synchronization bytes to restart inflate() */ /* ===========================================================================*/ var ENOUGH_LENS = 852; var ENOUGH_DISTS = 592; //var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); var MAX_WBITS = 15; /* 32K LZ77 window */ var DEF_WBITS = MAX_WBITS; function zswap32(q) { return (((q >>> 24) & 0xff) + ((q >>> 8) & 0xff00) + ((q & 0xff00) << 8) + ((q & 0xff) << 24)); } function InflateState() { this.mode = 0; /* current inflate mode */ this.last = false; /* true if processing last block */ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ this.havedict = false; /* true if dictionary provided */ this.flags = 0; /* gzip header method and flags (0 if zlib) */ this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ this.check = 0; /* protected copy of check value */ this.total = 0; /* protected copy of output count */ // TODO: may be {} this.head = null; /* where to save gzip header information */ /* sliding window */ this.wbits = 0; /* log base 2 of requested window size */ this.wsize = 0; /* window size or zero if not using window */ this.whave = 0; /* valid bytes in the window */ this.wnext = 0; /* window write index */ this.window = null; /* allocated sliding window, if needed */ /* bit accumulator */ this.hold = 0; /* input bit accumulator */ this.bits = 0; /* number of bits in "in" */ /* for string and stored block copying */ this.length = 0; /* literal or length of data to copy */ this.offset = 0; /* distance back to copy string from */ /* for table and code decoding */ this.extra = 0; /* extra bits needed */ /* fixed and dynamic code tables */ this.lencode = null; /* starting table for length/literal codes */ this.distcode = null; /* starting table for distance codes */ this.lenbits = 0; /* index bits for lencode */ this.distbits = 0; /* index bits for distcode */ /* dynamic table building */ this.ncode = 0; /* number of code length code lengths */ this.nlen = 0; /* number of length code lengths */ this.ndist = 0; /* number of distance code lengths */ this.have = 0; /* number of code lengths in lens[] */ this.next = null; /* next available space in codes[] */ this.lens = new utils.Buf16(320); /* temporary storage for code lengths */ this.work = new utils.Buf16(288); /* work area for code table building */ /* because we don't have pointers in js, we use lencode and distcode directly as buffers so we don't need codes */ //this.codes = new utils.Buf32(ENOUGH); /* space for code tables */ this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ this.distdyn = null; /* dynamic table for distance codes (JS specific) */ this.sane = 0; /* if false, allow invalid distance too far */ this.back = 0; /* bits back of last unprocessed length/lit */ this.was = 0; /* initial length of match */ } function inflateResetKeep(strm) { var state; if (!strm || !strm.state) { return Z_STREAM_ERROR; } state = strm.state; strm.total_in = strm.total_out = state.total = 0; strm.msg = ''; /*Z_NULL*/ if (state.wrap) { /* to support ill-conceived Java test suite */ strm.adler = state.wrap & 1; } state.mode = HEAD; state.last = 0; state.havedict = 0; state.dmax = 32768; state.head = null/*Z_NULL*/; state.hold = 0; state.bits = 0; //state.lencode = state.distcode = state.next = state.codes; state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS); state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS); state.sane = 1; state.back = -1; //Tracev((stderr, "inflate: reset\n")); return Z_OK; } function inflateReset(strm) { var state; if (!strm || !strm.state) { return Z_STREAM_ERROR; } state = strm.state; state.wsize = 0; state.whave = 0; state.wnext = 0; return inflateResetKeep(strm); } function inflateReset2(strm, windowBits) { var wrap; var state; /* get the state */ if (!strm || !strm.state) { return Z_STREAM_ERROR; } state = strm.state; /* extract wrap request from windowBits parameter */ if (windowBits < 0) { wrap = 0; windowBits = -windowBits; } else { wrap = (windowBits >> 4) + 1; if (windowBits < 48) { windowBits &= 15; } } /* set number of window bits, free window if different */ if (windowBits && (windowBits < 8 || windowBits > 15)) { return Z_STREAM_ERROR; } if (state.window !== null && state.wbits !== windowBits) { state.window = null; } /* update state and reset the rest of it */ state.wrap = wrap; state.wbits = windowBits; return inflateReset(strm); } function inflateInit2(strm, windowBits) { var ret; var state; if (!strm) { return Z_STREAM_ERROR; } //strm.msg = Z_NULL; /* in case we return an error */ state = new InflateState(); //if (state === Z_NULL) return Z_MEM_ERROR; //Tracev((stderr, "inflate: allocated\n")); strm.state = state; state.window = null/*Z_NULL*/; ret = inflateReset2(strm, windowBits); if (ret !== Z_OK) { strm.state = null/*Z_NULL*/; } return ret; } function inflateInit(strm) { return inflateInit2(strm, DEF_WBITS); } /* Return state with length and distance decoding tables and index sizes set to fixed code decoding. Normally this returns fixed tables from inffixed.h. If BUILDFIXED is defined, then instead this routine builds the tables the first time it's called, and returns those tables the first time and thereafter. This reduces the size of the code by about 2K bytes, in exchange for a little execution time. However, BUILDFIXED should not be used for threaded applications, since the rewriting of the tables and virgin may not be thread-safe. */ var virgin = true; var lenfix, distfix; // We have no pointers in JS, so keep tables separate function fixedtables(state) { /* build fixed huffman tables if first call (may not be thread safe) */ if (virgin) { var sym; lenfix = new utils.Buf32(512); distfix = new utils.Buf32(32); /* literal/length table */ sym = 0; while (sym < 144) { state.lens[sym++] = 8; } while (sym < 256) { state.lens[sym++] = 9; } while (sym < 280) { state.lens[sym++] = 7; } while (sym < 288) { state.lens[sym++] = 8; } inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); /* distance table */ sym = 0; while (sym < 32) { state.lens[sym++] = 5; } inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); /* do this just once */ virgin = false; } state.lencode = lenfix; state.lenbits = 9; state.distcode = distfix; state.distbits = 5; } /* Update the window with the last wsize (normally 32K) bytes written before returning. If window does not exist yet, create it. This is only called when a window is already in use, or when output has been written during this inflate call, but the end of the deflate stream has not been reached yet. It is also called to create a window for dictionary data when a dictionary is loaded. Providing output buffers larger than 32K to inflate() should provide a speed advantage, since only the last 32K of output is copied to the sliding window upon return from inflate(), and since all distances after the first 32K of output will fall in the output data, making match copies simpler and faster. The advantage may be dependent on the size of the processor's data caches. */ function updatewindow(strm, src, end, copy) { var dist; var state = strm.state; /* if it hasn't been done already, allocate space for the window */ if (state.window === null) { state.wsize = 1 << state.wbits; state.wnext = 0; state.whave = 0; state.window = new utils.Buf8(state.wsize); } /* copy state->wsize or less output bytes into the circular window */ if (copy >= state.wsize) { utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0); state.wnext = 0; state.whave = state.wsize; } else { dist = state.wsize - state.wnext; if (dist > copy) { dist = copy; } //zmemcpy(state->window + state->wnext, end - copy, dist); utils.arraySet(state.window, src, end - copy, dist, state.wnext); copy -= dist; if (copy) { //zmemcpy(state->window, end - copy, copy); utils.arraySet(state.window, src, end - copy, copy, 0); state.wnext = copy; state.whave = state.wsize; } else { state.wnext += dist; if (state.wnext === state.wsize) { state.wnext = 0; } if (state.whave < state.wsize) { state.whave += dist; } } } return 0; } function inflate(strm, flush) { var state; var input, output; // input/output buffers var next; /* next input INDEX */ var put; /* next output INDEX */ var have, left; /* available input and output */ var hold; /* bit buffer */ var bits; /* bits in bit buffer */ var _in, _out; /* save starting available input and output */ var copy; /* number of stored or match bytes to copy */ var from; /* where to copy match bytes from */ var from_source; var here = 0; /* current decoding table entry */ var here_bits, here_op, here_val; // paked "here" denormalized (JS specific) //var last; /* parent table entry */ var last_bits, last_op, last_val; // paked "last" denormalized (JS specific) var len; /* length to copy for repeats, bits to drop */ var ret; /* return code */ var hbuf = new utils.Buf8(4); /* buffer for gzip header crc calculation */ var opts; var n; // temporary var for NEED_BITS var order = /* permutation of code lengths */ [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; if (!strm || !strm.state || !strm.output || (!strm.input && strm.avail_in !== 0)) { return Z_STREAM_ERROR; } state = strm.state; if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ //--- LOAD() --- put = strm.next_out; output = strm.output; left = strm.avail_out; next = strm.next_in; input = strm.input; have = strm.avail_in; hold = state.hold; bits = state.bits; //--- _in = have; _out = left; ret = Z_OK; inf_leave: // goto emulation for (;;) { switch (state.mode) { case HEAD: if (state.wrap === 0) { state.mode = TYPEDO; break; } //=== NEEDBITS(16); while (bits < 16) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ state.check = 0/*crc32(0L, Z_NULL, 0)*/; //=== CRC2(state.check, hold); hbuf[0] = hold & 0xff; hbuf[1] = (hold >>> 8) & 0xff; state.check = crc32(state.check, hbuf, 2, 0); //===// //=== INITBITS(); hold = 0; bits = 0; //===// state.mode = FLAGS; break; } state.flags = 0; /* expect zlib header */ if (state.head) { state.head.done = false; } if (!(state.wrap & 1) || /* check if zlib header allowed */ (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { strm.msg = 'incorrect header check'; state.mode = BAD; break; } if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { strm.msg = 'unknown compression method'; state.mode = BAD; break; } //--- DROPBITS(4) ---// hold >>>= 4; bits -= 4; //---// len = (hold & 0x0f)/*BITS(4)*/ + 8; if (state.wbits === 0) { state.wbits = len; } else if (len > state.wbits) { strm.msg = 'invalid window size'; state.mode = BAD; break; } state.dmax = 1 << len; //Tracev((stderr, "inflate: zlib header ok\n")); strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; state.mode = hold & 0x200 ? DICTID : TYPE; //=== INITBITS(); hold = 0; bits = 0; //===// break; case FLAGS: //=== NEEDBITS(16); */ while (bits < 16) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.flags = hold; if ((state.flags & 0xff) !== Z_DEFLATED) { strm.msg = 'unknown compression method'; state.mode = BAD; break; } if (state.flags & 0xe000) { strm.msg = 'unknown header flags set'; state.mode = BAD; break; } if (state.head) { state.head.text = ((hold >> 8) & 1); } if (state.flags & 0x0200) { //=== CRC2(state.check, hold); hbuf[0] = hold & 0xff; hbuf[1] = (hold >>> 8) & 0xff; state.check = crc32(state.check, hbuf, 2, 0); //===// } //=== INITBITS(); hold = 0; bits = 0; //===// state.mode = TIME; /* falls through */ case TIME: //=== NEEDBITS(32); */ while (bits < 32) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// if (state.head) { state.head.time = hold; } if (state.flags & 0x0200) { //=== CRC4(state.check, hold) hbuf[0] = hold & 0xff; hbuf[1] = (hold >>> 8) & 0xff; hbuf[2] = (hold >>> 16) & 0xff; hbuf[3] = (hold >>> 24) & 0xff; state.check = crc32(state.check, hbuf, 4, 0); //=== } //=== INITBITS(); hold = 0; bits = 0; //===// state.mode = OS; /* falls through */ case OS: //=== NEEDBITS(16); */ while (bits < 16) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// if (state.head) { state.head.xflags = (hold & 0xff); state.head.os = (hold >> 8); } if (state.flags & 0x0200) { //=== CRC2(state.check, hold); hbuf[0] = hold & 0xff; hbuf[1] = (hold >>> 8) & 0xff; state.check = crc32(state.check, hbuf, 2, 0); //===// } //=== INITBITS(); hold = 0; bits = 0; //===// state.mode = EXLEN; /* falls through */ case EXLEN: if (state.flags & 0x0400) { //=== NEEDBITS(16); */ while (bits < 16) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.length = hold; if (state.head) { state.head.extra_len = hold; } if (state.flags & 0x0200) { //=== CRC2(state.check, hold); hbuf[0] = hold & 0xff; hbuf[1] = (hold >>> 8) & 0xff; state.check = crc32(state.check, hbuf, 2, 0); //===// } //=== INITBITS(); hold = 0; bits = 0; //===// } else if (state.head) { state.head.extra = null/*Z_NULL*/; } state.mode = EXTRA; /* falls through */ case EXTRA: if (state.flags & 0x0400) { copy = state.length; if (copy > have) { copy = have; } if (copy) { if (state.head) { len = state.head.extra_len - state.length; if (!state.head.extra) { // Use untyped array for more conveniend processing later state.head.extra = new Array(state.head.extra_len); } utils.arraySet( state.head.extra, input, next, // extra field is limited to 65536 bytes // - no need for additional size check copy, /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ len ); //zmemcpy(state.head.extra + len, next, // len + copy > state.head.extra_max ? // state.head.extra_max - len : copy); } if (state.flags & 0x0200) { state.check = crc32(state.check, input, copy, next); } have -= copy; next += copy; state.length -= copy; } if (state.length) { break inf_leave; } } state.length = 0; state.mode = NAME; /* falls through */ case NAME: if (state.flags & 0x0800) { if (have === 0) { break inf_leave; } copy = 0; do { // TODO: 2 or 1 bytes? len = input[next + copy++]; /* use constant limit because in js we should not preallocate memory */ if (state.head && len && (state.length < 65536 /*state.head.name_max*/)) { state.head.name += String.fromCharCode(len); } } while (len && copy < have); if (state.flags & 0x0200) { state.check = crc32(state.check, input, copy, next); } have -= copy; next += copy; if (len) { break inf_leave; } } else if (state.head) { state.head.name = null; } state.length = 0; state.mode = COMMENT; /* falls through */ case COMMENT: if (state.flags & 0x1000) { if (have === 0) { break inf_leave; } copy = 0; do { len = input[next + copy++]; /* use constant limit because in js we should not preallocate memory */ if (state.head && len && (state.length < 65536 /*state.head.comm_max*/)) { state.head.comment += String.fromCharCode(len); } } while (len && copy < have); if (state.flags & 0x0200) { state.check = crc32(state.check, input, copy, next); } have -= copy; next += copy; if (len) { break inf_leave; } } else if (state.head) { state.head.comment = null; } state.mode = HCRC; /* falls through */ case HCRC: if (state.flags & 0x0200) { //=== NEEDBITS(16); */ while (bits < 16) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// if (hold !== (state.check & 0xffff)) { strm.msg = 'header crc mismatch'; state.mode = BAD; break; } //=== INITBITS(); hold = 0; bits = 0; //===// } if (state.head) { state.head.hcrc = ((state.flags >> 9) & 1); state.head.done = true; } strm.adler = state.check = 0; state.mode = TYPE; break; case DICTID: //=== NEEDBITS(32); */ while (bits < 32) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// strm.adler = state.check = zswap32(hold); //=== INITBITS(); hold = 0; bits = 0; //===// state.mode = DICT; /* falls through */ case DICT: if (state.havedict === 0) { //--- RESTORE() --- strm.next_out = put; strm.avail_out = left; strm.next_in = next; strm.avail_in = have; state.hold = hold; state.bits = bits; //--- return Z_NEED_DICT; } strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; state.mode = TYPE; /* falls through */ case TYPE: if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } /* falls through */ case TYPEDO: if (state.last) { //--- BYTEBITS() ---// hold >>>= bits & 7; bits -= bits & 7; //---// state.mode = CHECK; break; } //=== NEEDBITS(3); */ while (bits < 3) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.last = (hold & 0x01)/*BITS(1)*/; //--- DROPBITS(1) ---// hold >>>= 1; bits -= 1; //---// switch ((hold & 0x03)/*BITS(2)*/) { case 0: /* stored block */ //Tracev((stderr, "inflate: stored block%s\n", // state.last ? " (last)" : "")); state.mode = STORED; break; case 1: /* fixed block */ fixedtables(state); //Tracev((stderr, "inflate: fixed codes block%s\n", // state.last ? " (last)" : "")); state.mode = LEN_; /* decode codes */ if (flush === Z_TREES) { //--- DROPBITS(2) ---// hold >>>= 2; bits -= 2; //---// break inf_leave; } break; case 2: /* dynamic block */ //Tracev((stderr, "inflate: dynamic codes block%s\n", // state.last ? " (last)" : "")); state.mode = TABLE; break; case 3: strm.msg = 'invalid block type'; state.mode = BAD; } //--- DROPBITS(2) ---// hold >>>= 2; bits -= 2; //---// break; case STORED: //--- BYTEBITS() ---// /* go to byte boundary */ hold >>>= bits & 7; bits -= bits & 7; //---// //=== NEEDBITS(32); */ while (bits < 32) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { strm.msg = 'invalid stored block lengths'; state.mode = BAD; break; } state.length = hold & 0xffff; //Tracev((stderr, "inflate: stored length %u\n", // state.length)); //=== INITBITS(); hold = 0; bits = 0; //===// state.mode = COPY_; if (flush === Z_TREES) { break inf_leave; } /* falls through */ case COPY_: state.mode = COPY; /* falls through */ case COPY: copy = state.length; if (copy) { if (copy > have) { copy = have; } if (copy > left) { copy = left; } if (copy === 0) { break inf_leave; } //--- zmemcpy(put, next, copy); --- utils.arraySet(output, input, next, copy, put); //---// have -= copy; next += copy; left -= copy; put += copy; state.length -= copy; break; } //Tracev((stderr, "inflate: stored end\n")); state.mode = TYPE; break; case TABLE: //=== NEEDBITS(14); */ while (bits < 14) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; //--- DROPBITS(5) ---// hold >>>= 5; bits -= 5; //---// state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; //--- DROPBITS(5) ---// hold >>>= 5; bits -= 5; //---// state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; //--- DROPBITS(4) ---// hold >>>= 4; bits -= 4; //---// //#ifndef PKZIP_BUG_WORKAROUND if (state.nlen > 286 || state.ndist > 30) { strm.msg = 'too many length or distance symbols'; state.mode = BAD; break; } //#endif //Tracev((stderr, "inflate: table sizes ok\n")); state.have = 0; state.mode = LENLENS; /* falls through */ case LENLENS: while (state.have < state.ncode) { //=== NEEDBITS(3); while (bits < 3) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); //--- DROPBITS(3) ---// hold >>>= 3; bits -= 3; //---// } while (state.have < 19) { state.lens[order[state.have++]] = 0; } // We have separate tables & no pointers. 2 commented lines below not needed. //state.next = state.codes; //state.lencode = state.next; // Switch to use dynamic table state.lencode = state.lendyn; state.lenbits = 7; opts = { bits: state.lenbits }; ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); state.lenbits = opts.bits; if (ret) { strm.msg = 'invalid code lengths set'; state.mode = BAD; break; } //Tracev((stderr, "inflate: code lengths ok\n")); state.have = 0; state.mode = CODELENS; /* falls through */ case CODELENS: while (state.have < state.nlen + state.ndist) { for (;;) { here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; if ((here_bits) <= bits) { break; } //--- PULLBYTE() ---// if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; //---// } if (here_val < 16) { //--- DROPBITS(here.bits) ---// hold >>>= here_bits; bits -= here_bits; //---// state.lens[state.have++] = here_val; } else { if (here_val === 16) { //=== NEEDBITS(here.bits + 2); n = here_bits + 2; while (bits < n) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// //--- DROPBITS(here.bits) ---// hold >>>= here_bits; bits -= here_bits; //---// if (state.have === 0) { strm.msg = 'invalid bit length repeat'; state.mode = BAD; break; } len = state.lens[state.have - 1]; copy = 3 + (hold & 0x03);//BITS(2); //--- DROPBITS(2) ---// hold >>>= 2; bits -= 2; //---// } else if (here_val === 17) { //=== NEEDBITS(here.bits + 3); n = here_bits + 3; while (bits < n) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// //--- DROPBITS(here.bits) ---// hold >>>= here_bits; bits -= here_bits; //---// len = 0; copy = 3 + (hold & 0x07);//BITS(3); //--- DROPBITS(3) ---// hold >>>= 3; bits -= 3; //---// } else { //=== NEEDBITS(here.bits + 7); n = here_bits + 7; while (bits < n) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// //--- DROPBITS(here.bits) ---// hold >>>= here_bits; bits -= here_bits; //---// len = 0; copy = 11 + (hold & 0x7f);//BITS(7); //--- DROPBITS(7) ---// hold >>>= 7; bits -= 7; //---// } if (state.have + copy > state.nlen + state.ndist) { strm.msg = 'invalid bit length repeat'; state.mode = BAD; break; } while (copy--) { state.lens[state.have++] = len; } } } /* handle error breaks in while */ if (state.mode === BAD) { break; } /* check for end-of-block code (better have one) */ if (state.lens[256] === 0) { strm.msg = 'invalid code -- missing end-of-block'; state.mode = BAD; break; } /* build code tables -- note: do not change the lenbits or distbits values here (9 and 6) without reading the comments in inftrees.h concerning the ENOUGH constants, which depend on those values */ state.lenbits = 9; opts = { bits: state.lenbits }; ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); // We have separate tables & no pointers. 2 commented lines below not needed. // state.next_index = opts.table_index; state.lenbits = opts.bits; // state.lencode = state.next; if (ret) { strm.msg = 'invalid literal/lengths set'; state.mode = BAD; break; } state.distbits = 6; //state.distcode.copy(state.codes); // Switch to use dynamic table state.distcode = state.distdyn; opts = { bits: state.distbits }; ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); // We have separate tables & no pointers. 2 commented lines below not needed. // state.next_index = opts.table_index; state.distbits = opts.bits; // state.distcode = state.next; if (ret) { strm.msg = 'invalid distances set'; state.mode = BAD; break; } //Tracev((stderr, 'inflate: codes ok\n')); state.mode = LEN_; if (flush === Z_TREES) { break inf_leave; } /* falls through */ case LEN_: state.mode = LEN; /* falls through */ case LEN: if (have >= 6 && left >= 258) { //--- RESTORE() --- strm.next_out = put; strm.avail_out = left; strm.next_in = next; strm.avail_in = have; state.hold = hold; state.bits = bits; //--- inflate_fast(strm, _out); //--- LOAD() --- put = strm.next_out; output = strm.output; left = strm.avail_out; next = strm.next_in; input = strm.input; have = strm.avail_in; hold = state.hold; bits = state.bits; //--- if (state.mode === TYPE) { state.back = -1; } break; } state.back = 0; for (;;) { here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; if (here_bits <= bits) { break; } //--- PULLBYTE() ---// if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; //---// } if (here_op && (here_op & 0xf0) === 0) { last_bits = here_bits; last_op = here_op; last_val = here_val; for (;;) { here = state.lencode[last_val + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; if ((last_bits + here_bits) <= bits) { break; } //--- PULLBYTE() ---// if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; //---// } //--- DROPBITS(last.bits) ---// hold >>>= last_bits; bits -= last_bits; //---// state.back += last_bits; } //--- DROPBITS(here.bits) ---// hold >>>= here_bits; bits -= here_bits; //---// state.back += here_bits; state.length = here_val; if (here_op === 0) { //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? // "inflate: literal '%c'\n" : // "inflate: literal 0x%02x\n", here.val)); state.mode = LIT; break; } if (here_op & 32) { //Tracevv((stderr, "inflate: end of block\n")); state.back = -1; state.mode = TYPE; break; } if (here_op & 64) { strm.msg = 'invalid literal/length code'; state.mode = BAD; break; } state.extra = here_op & 15; state.mode = LENEXT; /* falls through */ case LENEXT: if (state.extra) { //=== NEEDBITS(state.extra); n = state.extra; while (bits < n) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; //--- DROPBITS(state.extra) ---// hold >>>= state.extra; bits -= state.extra; //---// state.back += state.extra; } //Tracevv((stderr, "inflate: length %u\n", state.length)); state.was = state.length; state.mode = DIST; /* falls through */ case DIST: for (;;) { here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; if ((here_bits) <= bits) { break; } //--- PULLBYTE() ---// if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; //---// } if ((here_op & 0xf0) === 0) { last_bits = here_bits; last_op = here_op; last_val = here_val; for (;;) { here = state.distcode[last_val + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; if ((last_bits + here_bits) <= bits) { break; } //--- PULLBYTE() ---// if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; //---// } //--- DROPBITS(last.bits) ---// hold >>>= last_bits; bits -= last_bits; //---// state.back += last_bits; } //--- DROPBITS(here.bits) ---// hold >>>= here_bits; bits -= here_bits; //---// state.back += here_bits; if (here_op & 64) { strm.msg = 'invalid distance code'; state.mode = BAD; break; } state.offset = here_val; state.extra = (here_op) & 15; state.mode = DISTEXT; /* falls through */ case DISTEXT: if (state.extra) { //=== NEEDBITS(state.extra); n = state.extra; while (bits < n) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; //--- DROPBITS(state.extra) ---// hold >>>= state.extra; bits -= state.extra; //---// state.back += state.extra; } //#ifdef INFLATE_STRICT if (state.offset > state.dmax) { strm.msg = 'invalid distance too far back'; state.mode = BAD; break; } //#endif //Tracevv((stderr, "inflate: distance %u\n", state.offset)); state.mode = MATCH; /* falls through */ case MATCH: if (left === 0) { break inf_leave; } copy = _out - left; if (state.offset > copy) { /* copy from window */ copy = state.offset - copy; if (copy > state.whave) { if (state.sane) { strm.msg = 'invalid distance too far back'; state.mode = BAD; break; } // (!) This block is disabled in zlib defailts, // don't enable it for binary compatibility //#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR // Trace((stderr, "inflate.c too far\n")); // copy -= state.whave; // if (copy > state.length) { copy = state.length; } // if (copy > left) { copy = left; } // left -= copy; // state.length -= copy; // do { // output[put++] = 0; // } while (--copy); // if (state.length === 0) { state.mode = LEN; } // break; //#endif } if (copy > state.wnext) { copy -= state.wnext; from = state.wsize - copy; } else { from = state.wnext - copy; } if (copy > state.length) { copy = state.length; } from_source = state.window; } else { /* copy from output */ from_source = output; from = put - state.offset; copy = state.length; } if (copy > left) { copy = left; } left -= copy; state.length -= copy; do { output[put++] = from_source[from++]; } while (--copy); if (state.length === 0) { state.mode = LEN; } break; case LIT: if (left === 0) { break inf_leave; } output[put++] = state.length; left--; state.mode = LEN; break; case CHECK: if (state.wrap) { //=== NEEDBITS(32); while (bits < 32) { if (have === 0) { break inf_leave; } have--; // Use '|' insdead of '+' to make sure that result is signed hold |= input[next++] << bits; bits += 8; } //===// _out -= left; strm.total_out += _out; state.total += _out; if (_out) { strm.adler = state.check = /*UPDATE(state.check, put - _out, _out);*/ (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out)); } _out = left; // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too if ((state.flags ? hold : zswap32(hold)) !== state.check) { strm.msg = 'incorrect data check'; state.mode = BAD; break; } //=== INITBITS(); hold = 0; bits = 0; //===// //Tracev((stderr, "inflate: check matches trailer\n")); } state.mode = LENGTH; /* falls through */ case LENGTH: if (state.wrap && state.flags) { //=== NEEDBITS(32); while (bits < 32) { if (have === 0) { break inf_leave; } have--; hold += input[next++] << bits; bits += 8; } //===// if (hold !== (state.total & 0xffffffff)) { strm.msg = 'incorrect length check'; state.mode = BAD; break; } //=== INITBITS(); hold = 0; bits = 0; //===// //Tracev((stderr, "inflate: length matches trailer\n")); } state.mode = DONE; /* falls through */ case DONE: ret = Z_STREAM_END; break inf_leave; case BAD: ret = Z_DATA_ERROR; break inf_leave; case MEM: return Z_MEM_ERROR; case SYNC: /* falls through */ default: return Z_STREAM_ERROR; } } // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" /* Return from inflate(), updating the total counts and the check value. If there was no progress during the inflate() call, return a buffer error. Call updatewindow() to create and/or update the window state. Note: a memory error from inflate() is non-recoverable. */ //--- RESTORE() --- strm.next_out = put; strm.avail_out = left; strm.next_in = next; strm.avail_in = have; state.hold = hold; state.bits = bits; //--- if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && (state.mode < CHECK || flush !== Z_FINISH))) { if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ; } _in -= strm.avail_in; _out -= strm.avail_out; strm.total_in += _in; strm.total_out += _out; state.total += _out; if (state.wrap && _out) { strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/ (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out)); } strm.data_type = state.bits + (state.last ? 64 : 0) + (state.mode === TYPE ? 128 : 0) + (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) { ret = Z_BUF_ERROR; } return ret; } function inflateEnd(strm) { if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) { return Z_STREAM_ERROR; } var state = strm.state; if (state.window) { state.window = null; } strm.state = null; return Z_OK; } function inflateGetHeader(strm, head) { var state; /* check state */ if (!strm || !strm.state) { return Z_STREAM_ERROR; } state = strm.state; if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; } /* save header structure */ state.head = head; head.done = false; return Z_OK; } function inflateSetDictionary(strm, dictionary) { var dictLength = dictionary.length; var state; var dictid; var ret; /* check state */ if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; } state = strm.state; if (state.wrap !== 0 && state.mode !== DICT) { return Z_STREAM_ERROR; } /* check for correct dictionary identifier */ if (state.mode === DICT) { dictid = 1; /* adler32(0, null, 0)*/ /* dictid = adler32(dictid, dictionary, dictLength); */ dictid = adler32(dictid, dictionary, dictLength, 0); if (dictid !== state.check) { return Z_DATA_ERROR; } } /* copy dictionary to window using updatewindow(), which will amend the existing dictionary if appropriate */ ret = updatewindow(strm, dictionary, dictLength, dictLength); if (ret) { state.mode = MEM; return Z_MEM_ERROR; } state.havedict = 1; // Tracev((stderr, "inflate: dictionary set\n")); return Z_OK; } exports.inflateReset = inflateReset; exports.inflateReset2 = inflateReset2; exports.inflateResetKeep = inflateResetKeep; exports.inflateInit = inflateInit; exports.inflateInit2 = inflateInit2; exports.inflate = inflate; exports.inflateEnd = inflateEnd; exports.inflateGetHeader = inflateGetHeader; exports.inflateSetDictionary = inflateSetDictionary; exports.inflateInfo = 'pako inflate (from Nodeca project)'; /* Not implemented exports.inflateCopy = inflateCopy; exports.inflateGetDictionary = inflateGetDictionary; exports.inflateMark = inflateMark; exports.inflatePrime = inflatePrime; exports.inflateSync = inflateSync; exports.inflateSyncPoint = inflateSyncPoint; exports.inflateUndermine = inflateUndermine; */ },{"../utils/common":41,"./adler32":43,"./crc32":45,"./inffast":48,"./inftrees":50}],50:[function(require,module,exports){ var utils = require('../utils/common'); var MAXBITS = 15; var ENOUGH_LENS = 852; var ENOUGH_DISTS = 592; //var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); var CODES = 0; var LENS = 1; var DISTS = 2; var lbase = [ /* Length codes 257..285 base */ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 ]; var lext = [ /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 ]; var dbase = [ /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 ]; var dext = [ /* Distance codes 0..29 extra */ 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 64, 64 ]; module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts) { var bits = opts.bits; //here = opts.here; /* table entry for duplication */ var len = 0; /* a code's length in bits */ var sym = 0; /* index of code symbols */ var min = 0, max = 0; /* minimum and maximum code lengths */ var root = 0; /* number of index bits for root table */ var curr = 0; /* number of index bits for current table */ var drop = 0; /* code bits to drop for sub-table */ var left = 0; /* number of prefix codes available */ var used = 0; /* code entries in table used */ var huff = 0; /* Huffman code */ var incr; /* for incrementing code, index */ var fill; /* index for replicating entries */ var low; /* low bits for current root entry */ var mask; /* mask for low root bits */ var next; /* next available space in table */ var base = null; /* base value table to use */ var base_index = 0; // var shoextra; /* extra bits table to use */ var end; /* use base and extra for symbol > end */ var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ var extra = null; var extra_index = 0; var here_bits, here_op, here_val; /* Process a set of code lengths to create a canonical Huffman code. The code lengths are lens[0..codes-1]. Each length corresponds to the symbols 0..codes-1. The Huffman code is generated by first sorting the symbols by length from short to long, and retaining the symbol order for codes with equal lengths. Then the code starts with all zero bits for the first code of the shortest length, and the codes are integer increments for the same length, and zeros are appended as the length increases. For the deflate format, these bits are stored backwards from their more natural integer increment ordering, and so when the decoding tables are built in the large loop below, the integer codes are incremented backwards. This routine assumes, but does not check, that all of the entries in lens[] are in the range 0..MAXBITS. The caller must assure this. 1..MAXBITS is interpreted as that code length. zero means that that symbol does not occur in this code. The codes are sorted by computing a count of codes for each length, creating from that a table of starting indices for each length in the sorted table, and then entering the symbols in order in the sorted table. The sorted table is work[], with that space being provided by the caller. The length counts are used for other purposes as well, i.e. finding the minimum and maximum length codes, determining if there are any codes at all, checking for a valid set of lengths, and looking ahead at length counts to determine sub-table sizes when building the decoding tables. */ /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ for (len = 0; len <= MAXBITS; len++) { count[len] = 0; } for (sym = 0; sym < codes; sym++) { count[lens[lens_index + sym]]++; } /* bound code lengths, force root to be within code lengths */ root = bits; for (max = MAXBITS; max >= 1; max--) { if (count[max] !== 0) { break; } } if (root > max) { root = max; } if (max === 0) { /* no symbols to code at all */ //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ //table.bits[opts.table_index] = 1; //here.bits = (var char)1; //table.val[opts.table_index++] = 0; //here.val = (var short)0; table[table_index++] = (1 << 24) | (64 << 16) | 0; //table.op[opts.table_index] = 64; //table.bits[opts.table_index] = 1; //table.val[opts.table_index++] = 0; table[table_index++] = (1 << 24) | (64 << 16) | 0; opts.bits = 1; return 0; /* no symbols, but wait for decoding to report error */ } for (min = 1; min < max; min++) { if (count[min] !== 0) { break; } } if (root < min) { root = min; } /* check for an over-subscribed or incomplete set of lengths */ left = 1; for (len = 1; len <= MAXBITS; len++) { left <<= 1; left -= count[len]; if (left < 0) { return -1; } /* over-subscribed */ } if (left > 0 && (type === CODES || max !== 1)) { return -1; /* incomplete set */ } /* generate offsets into symbol table for each length for sorting */ offs[1] = 0; for (len = 1; len < MAXBITS; len++) { offs[len + 1] = offs[len] + count[len]; } /* sort symbols by length, by symbol order within each length */ for (sym = 0; sym < codes; sym++) { if (lens[lens_index + sym] !== 0) { work[offs[lens[lens_index + sym]]++] = sym; } } /* Create and fill in decoding tables. In this loop, the table being filled is at next and has curr index bits. The code being used is huff with length len. That code is converted to an index by dropping drop bits off of the bottom. For codes where len is less than drop + curr, those top drop + curr - len bits are incremented through all values to fill the table with replicated entries. root is the number of index bits for the root table. When len exceeds root, sub-tables are created pointed to by the root entry with an index of the low root bits of huff. This is saved in low to check for when a new sub-table should be started. drop is zero when the root table is being filled, and drop is root when sub-tables are being filled. When a new sub-table is needed, it is necessary to look ahead in the code lengths to determine what size sub-table is needed. The length counts are used for this, and so count[] is decremented as codes are entered in the tables. used keeps track of how many table entries have been allocated from the provided *table space. It is checked for LENS and DIST tables against the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in the initial root table size constants. See the comments in inftrees.h for more information. sym increments through all symbols, and the loop terminates when all codes of length max, i.e. all codes, have been processed. This routine permits incomplete codes, so another loop after this one fills in the rest of the decoding tables with invalid code markers. */ /* set up for code type */ // poor man optimization - use if-else instead of switch, // to avoid deopts in old v8 if (type === CODES) { base = extra = work; /* dummy value--not used */ end = 19; } else if (type === LENS) { base = lbase; base_index -= 257; extra = lext; extra_index -= 257; end = 256; } else { /* DISTS */ base = dbase; extra = dext; end = -1; } /* initialize opts for loop */ huff = 0; /* starting code */ sym = 0; /* starting code symbol */ len = min; /* starting code length */ next = table_index; /* current table to fill in */ curr = root; /* current table index bits */ drop = 0; /* current bits to drop from code for index */ low = -1; /* trigger new sub-table when len > root */ used = 1 << root; /* use root table entries */ mask = used - 1; /* mask for comparing low */ /* check available table space */ if ((type === LENS && used > ENOUGH_LENS) || (type === DISTS && used > ENOUGH_DISTS)) { return 1; } /* process all codes and make table entries */ for (;;) { /* create table entry */ here_bits = len - drop; if (work[sym] < end) { here_op = 0; here_val = work[sym]; } else if (work[sym] > end) { here_op = extra[extra_index + work[sym]]; here_val = base[base_index + work[sym]]; } else { here_op = 32 + 64; /* end of block */ here_val = 0; } /* replicate for those indices with low len bits equal to huff */ incr = 1 << (len - drop); fill = 1 << curr; min = fill; /* save offset to next table */ do { fill -= incr; table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; } while (fill !== 0); /* backwards increment the len-bit code huff */ incr = 1 << (len - 1); while (huff & incr) { incr >>= 1; } if (incr !== 0) { huff &= incr - 1; huff += incr; } else { huff = 0; } /* go to next symbol, update count, len */ sym++; if (--count[len] === 0) { if (len === max) { break; } len = lens[lens_index + work[sym]]; } /* create new sub-table if needed */ if (len > root && (huff & mask) !== low) { /* if first time, transition to sub-tables */ if (drop === 0) { drop = root; } /* increment past last table */ next += min; /* here min is 1 << curr */ /* determine length of next table */ curr = len - drop; left = 1 << curr; while (curr + drop < max) { left -= count[curr + drop]; if (left <= 0) { break; } curr++; left <<= 1; } /* check for enough space */ used += 1 << curr; if ((type === LENS && used > ENOUGH_LENS) || (type === DISTS && used > ENOUGH_DISTS)) { return 1; } /* point entry in root table to sub-table */ low = huff & mask; /*table.op[low] = curr; table.bits[low] = root; table.val[low] = next - opts.table_index;*/ table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; } } /* fill in remaining table entry if code is incomplete (guaranteed to have at most one remaining entry, since if the code is incomplete, the maximum code length that was allowed to get this far is one bit) */ if (huff !== 0) { //table.op[next + huff] = 64; /* invalid code marker */ //table.bits[next + huff] = len - drop; //table.val[next + huff] = 0; table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; } /* set return parameters */ //opts.table_index += used; opts.bits = root; return 0; }; },{"../utils/common":41}],51:[function(require,module,exports){ module.exports = { 2: 'need dictionary', /* Z_NEED_DICT 2 */ 1: 'stream end', /* Z_STREAM_END 1 */ 0: '', /* Z_OK 0 */ '-1': 'file error', /* Z_ERRNO (-1) */ '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ '-3': 'data error', /* Z_DATA_ERROR (-3) */ '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; },{}],52:[function(require,module,exports){ var utils = require('../utils/common'); /* Public constants ==========================================================*/ /* ===========================================================================*/ //var Z_FILTERED = 1; //var Z_HUFFMAN_ONLY = 2; //var Z_RLE = 3; var Z_FIXED = 4; //var Z_DEFAULT_STRATEGY = 0; /* Possible values of the data_type field (though see inflate()) */ var Z_BINARY = 0; var Z_TEXT = 1; //var Z_ASCII = 1; // = Z_TEXT var Z_UNKNOWN = 2; /*============================================================================*/ function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } } // From zutil.h var STORED_BLOCK = 0; var STATIC_TREES = 1; var DYN_TREES = 2; /* The three kinds of block type */ var MIN_MATCH = 3; var MAX_MATCH = 258; /* The minimum and maximum match lengths */ // From deflate.h /* =========================================================================== * Internal compression state. */ var LENGTH_CODES = 29; /* number of length codes, not counting the special END_BLOCK code */ var LITERALS = 256; /* number of literal bytes 0..255 */ var L_CODES = LITERALS + 1 + LENGTH_CODES; /* number of Literal or Length codes, including the END_BLOCK code */ var D_CODES = 30; /* number of distance codes */ var BL_CODES = 19; /* number of codes used to transfer the bit lengths */ var HEAP_SIZE = 2 * L_CODES + 1; /* maximum heap size */ var MAX_BITS = 15; /* All codes must not exceed MAX_BITS bits */ var Buf_size = 16; /* size of bit buffer in bi_buf */ /* =========================================================================== * Constants */ var MAX_BL_BITS = 7; /* Bit length codes must not exceed MAX_BL_BITS bits */ var END_BLOCK = 256; /* end of block literal code */ var REP_3_6 = 16; /* repeat previous bit length 3-6 times (2 bits of repeat count) */ var REPZ_3_10 = 17; /* repeat a zero length 3-10 times (3 bits of repeat count) */ var REPZ_11_138 = 18; /* repeat a zero length 11-138 times (7 bits of repeat count) */ /* eslint-disable comma-spacing,array-bracket-spacing */ var extra_lbits = /* extra bits for each length code */ [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; var extra_dbits = /* extra bits for each distance code */ [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]; var extra_blbits = /* extra bits for each bit length code */ [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]; var bl_order = [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]; /* eslint-enable comma-spacing,array-bracket-spacing */ /* The lengths of the bit length codes are sent in order of decreasing * probability, to avoid transmitting the lengths for unused bit length codes. */ /* =========================================================================== * Local data. These are initialized only once. */ // We pre-fill arrays with 0 to avoid uninitialized gaps var DIST_CODE_LEN = 512; /* see definition of array dist_code below */ // !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1 var static_ltree = new Array((L_CODES + 2) * 2); zero(static_ltree); /* The static literal tree. Since the bit lengths are imposed, there is no * need for the L_CODES extra codes used during heap construction. However * The codes 286 and 287 are needed to build a canonical tree (see _tr_init * below). */ var static_dtree = new Array(D_CODES * 2); zero(static_dtree); /* The static distance tree. (Actually a trivial tree since all codes use * 5 bits.) */ var _dist_code = new Array(DIST_CODE_LEN); zero(_dist_code); /* Distance codes. The first 256 values correspond to the distances * 3 .. 258, the last 256 values correspond to the top 8 bits of * the 15 bit distances. */ var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1); zero(_length_code); /* length code for each normalized match length (0 == MIN_MATCH) */ var base_length = new Array(LENGTH_CODES); zero(base_length); /* First normalized length for each code (0 = MIN_MATCH) */ var base_dist = new Array(D_CODES); zero(base_dist); /* First normalized distance for each code (0 = distance of 1) */ function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { this.static_tree = static_tree; /* static tree or NULL */ this.extra_bits = extra_bits; /* extra bits for each code or NULL */ this.extra_base = extra_base; /* base index for extra_bits */ this.elems = elems; /* max number of elements in the tree */ this.max_length = max_length; /* max bit length for the codes */ // show if `static_tree` has data or dummy - needed for monomorphic objects this.has_stree = static_tree && static_tree.length; } var static_l_desc; var static_d_desc; var static_bl_desc; function TreeDesc(dyn_tree, stat_desc) { this.dyn_tree = dyn_tree; /* the dynamic tree */ this.max_code = 0; /* largest code with non zero frequency */ this.stat_desc = stat_desc; /* the corresponding static tree */ } function d_code(dist) { return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; } /* =========================================================================== * Output a short LSB first on the stream. * IN assertion: there is enough room in pendingBuf. */ function put_short(s, w) { // put_byte(s, (uch)((w) & 0xff)); // put_byte(s, (uch)((ush)(w) >> 8)); s.pending_buf[s.pending++] = (w) & 0xff; s.pending_buf[s.pending++] = (w >>> 8) & 0xff; } /* =========================================================================== * Send a value on a given number of bits. * IN assertion: length <= 16 and value fits in length bits. */ function send_bits(s, value, length) { if (s.bi_valid > (Buf_size - length)) { s.bi_buf |= (value << s.bi_valid) & 0xffff; put_short(s, s.bi_buf); s.bi_buf = value >> (Buf_size - s.bi_valid); s.bi_valid += length - Buf_size; } else { s.bi_buf |= (value << s.bi_valid) & 0xffff; s.bi_valid += length; } } function send_code(s, c, tree) { send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); } /* =========================================================================== * Reverse the first len bits of a code, using straightforward code (a faster * method would use a table) * IN assertion: 1 <= len <= 15 */ function bi_reverse(code, len) { var res = 0; do { res |= code & 1; code >>>= 1; res <<= 1; } while (--len > 0); return res >>> 1; } /* =========================================================================== * Flush the bit buffer, keeping at most 7 bits in it. */ function bi_flush(s) { if (s.bi_valid === 16) { put_short(s, s.bi_buf); s.bi_buf = 0; s.bi_valid = 0; } else if (s.bi_valid >= 8) { s.pending_buf[s.pending++] = s.bi_buf & 0xff; s.bi_buf >>= 8; s.bi_valid -= 8; } } /* =========================================================================== * Compute the optimal bit lengths for a tree and update the total bit length * for the current block. * IN assertion: the fields freq and dad are set, heap[heap_max] and * above are the tree nodes sorted by increasing frequency. * OUT assertions: the field len is set to the optimal bit length, the * array bl_count contains the frequencies for each bit length. * The length opt_len is updated; static_len is also updated if stree is * not null. */ function gen_bitlen(s, desc) // deflate_state *s; // tree_desc *desc; /* the tree descriptor */ { var tree = desc.dyn_tree; var max_code = desc.max_code; var stree = desc.stat_desc.static_tree; var has_stree = desc.stat_desc.has_stree; var extra = desc.stat_desc.extra_bits; var base = desc.stat_desc.extra_base; var max_length = desc.stat_desc.max_length; var h; /* heap index */ var n, m; /* iterate over the tree elements */ var bits; /* bit length */ var xbits; /* extra bits */ var f; /* frequency */ var overflow = 0; /* number of elements with bit length too large */ for (bits = 0; bits <= MAX_BITS; bits++) { s.bl_count[bits] = 0; } /* In a first pass, compute the optimal bit lengths (which may * overflow in the case of the bit length tree). */ tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { n = s.heap[h]; bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; if (bits > max_length) { bits = max_length; overflow++; } tree[n * 2 + 1]/*.Len*/ = bits; /* We overwrite tree[n].Dad which is no longer needed */ if (n > max_code) { continue; } /* not a leaf node */ s.bl_count[bits]++; xbits = 0; if (n >= base) { xbits = extra[n - base]; } f = tree[n * 2]/*.Freq*/; s.opt_len += f * (bits + xbits); if (has_stree) { s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); } } if (overflow === 0) { return; } // Trace((stderr,"\nbit length overflow\n")); /* This happens for example on obj2 and pic of the Calgary corpus */ /* Find the first bit length which could increase: */ do { bits = max_length - 1; while (s.bl_count[bits] === 0) { bits--; } s.bl_count[bits]--; /* move one leaf down the tree */ s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ s.bl_count[max_length]--; /* The brother of the overflow item also moves one step up, * but this does not affect bl_count[max_length] */ overflow -= 2; } while (overflow > 0); /* Now recompute all bit lengths, scanning in increasing frequency. * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all * lengths instead of fixing only the wrong ones. This idea is taken * from 'ar' written by Haruhiko Okumura.) */ for (bits = max_length; bits !== 0; bits--) { n = s.bl_count[bits]; while (n !== 0) { m = s.heap[--h]; if (m > max_code) { continue; } if (tree[m * 2 + 1]/*.Len*/ !== bits) { // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; tree[m * 2 + 1]/*.Len*/ = bits; } n--; } } } /* =========================================================================== * Generate the codes for a given tree and bit counts (which need not be * optimal). * IN assertion: the array bl_count contains the bit length statistics for * the given tree and the field len is set for all tree elements. * OUT assertion: the field code is set for all tree elements of non * zero code length. */ function gen_codes(tree, max_code, bl_count) // ct_data *tree; /* the tree to decorate */ // int max_code; /* largest code with non zero frequency */ // ushf *bl_count; /* number of codes at each bit length */ { var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */ var code = 0; /* running code value */ var bits; /* bit index */ var n; /* code index */ /* The distribution counts are first used to generate the code values * without bit reversal. */ for (bits = 1; bits <= MAX_BITS; bits++) { next_code[bits] = code = (code + bl_count[bits - 1]) << 1; } /* Check that the bit counts in bl_count are consistent. The last code * must be all ones. */ //Assert (code + bl_count[MAX_BITS]-1 == (1< length code (0..28) */ length = 0; for (code = 0; code < LENGTH_CODES - 1; code++) { base_length[code] = length; for (n = 0; n < (1 << extra_lbits[code]); n++) { _length_code[length++] = code; } } //Assert (length == 256, "tr_static_init: length != 256"); /* Note that the length 255 (match length 258) can be represented * in two different ways: code 284 + 5 bits or code 285, so we * overwrite length_code[255] to use the best encoding: */ _length_code[length - 1] = code; /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ dist = 0; for (code = 0; code < 16; code++) { base_dist[code] = dist; for (n = 0; n < (1 << extra_dbits[code]); n++) { _dist_code[dist++] = code; } } //Assert (dist == 256, "tr_static_init: dist != 256"); dist >>= 7; /* from now on, all distances are divided by 128 */ for (; code < D_CODES; code++) { base_dist[code] = dist << 7; for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { _dist_code[256 + dist++] = code; } } //Assert (dist == 256, "tr_static_init: 256+dist != 512"); /* Construct the codes of the static literal tree */ for (bits = 0; bits <= MAX_BITS; bits++) { bl_count[bits] = 0; } n = 0; while (n <= 143) { static_ltree[n * 2 + 1]/*.Len*/ = 8; n++; bl_count[8]++; } while (n <= 255) { static_ltree[n * 2 + 1]/*.Len*/ = 9; n++; bl_count[9]++; } while (n <= 279) { static_ltree[n * 2 + 1]/*.Len*/ = 7; n++; bl_count[7]++; } while (n <= 287) { static_ltree[n * 2 + 1]/*.Len*/ = 8; n++; bl_count[8]++; } /* Codes 286 and 287 do not exist, but we must include them in the * tree construction to get a canonical Huffman tree (longest code * all ones) */ gen_codes(static_ltree, L_CODES + 1, bl_count); /* The static distance tree is trivial: */ for (n = 0; n < D_CODES; n++) { static_dtree[n * 2 + 1]/*.Len*/ = 5; static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); } // Now data ready and we can init static trees static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS); static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); //static_init_done = true; } /* =========================================================================== * Initialize a new block. */ function init_block(s) { var n; /* iterates over tree elements */ /* Initialize the trees. */ for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; s.opt_len = s.static_len = 0; s.last_lit = s.matches = 0; } /* =========================================================================== * Flush the bit buffer and align the output on a byte boundary */ function bi_windup(s) { if (s.bi_valid > 8) { put_short(s, s.bi_buf); } else if (s.bi_valid > 0) { //put_byte(s, (Byte)s->bi_buf); s.pending_buf[s.pending++] = s.bi_buf; } s.bi_buf = 0; s.bi_valid = 0; } /* =========================================================================== * Copy a stored block, storing first the length and its * one's complement if requested. */ function copy_block(s, buf, len, header) //DeflateState *s; //charf *buf; /* the input data */ //unsigned len; /* its length */ //int header; /* true if block header must be written */ { bi_windup(s); /* align on byte boundary */ { put_short(s, len); put_short(s, ~len); } // while (len--) { // put_byte(s, *buf++); // } utils.arraySet(s.pending_buf, s.window, buf, len, s.pending); s.pending += len; } /* =========================================================================== * Compares to subtrees, using the tree depth as tie breaker when * the subtrees have equal frequency. This minimizes the worst case length. */ function smaller(tree, n, m, depth) { var _n2 = n * 2; var _m2 = m * 2; return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); } /* =========================================================================== * Restore the heap property by moving down the tree starting at node k, * exchanging a node with the smallest of its two sons if necessary, stopping * when the heap property is re-established (each father smaller than its * two sons). */ function pqdownheap(s, tree, k) // deflate_state *s; // ct_data *tree; /* the tree to restore */ // int k; /* node to move down */ { var v = s.heap[k]; var j = k << 1; /* left son of k */ while (j <= s.heap_len) { /* Set j to the smallest of the two sons: */ if (j < s.heap_len && smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { j++; } /* Exit if v is smaller than both sons */ if (smaller(tree, v, s.heap[j], s.depth)) { break; } /* Exchange v with the smallest son */ s.heap[k] = s.heap[j]; k = j; /* And continue down the tree, setting j to the left son of k */ j <<= 1; } s.heap[k] = v; } // inlined manually // var SMALLEST = 1; /* =========================================================================== * Send the block data compressed using the given Huffman trees */ function compress_block(s, ltree, dtree) // deflate_state *s; // const ct_data *ltree; /* literal tree */ // const ct_data *dtree; /* distance tree */ { var dist; /* distance of matched string */ var lc; /* match length or unmatched char (if dist == 0) */ var lx = 0; /* running index in l_buf */ var code; /* the code to send */ var extra; /* number of extra bits to send */ if (s.last_lit !== 0) { do { dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]); lc = s.pending_buf[s.l_buf + lx]; lx++; if (dist === 0) { send_code(s, lc, ltree); /* send a literal byte */ //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); } else { /* Here, lc is the match length - MIN_MATCH */ code = _length_code[lc]; send_code(s, code + LITERALS + 1, ltree); /* send the length code */ extra = extra_lbits[code]; if (extra !== 0) { lc -= base_length[code]; send_bits(s, lc, extra); /* send the extra length bits */ } dist--; /* dist is now the match distance - 1 */ code = d_code(dist); //Assert (code < D_CODES, "bad d_code"); send_code(s, code, dtree); /* send the distance code */ extra = extra_dbits[code]; if (extra !== 0) { dist -= base_dist[code]; send_bits(s, dist, extra); /* send the extra distance bits */ } } /* literal or match pair ? */ /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, // "pendingBuf overflow"); } while (lx < s.last_lit); } send_code(s, END_BLOCK, ltree); } /* =========================================================================== * Construct one Huffman tree and assigns the code bit strings and lengths. * Update the total bit length for the current block. * IN assertion: the field freq is set for all tree elements. * OUT assertions: the fields len and code are set to the optimal bit length * and corresponding code. The length opt_len is updated; static_len is * also updated if stree is not null. The field max_code is set. */ function build_tree(s, desc) // deflate_state *s; // tree_desc *desc; /* the tree descriptor */ { var tree = desc.dyn_tree; var stree = desc.stat_desc.static_tree; var has_stree = desc.stat_desc.has_stree; var elems = desc.stat_desc.elems; var n, m; /* iterate over heap elements */ var max_code = -1; /* largest code with non zero frequency */ var node; /* new node being created */ /* Construct the initial heap, with least frequent element in * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. * heap[0] is not used. */ s.heap_len = 0; s.heap_max = HEAP_SIZE; for (n = 0; n < elems; n++) { if (tree[n * 2]/*.Freq*/ !== 0) { s.heap[++s.heap_len] = max_code = n; s.depth[n] = 0; } else { tree[n * 2 + 1]/*.Len*/ = 0; } } /* The pkzip format requires that at least one distance code exists, * and that at least one bit should be sent even if there is only one * possible code. So to avoid special checks later on we force at least * two codes of non zero frequency. */ while (s.heap_len < 2) { node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); tree[node * 2]/*.Freq*/ = 1; s.depth[node] = 0; s.opt_len--; if (has_stree) { s.static_len -= stree[node * 2 + 1]/*.Len*/; } /* node is 0 or 1 so it does not have extra bits */ } desc.max_code = max_code; /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, * establish sub-heaps of increasing lengths: */ for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } /* Construct the Huffman tree by repeatedly combining the least two * frequent nodes. */ node = elems; /* next internal node of the tree */ do { //pqremove(s, tree, n); /* n = node of least frequency */ /*** pqremove ***/ n = s.heap[1/*SMALLEST*/]; s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; pqdownheap(s, tree, 1/*SMALLEST*/); /***/ m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ s.heap[--s.heap_max] = m; /* Create a new node father of n and m */ tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; /* and insert the new node in the heap */ s.heap[1/*SMALLEST*/] = node++; pqdownheap(s, tree, 1/*SMALLEST*/); } while (s.heap_len >= 2); s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; /* At this point, the fields freq and dad are set. We can now * generate the bit lengths. */ gen_bitlen(s, desc); /* The field len is now set, we can generate the bit codes */ gen_codes(tree, max_code, s.bl_count); } /* =========================================================================== * Scan a literal or distance tree to determine the frequencies of the codes * in the bit length tree. */ function scan_tree(s, tree, max_code) // deflate_state *s; // ct_data *tree; /* the tree to be scanned */ // int max_code; /* and its largest code of non zero frequency */ { var n; /* iterates over all tree elements */ var prevlen = -1; /* last emitted length */ var curlen; /* length of current code */ var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ var count = 0; /* repeat count of the current code */ var max_count = 7; /* max repeat count */ var min_count = 4; /* min repeat count */ if (nextlen === 0) { max_count = 138; min_count = 3; } tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ for (n = 0; n <= max_code; n++) { curlen = nextlen; nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; if (++count < max_count && curlen === nextlen) { continue; } else if (count < min_count) { s.bl_tree[curlen * 2]/*.Freq*/ += count; } else if (curlen !== 0) { if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } s.bl_tree[REP_3_6 * 2]/*.Freq*/++; } else if (count <= 10) { s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; } else { s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; } count = 0; prevlen = curlen; if (nextlen === 0) { max_count = 138; min_count = 3; } else if (curlen === nextlen) { max_count = 6; min_count = 3; } else { max_count = 7; min_count = 4; } } } /* =========================================================================== * Send a literal or distance tree in compressed form, using the codes in * bl_tree. */ function send_tree(s, tree, max_code) // deflate_state *s; // ct_data *tree; /* the tree to be scanned */ // int max_code; /* and its largest code of non zero frequency */ { var n; /* iterates over all tree elements */ var prevlen = -1; /* last emitted length */ var curlen; /* length of current code */ var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ var count = 0; /* repeat count of the current code */ var max_count = 7; /* max repeat count */ var min_count = 4; /* min repeat count */ /* tree[max_code+1].Len = -1; */ /* guard already set */ if (nextlen === 0) { max_count = 138; min_count = 3; } for (n = 0; n <= max_code; n++) { curlen = nextlen; nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; if (++count < max_count && curlen === nextlen) { continue; } else if (count < min_count) { do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); } else if (curlen !== 0) { if (curlen !== prevlen) { send_code(s, curlen, s.bl_tree); count--; } //Assert(count >= 3 && count <= 6, " 3_6?"); send_code(s, REP_3_6, s.bl_tree); send_bits(s, count - 3, 2); } else if (count <= 10) { send_code(s, REPZ_3_10, s.bl_tree); send_bits(s, count - 3, 3); } else { send_code(s, REPZ_11_138, s.bl_tree); send_bits(s, count - 11, 7); } count = 0; prevlen = curlen; if (nextlen === 0) { max_count = 138; min_count = 3; } else if (curlen === nextlen) { max_count = 6; min_count = 3; } else { max_count = 7; min_count = 4; } } } /* =========================================================================== * Construct the Huffman tree for the bit lengths and return the index in * bl_order of the last bit length code to send. */ function build_bl_tree(s) { var max_blindex; /* index of last bit length code of non zero freq */ /* Determine the bit length frequencies for literal and distance trees */ scan_tree(s, s.dyn_ltree, s.l_desc.max_code); scan_tree(s, s.dyn_dtree, s.d_desc.max_code); /* Build the bit length tree: */ build_tree(s, s.bl_desc); /* opt_len now includes the length of the tree representations, except * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. */ /* Determine the number of bit length codes to send. The pkzip format * requires that at least 4 bit length codes be sent. (appnote.txt says * 3 but the actual value used is 4.) */ for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { break; } } /* Update opt_len to include the bit length tree and counts */ s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", // s->opt_len, s->static_len)); return max_blindex; } /* =========================================================================== * Send the header for a block using dynamic Huffman trees: the counts, the * lengths of the bit length codes, the literal tree and the distance tree. * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. */ function send_all_trees(s, lcodes, dcodes, blcodes) // deflate_state *s; // int lcodes, dcodes, blcodes; /* number of codes for each tree */ { var rank; /* index in bl_order */ //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, // "too many codes"); //Tracev((stderr, "\nbl counts: ")); send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ send_bits(s, dcodes - 1, 5); send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); } //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); } /* =========================================================================== * Check if the data type is TEXT or BINARY, using the following algorithm: * - TEXT if the two conditions below are satisfied: * a) There are no non-portable control characters belonging to the * "black list" (0..6, 14..25, 28..31). * b) There is at least one printable character belonging to the * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). * - BINARY otherwise. * - The following partially-portable control characters form a * "gray list" that is ignored in this detection algorithm: * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). * IN assertion: the fields Freq of dyn_ltree are set. */ function detect_data_type(s) { /* black_mask is the bit mask of black-listed bytes * set bits 0..6, 14..25, and 28..31 * 0xf3ffc07f = binary 11110011111111111100000001111111 */ var black_mask = 0xf3ffc07f; var n; /* Check for non-textual ("black-listed") bytes. */ for (n = 0; n <= 31; n++, black_mask >>>= 1) { if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { return Z_BINARY; } } /* Check for textual ("white-listed") bytes. */ if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { return Z_TEXT; } for (n = 32; n < LITERALS; n++) { if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { return Z_TEXT; } } /* There are no "black-listed" or "white-listed" bytes: * this stream either is empty or has tolerated ("gray-listed") bytes only. */ return Z_BINARY; } var static_init_done = false; /* =========================================================================== * Initialize the tree data structures for a new zlib stream. */ function _tr_init(s) { if (!static_init_done) { tr_static_init(); static_init_done = true; } s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); s.bi_buf = 0; s.bi_valid = 0; /* Initialize the first block of the first file: */ init_block(s); } /* =========================================================================== * Send a stored block */ function _tr_stored_block(s, buf, stored_len, last) //DeflateState *s; //charf *buf; /* input block */ //ulg stored_len; /* length of input block */ //int last; /* one if this is the last block for a file */ { send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ copy_block(s, buf, stored_len); /* with header */ } /* =========================================================================== * Send one empty static block to give enough lookahead for inflate. * This takes 10 bits, of which 7 may remain in the bit buffer. */ function _tr_align(s) { send_bits(s, STATIC_TREES << 1, 3); send_code(s, END_BLOCK, static_ltree); bi_flush(s); } /* =========================================================================== * Determine the best encoding for the current block: dynamic trees, static * trees or store, and output the encoded block to the zip file. */ function _tr_flush_block(s, buf, stored_len, last) //DeflateState *s; //charf *buf; /* input block, or NULL if too old */ //ulg stored_len; /* length of input block */ //int last; /* one if this is the last block for a file */ { var opt_lenb, static_lenb; /* opt_len and static_len in bytes */ var max_blindex = 0; /* index of last bit length code of non zero freq */ /* Build the Huffman trees unless a stored block is forced */ if (s.level > 0) { /* Check if the file is binary or text */ if (s.strm.data_type === Z_UNKNOWN) { s.strm.data_type = detect_data_type(s); } /* Construct the literal and distance trees */ build_tree(s, s.l_desc); // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, // s->static_len)); build_tree(s, s.d_desc); // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, // s->static_len)); /* At this point, opt_len and static_len are the total bit lengths of * the compressed block data, excluding the tree representations. */ /* Build the bit length tree for the above two trees, and get the index * in bl_order of the last bit length code to send. */ max_blindex = build_bl_tree(s); /* Determine the best encoding. Compute the block lengths in bytes. */ opt_lenb = (s.opt_len + 3 + 7) >>> 3; static_lenb = (s.static_len + 3 + 7) >>> 3; // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, // s->last_lit)); if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } } else { // Assert(buf != (char*)0, "lost buf"); opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ } if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { /* 4: two words for the lengths */ /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. * Otherwise we can't have processed more than WSIZE input bytes since * the last block flush, because compression would have been * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to * transform a block into a stored block. */ _tr_stored_block(s, buf, stored_len, last); } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); compress_block(s, static_ltree, static_dtree); } else { send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); compress_block(s, s.dyn_ltree, s.dyn_dtree); } // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); /* The above check is made mod 2^32, for files larger than 512 MB * and uLong implemented on 32 bits. */ init_block(s); if (last) { bi_windup(s); } // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, // s->compressed_len-7*last)); } /* =========================================================================== * Save the match info and tally the frequency counts. Return true if * the current block must be flushed. */ function _tr_tally(s, dist, lc) // deflate_state *s; // unsigned dist; /* distance of matched string */ // unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ { //var out_length, in_length, dcode; s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff; s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff; s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff; s.last_lit++; if (dist === 0) { /* lc is the unmatched char */ s.dyn_ltree[lc * 2]/*.Freq*/++; } else { s.matches++; /* Here, lc is the match length - MIN_MATCH */ dist--; /* dist = match distance - 1 */ //Assert((ush)dist < (ush)MAX_DIST(s) && // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++; s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; } // (!) This block is disabled in zlib defailts, // don't enable it for binary compatibility //#ifdef TRUNCATE_BLOCK // /* Try to guess if it is profitable to stop the current block here */ // if ((s.last_lit & 0x1fff) === 0 && s.level > 2) { // /* Compute an upper bound for the compressed length */ // out_length = s.last_lit*8; // in_length = s.strstart - s.block_start; // // for (dcode = 0; dcode < D_CODES; dcode++) { // out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]); // } // out_length >>>= 3; // //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", // // s->last_lit, in_length, out_length, // // 100L - out_length*100L/in_length)); // if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) { // return true; // } // } //#endif return (s.last_lit === s.lit_bufsize - 1); /* We avoid equality with lit_bufsize because of wraparound at 64K * on 16 bit machines and because stored blocks are restricted to * 64K-1 bytes. */ } exports._tr_init = _tr_init; exports._tr_stored_block = _tr_stored_block; exports._tr_flush_block = _tr_flush_block; exports._tr_tally = _tr_tally; exports._tr_align = _tr_align; },{"../utils/common":41}],53:[function(require,module,exports){ function ZStream() { /* next input byte */ this.input = null; // JS specific, because we have no pointers this.next_in = 0; /* number of bytes available at input */ this.avail_in = 0; /* total number of input bytes read so far */ this.total_in = 0; /* next output byte should be put there */ this.output = null; // JS specific, because we have no pointers this.next_out = 0; /* remaining free space at output */ this.avail_out = 0; /* total number of bytes output so far */ this.total_out = 0; /* last error message, NULL if no error */ this.msg = ''/*Z_NULL*/; /* not visible by applications */ this.state = null; /* best guess about the data type: binary or text */ this.data_type = 2/*Z_UNKNOWN*/; /* adler32 value of the uncompressed data */ this.adler = 0; } module.exports = ZStream; },{}]},{},[10])(10) }); /* FileSaver.js * A saveAs() FileSaver implementation. * 1.3.8 * 2018-03-22 14:03:47 * * By Eli Grey, https://eligrey.com * License: MIT * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md */ /*global self */ /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */ (function(view) { // IE <10 is explicitly unsupported if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { return; } var doc = view.document // only get URL when necessary in case Blob.js hasn't overridden it yet , get_URL = function() { return view.URL || view.webkitURL || view; } , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") , can_use_save_link = "download" in save_link , click = function(node) { var event = new MouseEvent("click"); node.dispatchEvent(event); } , is_safari = /constructor/i.test(view.HTMLElement) || view.safari , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent) , setImmediate = view.setImmediate || view.setTimeout , throw_outside = function(ex) { setImmediate(function() { throw ex; }, 0); } , force_saveable_type = "application/octet-stream" // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to , arbitrary_revoke_timeout = 1000 * 40 // in ms , revoke = function(file) { var revoker = function() { if (typeof file === "string") { // file is an object URL get_URL().revokeObjectURL(file); } else { // file is a File file.remove(); } }; setTimeout(revoker, arbitrary_revoke_timeout); } , dispatch = function(filesaver, event_types, event) { event_types = [].concat(event_types); var i = event_types.length; while (i--) { var listener = filesaver["on" + event_types[i]]; if (typeof listener === "function") { try { listener.call(filesaver, event || filesaver); } catch (ex) { throw_outside(ex); } } } } , auto_bom = function(blob) { // prepend BOM for UTF-8 XML and text/* types (including HTML) // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); } return blob; } , FileSaver = function(blob, name, no_auto_bom) { if (!no_auto_bom) { blob = auto_bom(blob); } // First try a.download, then web filesystem, then object URLs var filesaver = this , type = blob.type , force = type === force_saveable_type , object_url , dispatch_all = function() { dispatch(filesaver, "writestart progress write writeend".split(" ")); } // on any filesys errors revert to saving with object URLs , fs_error = function() { if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { // Safari doesn't allow downloading of blob urls var reader = new FileReader(); reader.onloadend = function() { var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); var popup = view.open(url, '_blank'); if(!popup) view.location.href = url; url=undefined; // release reference before dispatching filesaver.readyState = filesaver.DONE; dispatch_all(); }; reader.readAsDataURL(blob); filesaver.readyState = filesaver.INIT; return; } // don't create more object URLs than needed if (!object_url) { object_url = get_URL().createObjectURL(blob); } if (force) { view.location.href = object_url; } else { var opened = view.open(object_url, "_blank"); if (!opened) { // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html view.location.href = object_url; } } filesaver.readyState = filesaver.DONE; dispatch_all(); revoke(object_url); } ; filesaver.readyState = filesaver.INIT; if (can_use_save_link) { object_url = get_URL().createObjectURL(blob); setImmediate(function() { save_link.href = object_url; save_link.download = name; click(save_link); dispatch_all(); revoke(object_url); filesaver.readyState = filesaver.DONE; }, 0); return; } fs_error(); } , FS_proto = FileSaver.prototype , saveAs = function(blob, name, no_auto_bom) { return new FileSaver(blob, name || blob.name || "download", no_auto_bom); } ; // IE 10+ (native saveAs) if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { return function(blob, name, no_auto_bom) { name = name || blob.name || "download"; if (!no_auto_bom) { blob = auto_bom(blob); } return navigator.msSaveOrOpenBlob(blob, name); }; } // todo: detect chrome extensions & packaged apps //save_link.target = "_blank"; FS_proto.abort = function(){}; FS_proto.readyState = FS_proto.INIT = 0; FS_proto.WRITING = 1; FS_proto.DONE = 2; FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null; view.FileSaver_saveAs = saveAs; }( typeof self !== "undefined" && self || typeof window !== "undefined" && window || undefined )); /* * [js-sha1]{@link https://github.com/emn178/js-sha1} * * @version 0.6.0 * @author Chen, Yi-Cyuan [emn178@gmail.com] * @copyright Chen, Yi-Cyuan 2014-2017 * @license MIT */ /*jslint bitwise: true */ (function() { var root = typeof window === 'object' ? window : {}; var NODE_JS = !root.JS_SHA1_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node; if (NODE_JS) { root = global; } var COMMON_JS = !root.JS_SHA1_NO_COMMON_JS && typeof module === 'object' && module.exports; var AMD = typeof define === 'function' && define.amd; var HEX_CHARS = '0123456789abcdef'.split(''); var EXTRA = [-2147483648, 8388608, 32768, 128]; var SHIFT = [24, 16, 8, 0]; var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer']; var blocks = []; var createOutputMethod = function (outputType) { return function (message) { return new Sha1(true).update(message)[outputType](); }; }; var createMethod = function () { var method = createOutputMethod('hex'); if (NODE_JS) { method = nodeWrap(method); } method.create = function () { return new Sha1(); }; method.update = function (message) { return method.create().update(message); }; for (var i = 0; i < OUTPUT_TYPES.length; ++i) { var type = OUTPUT_TYPES[i]; method[type] = createOutputMethod(type); } return method; }; var nodeWrap = function (method) { var crypto = eval("require('crypto')"); var Buffer = eval("require('buffer').Buffer"); var nodeMethod = function (message) { if (typeof message === 'string') { return crypto.createHash('sha1').update(message, 'utf8').digest('hex'); } else if (message.constructor === ArrayBuffer) { message = new Uint8Array(message); } else if (message.length === undefined) { return method(message); } return crypto.createHash('sha1').update(new Buffer(message)).digest('hex'); }; return nodeMethod; }; function Sha1(sharedMemory) { if (sharedMemory) { blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; this.blocks = blocks; } else { this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } this.h0 = 0x67452301; this.h1 = 0xEFCDAB89; this.h2 = 0x98BADCFE; this.h3 = 0x10325476; this.h4 = 0xC3D2E1F0; this.block = this.start = this.bytes = this.hBytes = 0; this.finalized = this.hashed = false; this.first = true; } Sha1.prototype.update = function (message) { if (this.finalized) { return; } var notString = typeof(message) !== 'string'; if (notString && message.constructor === root.ArrayBuffer) { message = new Uint8Array(message); } var code, index = 0, i, length = message.length || 0, blocks = this.blocks; while (index < length) { if (this.hashed) { this.hashed = false; blocks[0] = this.block; blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; } if(notString) { for (i = this.start; index < length && i < 64; ++index) { blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; } } else { for (i = this.start; index < length && i < 64; ++index) { code = message.charCodeAt(index); if (code < 0x80) { blocks[i >> 2] |= code << SHIFT[i++ & 3]; } else if (code < 0x800) { blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; } else if (code < 0xd800 || code >= 0xe000) { blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; } else { code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; } } } this.lastByteIndex = i; this.bytes += i - this.start; if (i >= 64) { this.block = blocks[16]; this.start = i - 64; this.hash(); this.hashed = true; } else { this.start = i; } } if (this.bytes > 4294967295) { this.hBytes += this.bytes / 4294967296 << 0; this.bytes = this.bytes % 4294967296; } return this; }; Sha1.prototype.finalize = function () { if (this.finalized) { return; } this.finalized = true; var blocks = this.blocks, i = this.lastByteIndex; blocks[16] = this.block; blocks[i >> 2] |= EXTRA[i & 3]; this.block = blocks[16]; if (i >= 56) { if (!this.hashed) { this.hash(); } blocks[0] = this.block; blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; } blocks[14] = this.hBytes << 3 | this.bytes >>> 29; blocks[15] = this.bytes << 3; this.hash(); }; Sha1.prototype.hash = function () { var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4; var f, j, t, blocks = this.blocks; for(j = 16; j < 80; ++j) { t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16]; blocks[j] = (t << 1) | (t >>> 31); } for(j = 0; j < 20; j += 5) { f = (b & c) | ((~b) & d); t = (a << 5) | (a >>> 27); e = t + f + e + 1518500249 + blocks[j] << 0; b = (b << 30) | (b >>> 2); f = (a & b) | ((~a) & c); t = (e << 5) | (e >>> 27); d = t + f + d + 1518500249 + blocks[j + 1] << 0; a = (a << 30) | (a >>> 2); f = (e & a) | ((~e) & b); t = (d << 5) | (d >>> 27); c = t + f + c + 1518500249 + blocks[j + 2] << 0; e = (e << 30) | (e >>> 2); f = (d & e) | ((~d) & a); t = (c << 5) | (c >>> 27); b = t + f + b + 1518500249 + blocks[j + 3] << 0; d = (d << 30) | (d >>> 2); f = (c & d) | ((~c) & e); t = (b << 5) | (b >>> 27); a = t + f + a + 1518500249 + blocks[j + 4] << 0; c = (c << 30) | (c >>> 2); } for(; j < 40; j += 5) { f = b ^ c ^ d; t = (a << 5) | (a >>> 27); e = t + f + e + 1859775393 + blocks[j] << 0; b = (b << 30) | (b >>> 2); f = a ^ b ^ c; t = (e << 5) | (e >>> 27); d = t + f + d + 1859775393 + blocks[j + 1] << 0; a = (a << 30) | (a >>> 2); f = e ^ a ^ b; t = (d << 5) | (d >>> 27); c = t + f + c + 1859775393 + blocks[j + 2] << 0; e = (e << 30) | (e >>> 2); f = d ^ e ^ a; t = (c << 5) | (c >>> 27); b = t + f + b + 1859775393 + blocks[j + 3] << 0; d = (d << 30) | (d >>> 2); f = c ^ d ^ e; t = (b << 5) | (b >>> 27); a = t + f + a + 1859775393 + blocks[j + 4] << 0; c = (c << 30) | (c >>> 2); } for(; j < 60; j += 5) { f = (b & c) | (b & d) | (c & d); t = (a << 5) | (a >>> 27); e = t + f + e - 1894007588 + blocks[j] << 0; b = (b << 30) | (b >>> 2); f = (a & b) | (a & c) | (b & c); t = (e << 5) | (e >>> 27); d = t + f + d - 1894007588 + blocks[j + 1] << 0; a = (a << 30) | (a >>> 2); f = (e & a) | (e & b) | (a & b); t = (d << 5) | (d >>> 27); c = t + f + c - 1894007588 + blocks[j + 2] << 0; e = (e << 30) | (e >>> 2); f = (d & e) | (d & a) | (e & a); t = (c << 5) | (c >>> 27); b = t + f + b - 1894007588 + blocks[j + 3] << 0; d = (d << 30) | (d >>> 2); f = (c & d) | (c & e) | (d & e); t = (b << 5) | (b >>> 27); a = t + f + a - 1894007588 + blocks[j + 4] << 0; c = (c << 30) | (c >>> 2); } for(; j < 80; j += 5) { f = b ^ c ^ d; t = (a << 5) | (a >>> 27); e = t + f + e - 899497514 + blocks[j] << 0; b = (b << 30) | (b >>> 2); f = a ^ b ^ c; t = (e << 5) | (e >>> 27); d = t + f + d - 899497514 + blocks[j + 1] << 0; a = (a << 30) | (a >>> 2); f = e ^ a ^ b; t = (d << 5) | (d >>> 27); c = t + f + c - 899497514 + blocks[j + 2] << 0; e = (e << 30) | (e >>> 2); f = d ^ e ^ a; t = (c << 5) | (c >>> 27); b = t + f + b - 899497514 + blocks[j + 3] << 0; d = (d << 30) | (d >>> 2); f = c ^ d ^ e; t = (b << 5) | (b >>> 27); a = t + f + a - 899497514 + blocks[j + 4] << 0; c = (c << 30) | (c >>> 2); } this.h0 = this.h0 + a << 0; this.h1 = this.h1 + b << 0; this.h2 = this.h2 + c << 0; this.h3 = this.h3 + d << 0; this.h4 = this.h4 + e << 0; }; Sha1.prototype.hex = function () { this.finalize(); var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4; return HEX_CHARS[(h0 >> 28) & 0x0F] + HEX_CHARS[(h0 >> 24) & 0x0F] + HEX_CHARS[(h0 >> 20) & 0x0F] + HEX_CHARS[(h0 >> 16) & 0x0F] + HEX_CHARS[(h0 >> 12) & 0x0F] + HEX_CHARS[(h0 >> 8) & 0x0F] + HEX_CHARS[(h0 >> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] + HEX_CHARS[(h1 >> 28) & 0x0F] + HEX_CHARS[(h1 >> 24) & 0x0F] + HEX_CHARS[(h1 >> 20) & 0x0F] + HEX_CHARS[(h1 >> 16) & 0x0F] + HEX_CHARS[(h1 >> 12) & 0x0F] + HEX_CHARS[(h1 >> 8) & 0x0F] + HEX_CHARS[(h1 >> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] + HEX_CHARS[(h2 >> 28) & 0x0F] + HEX_CHARS[(h2 >> 24) & 0x0F] + HEX_CHARS[(h2 >> 20) & 0x0F] + HEX_CHARS[(h2 >> 16) & 0x0F] + HEX_CHARS[(h2 >> 12) & 0x0F] + HEX_CHARS[(h2 >> 8) & 0x0F] + HEX_CHARS[(h2 >> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] + HEX_CHARS[(h3 >> 28) & 0x0F] + HEX_CHARS[(h3 >> 24) & 0x0F] + HEX_CHARS[(h3 >> 20) & 0x0F] + HEX_CHARS[(h3 >> 16) & 0x0F] + HEX_CHARS[(h3 >> 12) & 0x0F] + HEX_CHARS[(h3 >> 8) & 0x0F] + HEX_CHARS[(h3 >> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] + HEX_CHARS[(h4 >> 28) & 0x0F] + HEX_CHARS[(h4 >> 24) & 0x0F] + HEX_CHARS[(h4 >> 20) & 0x0F] + HEX_CHARS[(h4 >> 16) & 0x0F] + HEX_CHARS[(h4 >> 12) & 0x0F] + HEX_CHARS[(h4 >> 8) & 0x0F] + HEX_CHARS[(h4 >> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F]; }; Sha1.prototype.toString = Sha1.prototype.hex; Sha1.prototype.digest = function () { this.finalize(); var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4; return [ (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF ]; }; Sha1.prototype.array = Sha1.prototype.digest; Sha1.prototype.arrayBuffer = function () { this.finalize(); var buffer = new ArrayBuffer(20); var dataView = new DataView(buffer); dataView.setUint32(0, this.h0); dataView.setUint32(4, this.h1); dataView.setUint32(8, this.h2); dataView.setUint32(12, this.h3); dataView.setUint32(16, this.h4); return buffer; }; var exports = createMethod(); if (COMMON_JS) { module.exports = exports; } else { root.sha1 = exports; if (AMD) { define(function () { return exports; }); } } })(); /* * Copyright (c) 2013-2025 Vanessa Freudenberg * * 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. */ Object.extend(Squeak, { vmPath: "/", platformSubtype: "Browser", osVersion: navigator.userAgent, // might want to parse windowSystem: "HTML", }); // UI namespace window.SqueakJS = {}; ////////////////////////////////////////////////////////////////////////////// // display & event setup ////////////////////////////////////////////////////////////////////////////// function setupFullscreen(display, canvas, options) { // Fullscreen can only be enabled in an event handler. So we check the // fullscreen flag on every mouse down/up and keyboard event. var box = canvas.parentElement, fullscreenEvent = "fullscreenchange", fullscreenElement = "fullscreenElement", fullscreenEnabled = "fullscreenEnabled"; if (!box.requestFullscreen) { [ // Fullscreen support is still very browser-dependent {req: box.webkitRequestFullscreen, exit: document.webkitExitFullscreen, evt: "webkitfullscreenchange", elem: "webkitFullscreenElement", enable: "webkitFullscreenEnabled"}, {req: box.mozRequestFullScreen, exit: document.mozCancelFullScreen, evt: "mozfullscreenchange", elem: "mozFullScreenElement", enable: "mozFullScreenEnabled"}, {req: box.msRequestFullscreen, exit: document.msExitFullscreen, evt: "MSFullscreenChange", elem: "msFullscreenElement", enable: "msFullscreenEnabled"}, ].forEach(function(browser) { if (browser.req) { box.requestFullscreen = browser.req; document.exitFullscreen = browser.exit; fullscreenEvent = browser.evt; fullscreenElement = browser.elem; fullscreenEnabled = browser.enable; } }); } // If the user canceled fullscreen, turn off the fullscreen flag so // we don't try to enable it again in the next event function fullscreenChange(fullscreen) { display.fullscreen = fullscreen; var fullwindow = fullscreen || options.fullscreen; box.style.background = fullwindow ? 'black' : ''; box.style.border = fullwindow ? 'none' : ''; box.style.borderRadius = fullwindow ? '0px' : ''; setTimeout(onresize, 0); } var checkFullscreen; if (box.requestFullscreen) { document.addEventListener(fullscreenEvent, function(){fullscreenChange(box == document[fullscreenElement]);}); checkFullscreen = function() { if (document[fullscreenEnabled] && (box == document[fullscreenElement]) != display.fullscreen) { if (display.fullscreen) box.requestFullscreen(); else document.exitFullscreen(); } }; } else { var isFullscreen = false; checkFullscreen = function() { if (isFullscreen != display.fullscreen) { isFullscreen = display.fullscreen; fullscreenChange(isFullscreen); } }; } return checkFullscreen; } function recordModifiers(evt, display) { var shiftPressed = evt.shiftKey, ctrlPressed = evt.ctrlKey && !evt.altKey, cmdPressed = (display.isMac ? evt.metaKey : evt.altKey && !evt.ctrlKey) || display.cmdButtonTouched, modifiers = (shiftPressed ? Squeak.Keyboard_Shift : 0) + (ctrlPressed ? Squeak.Keyboard_Ctrl : 0) + (cmdPressed ? Squeak.Keyboard_Cmd : 0); display.buttons = (display.buttons & ~Squeak.Keyboard_All) | modifiers; return modifiers; } var canUseMouseOffset = null; function updateMousePos(evt, canvas, display) { if (canUseMouseOffset === null) { // Per https://caniuse.com/mdn-api_mouseevent_offsetx, essentially all *current* // browsers support `offsetX`/`offsetY`, but it does little harm to fall back to the // older `layerX`/`layerY` for now. canUseMouseOffset = 'offsetX' in evt; } var evtX = canUseMouseOffset ? evt.offsetX : evt.layerX, evtY = canUseMouseOffset ? evt.offsetY : evt.layerY; if (display.cursorCanvas) { display.cursorCanvas.style.left = (evtX + canvas.offsetLeft + display.cursorOffsetX) + "px"; display.cursorCanvas.style.top = (evtY + canvas.offsetTop + display.cursorOffsetY) + "px"; } var x = (evtX * canvas.width / canvas.offsetWidth) | 0, y = (evtY * canvas.height / canvas.offsetHeight) | 0, w = display.width || canvas.width, h = display.height || canvas.height; // clamp to display size display.mouseX = Math.max(0, Math.min(w, x)); display.mouseY = Math.max(0, Math.min(h, y)); } function recordMouseEvent(what, evt, canvas, display, options) { updateMousePos(evt, canvas, display); if (!display.vm) return; var buttons = display.buttons & Squeak.Mouse_All; switch (what) { case 'mousedown': switch (evt.button || 0) { case 0: buttons = Squeak.Mouse_Red; break; // left case 1: buttons = Squeak.Mouse_Yellow; break; // middle case 2: buttons = Squeak.Mouse_Blue; break; // right } if (buttons === Squeak.Mouse_Red && (evt.altKey || evt.metaKey) || display.cmdButtonTouched) buttons = Squeak.Mouse_Yellow; // emulate middle-click if (options.swapButtons) if (buttons == Squeak.Mouse_Yellow) buttons = Squeak.Mouse_Blue; else if (buttons == Squeak.Mouse_Blue) buttons = Squeak.Mouse_Yellow; break; case 'mousemove': break; // nothing more to do case 'mouseup': buttons = 0; break; } display.buttons = buttons | recordModifiers(evt, display); if (display.eventQueue) { display.eventQueue.push([ Squeak.EventTypeMouse, evt.timeStamp, // converted to Squeak time in makeSqueakEvent() display.mouseX, display.mouseY, display.buttons & Squeak.Mouse_All, display.buttons >> 3, ]); if (display.signalInputEvent) display.signalInputEvent(); } display.idle = 0; if (what === 'mouseup') display.runFor(100, what); // process copy/paste or fullscreen flag change else display.runNow(what); // don't wait for timeout to run } function recordWheelEvent(evt, display) { if (!display.vm) return; if (!display.eventQueue || !display.vm.image.isSpur) { // for old images, queue wheel events as ctrl+up/down fakeCmdOrCtrlKey(evt.deltaY > 0 ? 31 : 30, evt.timeStamp, display); return; // TODO: use or set VM parameter 48 (see vmParameterAt) } var squeakEvt = [ Squeak.EventTypeMouseWheel, evt.timeStamp, // converted to Squeak time in makeSqueakEvent() evt.deltaX, -evt.deltaY, display.buttons & Squeak.Mouse_All, display.buttons >> 3, ]; display.eventQueue.push(squeakEvt); if (display.signalInputEvent) display.signalInputEvent(); display.idle = 0; if (display.runNow) display.runNow('wheel'); // don't wait for timeout to run } // Squeak traditional keycodes are MacRoman var MacRomanToUnicode = [ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, ]; var UnicodeToMacRoman = {}; for (var i = 0; i < MacRomanToUnicode.length; i++) UnicodeToMacRoman[MacRomanToUnicode[i]] = i + 128; function recordKeyboardEvent(unicode, timestamp, display) { if (!display.vm) return; var macCode = UnicodeToMacRoman[unicode] || (unicode < 128 ? unicode : 0); var modifiersAndKey = (display.buttons >> 3) << 8 | macCode; if (display.eventQueue) { display.eventQueue.push([ Squeak.EventTypeKeyboard, timestamp, // converted to Squeak time in makeSqueakEvent() macCode, // MacRoman Squeak.EventKeyChar, display.buttons >> 3, unicode, // Unicode ]); if (display.signalInputEvent) display.signalInputEvent(); // There are some old images that use both event-based // and polling primitives. To make those work, keep the // last key event display.keys[0] = modifiersAndKey; } else if (modifiersAndKey === display.vm.interruptKeycode) { display.vm.interruptPending = true; } else { // no event queue, queue keys the old-fashioned way display.keys.push(modifiersAndKey); } display.idle = 0; if (display.runNow) display.runNow('keyboard'); // don't wait for timeout to run } function recordDragDropEvent(type, evt, canvas, display) { if (!display.vm || !display.eventQueue) return; updateMousePos(evt, canvas, display); display.eventQueue.push([ Squeak.EventTypeDragDropFiles, evt.timeStamp, // converted to Squeak time in makeSqueakEvent() type, display.mouseX, display.mouseY, display.buttons >> 3, display.droppedFiles.length, ]); if (display.signalInputEvent) display.signalInputEvent(); display.idle = 0; if (display.runNow) display.runNow('drag-drop'); // don't wait for timeout to run } function fakeCmdOrCtrlKey(key, timestamp, display) { // set both Cmd and Ctrl bit, because we don't know what the image wants display.buttons &= ~Squeak.Keyboard_All; // remove all modifiers display.buttons |= Squeak.Keyboard_Cmd | Squeak.Keyboard_Ctrl; display.keys = []; // flush other keys recordKeyboardEvent(key, timestamp, display); } function makeSqueakEvent(evt, sqEvtBuf, sqTimeOffset) { sqEvtBuf[0] = evt[0]; sqEvtBuf[1] = (evt[1] - sqTimeOffset) & Squeak.MillisecondClockMask; for (var i = 2; i < evt.length; i++) sqEvtBuf[i] = evt[i]; } function createSqueakDisplay(canvas, options) { options = options || {}; if (options.fullscreen) { document.body.style.margin = 0; document.body.style.backgroundColor = 'black'; canvas.style.border = 'none'; canvas.style.borderRadius = '0px'; document.ontouchmove = function(evt) { evt.preventDefault(); }; } var display = { context: canvas.getContext("2d"), fullscreen: false, width: 0, // if 0, VM uses canvas.width height: 0, // if 0, VM uses canvas.height scale: 1, // VM will use window.devicePixelRatio if highdpi is enabled, also changes when touch-zooming highdpi: options.highdpi, // TODO: use or set VM parameter 48 (see vmParameterAt) mouseX: 0, mouseY: 0, buttons: 0, keys: [], cmdButtonTouched: null, // touchscreen button pressed (touch ID) eventQueue: null, // only used if image uses event primitives clipboardString: '', clipboardStringChanged: false, handlingEvent: '', // set to 'mouse' or 'keyboard' while handling an event cursorCanvas: options.cursor !== false && document.getElementById("sqCursor") || document.createElement("canvas"), cursorOffsetX: 0, cursorOffsetY: 0, droppedFiles: [], signalInputEvent: null, // function set by VM changedCallback: null, // invoked when display size/scale changes // additional functions added below }; if (options.pixelated) { canvas.classList.add("pixelated"); display.cursorCanvas && display.cursorCanvas.classList.add("pixelated"); } display.reset = function() { display.eventQueue = null; display.signalInputEvent = null; display.lastTick = 0; display.getNextEvent = function(firstEvtBuf, firstOffset) { // might be called from VM to get queued event display.eventQueue = []; // create queue on first call display.eventQueue.push = function(evt) { display.eventQueue.offset = Date.now() - evt[1]; // get epoch from first event delete display.eventQueue.push; // use original push from now on display.eventQueue.push(evt); }; display.getNextEvent = function(evtBuf, timeOffset) { var evt = display.eventQueue.shift(); if (evt) makeSqueakEvent(evt, evtBuf, timeOffset - display.eventQueue.offset); else evtBuf[0] = Squeak.EventTypeNone; }; display.getNextEvent(firstEvtBuf, firstOffset); }; }; display.reset(); var checkFullscreen = setupFullscreen(display, canvas, options); display.fullscreenRequest = function(fullscreen, thenDo) { // called from primitive to change fullscreen mode if (display.fullscreen != fullscreen) { display.fullscreen = fullscreen; display.resizeTodo = thenDo; // called after resizing display.resizeTodoTimeout = setTimeout(display.resizeDone, 1000); checkFullscreen(); } else thenDo(); }; display.resizeDone = function() { clearTimeout(display.resizeTodoTimeout); var todo = display.resizeTodo; if (todo) { display.resizeTodo = null; todo(); } }; display.clear = function() { canvas.width = canvas.width; }; display.setTitle = function(title) { document.title = title; }; display.showBanner = function(msg, style) { style = style || display.context.canvas.style || {}; var ctx = display.context; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = style.color || "#F90"; ctx.font = style.font || "bold 48px sans-serif"; if (!style.font && ctx.measureText(msg).width > canvas.width) ctx.font = "bold 24px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(msg, canvas.width / 2, canvas.height / 2); }; display.showProgress = function(value, style) { style = style || display.context.canvas.style || {}; var ctx = display.context, w = (canvas.width / 3) | 0, h = 24, x = canvas.width * 0.5 - w / 2, y = canvas.height * 0.5 + 2 * h; ctx.fillStyle = style.background || "#000"; ctx.fillRect(x, y, w, h); ctx.lineWidth = 2; ctx.strokeStyle = style.stroke || "#F90"; ctx.strokeRect(x, y, w, h); ctx.fillStyle = style.fill || "#F90"; ctx.fillRect(x, y, w * value, h); }; display.executeClipboardPasteKey = function(text, timestamp) { if (!display.vm) return true; try { display.clipboardString = text; // simulate paste event for Squeak fakeCmdOrCtrlKey('v'.charCodeAt(0), timestamp, display); } catch(err) { console.error("paste error " + err); } }; display.executeClipboardCopyKey = function(key, timestamp) { if (!display.vm) return true; // simulate copy event for Squeak so it places its text in clipboard display.clipboardStringChanged = false; fakeCmdOrCtrlKey((key || 'c').charCodeAt(0), timestamp, display); var start = Date.now(); // now interpret until Squeak has copied to the clipboard while (!display.clipboardStringChanged && Date.now() - start < 500) display.vm.interpret(20); if (!display.clipboardStringChanged) return; // got it, now copy to the system clipboard try { return display.clipboardString; } catch(err) { console.error("copy error " + err); } }; canvas.onmousedown = function(evt) { checkFullscreen(); recordMouseEvent('mousedown', evt, canvas, display, options); evt.preventDefault(); return false; }; canvas.onmouseup = function(evt) { recordMouseEvent('mouseup', evt, canvas, display, options); checkFullscreen(); evt.preventDefault(); }; canvas.onmousemove = function(evt) { recordMouseEvent('mousemove', evt, canvas, display, options); evt.preventDefault(); }; canvas.onwheel = function(evt) { recordWheelEvent(evt, display); evt.preventDefault(); }; canvas.oncontextmenu = function() { return false; }; // touch event handling var touch = { state: 'idle', button: 0, x: 0, y: 0, dist: 0, down: {}, }; function touchToMouse(evt) { if (evt.touches.length) { // average all touch positions // but ignore the cmd button touch var x = 0, y = 0, n = 0; for (var i = 0; i < evt.touches.length; i++) { if (evt.touches[i].identifier === display.cmdButtonTouched) continue; x += evt.touches[i].pageX; y += evt.touches[i].pageY; n++; } if (n > 0) { touch.x = x / n; touch.y = y / n; } } return { timeStamp: evt.timeStamp, button: touch.button, offsetX: touch.x - canvas.offsetLeft, offsetY: touch.y - canvas.offsetTop, }; } function dd(ax, ay, bx, by) {var x = ax - bx, y = ay - by; return Math.sqrt(x*x + y*y);} function dist(a, b) {return dd(a.pageX, a.pageY, b.pageX, b.pageY);} function adjustCanvas(l, t, w, h) { var cursorCanvas = display.cursorCanvas, cssScale = w / canvas.width, ratio = display.highdpi ? window.devicePixelRatio : 1, pixelScale = cssScale * ratio; canvas.style.left = (l|0) + "px"; canvas.style.top = (t|0) + "px"; canvas.style.width = (w|0) + "px"; canvas.style.height = (h|0) + "px"; if (cursorCanvas) { cursorCanvas.style.left = (l + display.cursorOffsetX + display.mouseX * cssScale|0) + "px"; cursorCanvas.style.top = (t + display.cursorOffsetY + display.mouseY * cssScale|0) + "px"; cursorCanvas.style.width = (cursorCanvas.width * pixelScale|0) + "px"; cursorCanvas.style.height = (cursorCanvas.height * pixelScale|0) + "px"; } // if pixelation is not forced, turn it on for integer scales if (!options.pixelated) { if (pixelScale % 1 === 0 || pixelScale > 5) { canvas.classList.add("pixelated"); cursorCanvas && cursorCanvas.classList.add("pixelated"); } else { canvas.classList.remove("pixelated"); cursorCanvas && display.cursorCanvas.classList.remove("pixelated"); } } display.css = { left: l, top: t, width: w, height: h, scale: cssScale, pixelScale: pixelScale, ratio: ratio, }; if (display.changedCallback) display.changedCallback(); return cssScale; } // zooming/panning with two fingers var maxZoom = 5; function zoomStart(evt) { touch.dist = dist(evt.touches[0], evt.touches[1]); touch.down.x = touch.x; touch.down.y = touch.y; touch.down.dist = touch.dist; touch.down.left = canvas.offsetLeft; touch.down.top = canvas.offsetTop; touch.down.width = canvas.offsetWidth; touch.down.height = canvas.offsetHeight; // store original canvas bounds if (!touch.orig) touch.orig = { left: touch.down.left, top: touch.down.top, right: touch.down.left + touch.down.width, bottom: touch.down.top + touch.down.height, width: touch.down.width, height: touch.down.height, }; } function zoomMove(evt) { if (evt.touches.length < 2) return; touch.dist = dist(evt.touches[0], evt.touches[1]); var minScale = touch.orig.width / touch.down.width, //nowScale = dent(touch.dist / touch.down.dist, 0.8, 1, 1.5), nowScale = touch.dist / touch.down.dist, scale = Math.min(Math.max(nowScale, minScale * 0.95), minScale * maxZoom), w = touch.down.width * scale, h = touch.orig.height * w / touch.orig.width, l = touch.down.left - (touch.down.x - touch.down.left) * (scale - 1) + (touch.x - touch.down.x), t = touch.down.top - (touch.down.y - touch.down.top) * (scale - 1) + (touch.y - touch.down.y); // allow to rubber-band by 20px for feedback l = Math.max(Math.min(l, touch.orig.left + 20), touch.orig.right - w - 20); t = Math.max(Math.min(t, touch.orig.top + 20), touch.orig.bottom - h - 20); adjustCanvas(l, t, w, h); } function zoomEnd(evt) { var l = canvas.offsetLeft, t = canvas.offsetTop, w = canvas.offsetWidth, h = canvas.offsetHeight; w = Math.min(Math.max(w, touch.orig.width), touch.orig.width * maxZoom); h = touch.orig.height * w / touch.orig.width; l = Math.max(Math.min(l, touch.orig.left), touch.orig.right - w); t = Math.max(Math.min(t, touch.orig.top), touch.orig.bottom - h); var scale = adjustCanvas(l, t, w, h); if ((scale - display.scale) < 0.0001) { touch.orig = null; onresize(); } } // State machine to distinguish between 1st/2nd mouse button and zoom/pan: // * if moved, or no 2nd finger within 100ms of 1st down, start mousing // * if fingers moved significantly within 200ms of 2nd down, start zooming // * if touch ended within this time, generate click (down+up) // * otherwise, start mousing with 2nd button // * also, ignore finger on cmd button // When mousing, always generate a move event before down event so that // mouseover eventhandlers in image work better canvas.ontouchstart = function(evt) { evt.preventDefault(); var e = touchToMouse(evt); for (var i = 0; i < evt.changedTouches.length; i++) { if (evt.changedTouches[i].identifier === display.cmdButtonTouched) continue; switch (touch.state) { case 'idle': touch.state = 'got1stFinger'; touch.first = e; setTimeout(function(){ if (touch.state !== 'got1stFinger') return; touch.state = 'mousing'; touch.button = e.button = 0; recordMouseEvent('mousemove', e, canvas, display, options); recordMouseEvent('mousedown', e, canvas, display, options); }, 100); break; case 'got1stFinger': touch.state = 'got2ndFinger'; zoomStart(evt); setTimeout(function(){ if (touch.state !== 'got2ndFinger') return; var didMove = Math.abs(touch.down.dist - touch.dist) > 10 || dd(touch.down.x, touch.down.y, touch.x, touch.y) > 10; if (didMove) { touch.state = 'zooming'; } else { touch.state = 'mousing'; touch.button = e.button = 2; recordMouseEvent('mousemove', e, canvas, display, options); recordMouseEvent('mousedown', e, canvas, display, options); } }, 200); break; } } }; canvas.ontouchmove = function(evt) { evt.preventDefault(); var e = touchToMouse(evt); switch (touch.state) { case 'got1stFinger': touch.state = 'mousing'; touch.button = e.button = 0; recordMouseEvent('mousemove', e, canvas, display, options); recordMouseEvent('mousedown', e, canvas, display, options); break; case 'mousing': recordMouseEvent('mousemove', e, canvas, display, options); break; case 'got2ndFinger': if (evt.touches.length > 1) touch.dist = dist(evt.touches[0], evt.touches[1]); break; case 'zooming': zoomMove(evt); break; } }; canvas.ontouchend = function(evt) { evt.preventDefault(); checkFullscreen(); var e = touchToMouse(evt); var n = evt.touches.length; if (Array.from(evt.touches).findIndex(t => t.identifier === display.cmdButtonTouched) >= 0) n--; for (var i = 0; i < evt.changedTouches.length; i++) { if (evt.changedTouches[i].identifier === display.cmdButtonTouched) { continue; } switch (touch.state) { case 'mousing': if (n > 0) break; touch.state = 'idle'; recordMouseEvent('mouseup', e, canvas, display, options); break; case 'got1stFinger': touch.state = 'idle'; touch.button = e.button = 0; recordMouseEvent('mousemove', e, canvas, display, options); recordMouseEvent('mousedown', e, canvas, display, options); recordMouseEvent('mouseup', e, canvas, display, options); break; case 'got2ndFinger': touch.state = 'mousing'; touch.button = e.button = 2; recordMouseEvent('mousemove', e, canvas, display, options); recordMouseEvent('mousedown', e, canvas, display, options); break; case 'zooming': if (n > 0) break; touch.state = 'idle'; zoomEnd(); break; } } }; canvas.ontouchcancel = function(evt) { canvas.ontouchend(evt); }; // cursorCanvas shows Squeak cursor if (display.cursorCanvas) { var absolute = window.getComputedStyle(canvas).position === "absolute"; display.cursorCanvas.style.display = "block"; display.cursorCanvas.style.position = absolute ? "absolute": "fixed"; display.cursorCanvas.style.cursor = "none"; display.cursorCanvas.style.background = "transparent"; display.cursorCanvas.style.pointerEvents = "none"; canvas.parentElement.appendChild(display.cursorCanvas); canvas.style.cursor = "none"; } // keyboard stuff // create hidden input field to capture not only keyboard events // but also copy/paste and input events (for dead keys) var input = document.createElement("input"); input.setAttribute("autocomplete", "off"); input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); input.style.position = "absolute"; input.style.left = "-1000px"; canvas.parentElement.appendChild(input); // touch-keyboard buttons if ('ontouchstart' in document) { // button to show on-screen keyboard var keyboardButton = document.createElement('div'); keyboardButton.innerHTML = ''; keyboardButton.setAttribute('style', 'position:fixed;right:0;bottom:0;background-color:rgba(128,128,128,0.5);border-radius:5px'); canvas.parentElement.appendChild(keyboardButton); keyboardButton.onmousedown = function(evt) { // show on-screen keyboard input.focus({ preventScroll: true }); evt.preventDefault(); }; keyboardButton.ontouchstart = keyboardButton.onmousedown; // modifier button for CMD key var cmdButton = document.createElement('div'); cmdButton.innerHTML = '⌘'; cmdButton.setAttribute('style', 'position:fixed;left:0;background-color:rgba(128,128,128,0.5);width:50px;height:50px;font-size:30px;text-align:center;vertical-align:middle;line-height:50px;border-radius:5px'); if (window.visualViewport) { // fix position of button when virtual keyboard is shown const vv = window.visualViewport; const fixPosition = () => cmdButton.style.top = `${vv.height}px`; vv.addEventListener('resize', fixPosition); cmdButton.style.transform = `translateY(-100%)`; fixPosition(); } else { cmdButton.style.bottom = '0'; } canvas.parentElement.appendChild(cmdButton); cmdButton.ontouchstart = function(evt) { display.cmdButtonTouched = evt.changedTouches[0].identifier; cmdButton.style.backgroundColor = 'rgba(255,255,255,0.5)'; evt.preventDefault(); evt.stopPropagation(); }; cmdButton.ontouchend = function(evt) { display.cmdButtonTouched = null; cmdButton.style.backgroundColor = 'rgba(128,128,128,0.5)'; evt.preventDefault(); evt.stopPropagation(); }; cmdButton.ontouchcancel = cmdButton.ontouchend; } else { // keep focus on input field input.onblur = function() { input.focus({ preventScroll: true }); }; input.focus({ preventScroll: true }); } display.isMac = navigator.userAgent.includes("Mac"); // emulate keypress events var deadKey = false, // true if last keydown was a dead key deadChars = []; input.oninput = function(evt) { if (!display.vm) return true; if (evt.inputType === "insertText" // regular key, or Chrome || evt.inputType === "insertCompositionText" // Firefox, Chrome || evt.inputType === "insertFromComposition") // Safari { // generate backspace to delete inserted dead chars var hadDeadChars = deadChars.length > 0; if (hadDeadChars) { var oldButtons = display.buttons; display.buttons &= ~Squeak.Keyboard_All; // remove all modifiers for (var i = 0; i < deadChars.length; i++) { recordKeyboardEvent(8, evt.timeStamp, display); } display.buttons = oldButtons; deadChars = []; } // generate keyboard events for each character // single input could be many characters, e.g. for emoji var chars = Array.from(evt.data); // split by surrogate pairs for (var i = 0; i < chars.length; i++) { var unicode = chars[i].codePointAt(0); // codePointAt combines pair into unicode recordKeyboardEvent(unicode, evt.timeStamp, display); } if (!hadDeadChars && evt.isComposing && evt.inputType === "insertCompositionText") { deadChars = deadChars.concat(chars); } } if (!deadChars.length) resetInput(); }; input.onkeydown = function(evt) { checkFullscreen(); if (!display.vm) return true; deadKey = evt.key === "Dead"; if (deadKey) return; // let browser handle dead keys recordModifiers(evt, display); var squeakCode = ({ 8: 8, // Backspace 9: 9, // Tab 13: 13, // Return 27: 27, // Escape 32: 32, // Space 33: 11, // PageUp 34: 12, // PageDown 35: 4, // End 36: 1, // Home 37: 28, // Left 38: 30, // Up 39: 29, // Right 40: 31, // Down 45: 5, // Insert 46: 127, // Delete })[evt.keyCode]; if (squeakCode) { // special key pressed recordKeyboardEvent(squeakCode, evt.timeStamp, display); return evt.preventDefault(); } // copy/paste new-style if (display.isMac ? evt.metaKey : evt.ctrlKey) { switch (evt.key) { case "c": case "x": if (!navigator.clipboard) return; // fire document.oncopy/oncut var text = display.executeClipboardCopyKey(evt.key, evt.timeStamp); if (typeof text === 'string') { navigator.clipboard.writeText(text) .catch(function(err) { console.error("display: copy error " + err.message); }); } return evt.preventDefault(); case "v": if (!navigator.clipboard) return; // fire document.onpaste navigator.clipboard.readText() .then(function(text) { display.executeClipboardPasteKey(text, evt.timeStamp); }) .catch(function(err) { console.error("display: paste error " + err.message); }); return evt.preventDefault(); } } if (evt.key.length !== 1) return; // let browser handle other keys if (display.buttons & (Squeak.Keyboard_Cmd | Squeak.Keyboard_Ctrl)) { var code = evt.key.toLowerCase().charCodeAt(0); if ((display.buttons & Squeak.Keyboard_Ctrl) && code >= 96 && code < 127) code &= 0x1F; // ctrl- recordKeyboardEvent(code, evt.timeStamp, display); return evt.preventDefault(); } }; input.onkeyup = function(evt) { if (!display.vm) return true; recordModifiers(evt, display); }; function resetInput() { input.value = "**"; input.selectionStart = 1; input.selectionEnd = 1; } resetInput(); // hack to generate arrow keys when moving the cursor (e.g. via spacebar on iPhone) // we're not getting any events for that but the cursor (selection) changes if ('ontouchstart' in document) { let count = 0; // count how often the interval has run after the first move setInterval(() => { const direction = input.selectionStart - 1; if (direction === 0) { count = 0; return; } // move cursor once, then not for 500ms, then every 250ms if (count === 0 || count > 2) { const key = direction < 1 ? 28 : 29; // arrow left or right recordKeyboardEvent(key, Date.now(), display); } input.selectionStart = 1; input.selectionEnd = 1; count++; }, 250); } // more copy/paste if (navigator.clipboard) { // new-style copy/paste (all modern browsers) display.readFromSystemClipboard = () => display.handlingEvent && navigator.clipboard.readText() .then(text => display.clipboardString = text) .catch(err => console.error("readFromSystemClipboard " + err.message)); display.writeToSystemClipboard = () => display.handlingEvent && navigator.clipboard.writeText(display.clipboardString) .then(() => display.clipboardStringChanged = false) .catch(err => console.error("writeToSystemClipboard " + err.message)); } else { // old-style copy/paste document.oncopy = function(evt, key) { var text = display.executeClipboardCopyKey(key, evt.timeStamp); if (typeof text === 'string') { evt.clipboardData.setData("Text", text); } evt.preventDefault(); }; document.oncut = function(evt) { if (!display.vm) return true; document.oncopy(evt, 'x'); }; document.onpaste = function(evt) { var text = evt.clipboardData.getData('Text'); display.executeClipboardPasteKey(text, evt.timeStamp); evt.preventDefault(); }; } // do not use addEventListener, we want to replace any previous drop handler function dragEventHasFiles(evt) { for (var i = 0; i < evt.dataTransfer.types.length; i++) if (evt.dataTransfer.types[i] == 'Files') return true; return false; } document.ondragover = function(evt) { evt.preventDefault(); if (!dragEventHasFiles(evt)) { evt.dataTransfer.dropEffect = 'none'; } else { evt.dataTransfer.dropEffect = 'copy'; recordDragDropEvent(Squeak.EventDragMove, evt, canvas, display); } }; document.ondragenter = function(evt) { if (!dragEventHasFiles(evt)) return; recordDragDropEvent(Squeak.EventDragEnter, evt, canvas, display); }; document.ondragleave = function(evt) { if (!dragEventHasFiles(evt)) return; recordDragDropEvent(Squeak.EventDragLeave, evt, canvas, display); }; document.ondrop = function(evt) { evt.preventDefault(); if (!dragEventHasFiles(evt)) return false; var files = [].slice.call(evt.dataTransfer.files), loaded = [], image, imageName = null; display.droppedFiles = []; files.forEach(function(f) { var path = options.root + f.name; display.droppedFiles.push(path); var reader = new FileReader(); reader.onload = function () { var buffer = this.result; Squeak.filePut(path, buffer); loaded.push(path); if (!image && /.*image$/.test(path) && (!display.vm || confirm("Run " + f.name + " now?\n(cancel to use as file)"))) { image = buffer; imageName = path; } if (loaded.length == files.length) { if (image) { if (display.vm) { display.quitFlag = true; options.onQuit = function(vm, display, options) { options.onQuit = null; SqueakJS.appName = imageName.replace(/.*\//,'').replace(/\.image$/,''); SqueakJS.runImage(image, imageName, display, options); }; } else { SqueakJS.appName = imageName.replace(/.*\//,'').replace(/\.image$/,''); SqueakJS.runImage(image, imageName, display, options); } } else { recordDragDropEvent(Squeak.EventDragDrop, evt, canvas, display); } } }; reader.readAsArrayBuffer(f); }); return false; }; var debounceTimeout; function onresize() { if (touch.orig) return; // manually resized // call resizeDone only if window size didn't change for 300ms var debounceWidth = window.innerWidth, debounceHeight = window.innerHeight; clearTimeout(debounceTimeout); debounceTimeout = setTimeout(function() { if (debounceWidth == window.innerWidth && debounceHeight == window.innerHeight) display.resizeDone(); else onresize(); }, 300); // CSS won't let us do what we want so we will layout the canvas ourselves. var x = 0, y = 0, w = window.innerWidth, h = window.innerHeight, paddingX = 0, // padding outside canvas paddingY = 0; // above are the default values for laying out the canvas if (!options.fixedWidth) { // set canvas resolution if (!options.minWidth) options.minWidth = 700; if (!options.minHeight) options.minHeight = 700; var defaultScale = display.highdpi ? window.devicePixelRatio : 1, scaleW = w < options.minWidth ? options.minWidth / w : defaultScale, scaleH = h < options.minHeight ? options.minHeight / h : defaultScale, scale = Math.max(scaleW, scaleH); display.width = Math.floor(w * scale); display.height = Math.floor(h * scale); display.scale = w / display.width; } else { // fixed resolution and aspect ratio display.width = options.fixedWidth; display.height = options.fixedHeight; var wantRatio = display.width / display.height, haveRatio = w / h; if (haveRatio > wantRatio) { paddingX = w - Math.floor(h * wantRatio); } else { paddingY = h - Math.floor(w / wantRatio); } display.scale = (w - paddingX) / display.width; } // set resolution if (canvas.width != display.width || canvas.height != display.height) { var preserveScreen = options.fixedWidth || !display.resizeTodo, // preserve unless changing fullscreen imgData = preserveScreen && display.context.getImageData(0, 0, canvas.width, canvas.height); canvas.width = display.width; canvas.height = display.height; if (imgData) display.context.putImageData(imgData, 0, 0); } // set canvas and cursor canvas size, position, pixelation adjustCanvas( x + Math.floor(paddingX / 2), y + Math.floor(paddingY / 2), w - paddingX, h - paddingY ); } if (!options.embedded) { onresize(); window.onresize = onresize; } return display; } function setupSpinner(vm, options) { var spinner = options.spinner; if (!spinner) return null; spinner.onmousedown = function(evt) { if (confirm(SqueakJS.appName + " is busy. Interrupt?")) vm.interruptPending = true; }; return spinner.style; } var spinnerAngle = 0, becameBusy = 0; function updateSpinner(spinner, idleMS, vm, display) { var busy = idleMS === 0, animating = vm.lastTick - display.lastTick < 500; if (!busy || animating) { spinner.display = "none"; becameBusy = 0; } else { if (becameBusy === 0) { becameBusy = vm.lastTick; } else if (vm.lastTick - becameBusy > 1000) { spinner.display = "block"; spinnerAngle = (spinnerAngle + 30) % 360; spinner.webkitTransform = spinner.transform = "rotate(" + spinnerAngle + "deg)"; } } } ////////////////////////////////////////////////////////////////////////////// // main loop ////////////////////////////////////////////////////////////////////////////// var loop; // holds timeout for main loop SqueakJS.runImage = function(buffer, name, display, options) { window.onbeforeunload = function(evt) { var msg = SqueakJS.appName + " is still running"; evt.returnValue = msg; return msg; }; window.clearTimeout(loop); display.reset(); display.clear(); display.showBanner("Loading " + SqueakJS.appName); display.showProgress(0); window.setTimeout(function readImageAsync() { var image = new Squeak.Image(name); image.readFromBuffer(buffer, function startRunning() { display.quitFlag = false; var vm = new Squeak.Interpreter(image, display, options); SqueakJS.vm = vm; Squeak.Settings["squeakImageName"] = name; display.clear(); display.showBanner("Starting " + SqueakJS.appName); var spinner = setupSpinner(vm, options); function run() { try { if (display.quitFlag) SqueakJS.onQuit(vm, display, options); else vm.interpret(50, function runAgain(ms) { if (ms == "sleep") ms = 200; if (spinner) updateSpinner(spinner, ms, vm, display); loop = window.setTimeout(run, ms); }); } catch(error) { console.error(error); alert(error); } } display.runNow = function(event) { window.clearTimeout(loop); display.handlingEvent = event; run(); display.handlingEvent = ''; }; display.runFor = function(milliseconds, event) { var stoptime = Date.now() + milliseconds; do { if (display.quitFlag) return; display.runNow(event); } while (Date.now() < stoptime); }; if (options.onStart) options.onStart(vm, display, options); run(); }, function readProgress(value) {display.showProgress(value);}); }, 0); }; function processOptions(options) { var search = (location.hash || location.search).slice(1), args = search && search.split("&"); if (args) for (var i = 0; i < args.length; i++) { var keyAndVal = args[i].split("="), key = keyAndVal[0], val = true; if (keyAndVal.length > 1) { val = decodeURIComponent(keyAndVal.slice(1).join("=")); if (val.match(/^(true|false|null|[0-9"[{].*)$/)) try { val = JSON.parse(val); } catch(e) { if (val[0] === "[") val = val.slice(1,-1).split(","); // handle string arrays // if not JSON use string itself } } options[key] = val; } var root = Squeak.splitFilePath(options.root || "/").fullname; Squeak.dirCreate(root, true); if (!/\/$/.test(root)) root += "/"; options.root = root; if (options.w) options.fixedWidth = options.w; if (options.h) options.fixedHeight = options.h; if (options.fixedWidth && !options.fixedHeight) options.fixedHeight = options.fixedWidth * 3 / 4 | 0; if (options.fixedHeight && !options.fixedWidth) options.fixedWidth = options.fixedHeight * 4 / 3 | 0; if (options.fixedWidth && options.fixedHeight) options.fullscreen = true; SqueakJS.options = options; } function fetchTemplates(options) { if (options.templates) { if (options.templates.constructor === Array) { var templates = {}; options.templates.forEach(function(path){ templates[path] = path; }); options.templates = templates; } for (var path in options.templates) { var dir = path[0] == "/" ? path : options.root + path, baseUrl = new URL(options.url, document.baseURI).href.split(/[?#]/)[0], url = Squeak.splitUrl(options.templates[path], baseUrl).full; if (url.endsWith("/")) url = url.slice(0,-1); if (url.endsWith("/.")) url = url.slice(0,-2); Squeak.fetchTemplateDir(dir, url); } } } function processFile(file, display, options, thenDo) { Squeak.filePut(options.root + file.name, file.data, function() { console.log("Stored " + options.root + file.name); if (file.zip) { processZip(file, display, options, thenDo); } else { thenDo(); } }); } function processZip(file, display, options, thenDo) { display.showBanner("Analyzing " + file.name); JSZip.loadAsync(file.data, { createFolders: true }).then(function(zip) { var todo = []; zip.forEach(function(filename, meta) { if (filename.startsWith("__MACOSX/") || filename.endsWith(".DS_Store")) return; // skip macOS metadata if (meta.dir) { filename = filename.replace(/\/$/, ""); Squeak.dirCreate(options.root + filename, true); return; } if (!options.image.name && filename.match(/\.image$/)) options.image.name = filename; if (options.forceDownload || !Squeak.fileExists(options.root + filename)) { todo.push(filename); } else if (options.image.name === filename) { // image exists, need to fetch it from storage var _thenDo = thenDo; thenDo = function() { Squeak.fileGet(options.root + filename, function(data) { options.image.data = data; return _thenDo(); }, function onError() { Squeak.fileDelete(options.root + file.name); return processZip(file, display, options, _thenDo); }); }; } }); if (todo.length === 0) return thenDo(); var done = 0; display.showBanner("Unzipping " + file.name); display.showProgress(0); todo.forEach(function(filename){ console.log("Inflating " + file.name + ": " + filename); function progress(x) { display.showProgress((x.percent / 100 + done) / todo.length); } zip.file(filename).async("arraybuffer", progress).then(function(buffer){ console.log("Expanded size of " + filename + ": " + buffer.byteLength + " bytes"); var unzipped = {}; if (options.image.name === filename) unzipped = options.image; unzipped.name = filename; unzipped.data = buffer; processFile(unzipped, display, options, function() { if (++done === todo.length) thenDo(); }); }); }); }); } function checkExisting(file, display, options, ifExists, ifNotExists) { if (!Squeak.fileExists(options.root + file.name)) return ifNotExists(); if (file.image || file.zip) { // if it's the image or a zip, load from file storage Squeak.fileGet(options.root + file.name, function(data) { file.data = data; if (file.zip) processZip(file, display, options, ifExists); else ifExists(); }, function onError() { // if error, download it Squeak.fileDelete(options.root + file.name); return ifNotExists(); }); } else { // for all other files assume they're okay ifExists(); } } function downloadFile(file, display, options, thenDo) { display.showBanner("Downloading " + file.name); var rq = new XMLHttpRequest(), proxy = options.proxy || ""; rq.open('GET', proxy + file.url); if (options.ajax) rq.setRequestHeader("X-Requested-With", "XMLHttpRequest"); rq.responseType = 'arraybuffer'; rq.onprogress = function(e) { if (e.lengthComputable) display.showProgress(e.loaded / e.total); }; rq.onload = function(e) { if (this.status == 200) { file.data = this.response; processFile(file, display, options, thenDo); } else this.onerror(this.statusText); }; rq.onerror = function(e) { if (options.proxy) { console.error(Squeak.bytesAsString(new Uint8Array(this.response))); return alert("Failed to download:\n" + file.url); } var proxy = Squeak.defaultCORSProxy, retry = new XMLHttpRequest(); console.warn('Retrying with CORS proxy: ' + proxy + file.url); retry.open('GET', proxy + file.url); if (options.ajax) retry.setRequestHeader("X-Requested-With", "XMLHttpRequest"); retry.responseType = rq.responseType; retry.onprogress = rq.onprogress; retry.onload = rq.onload; retry.onerror = function() { console.error(Squeak.bytesAsString(new Uint8Array(this.response))); alert("Failed to download:\n" + file.url);}; retry.send(); }; rq.send(); } function fetchFiles(files, display, options, thenDo) { // check if files exist locally and download if nessecary function getNextFile() { if (files.length === 0) return thenDo(); var file = files.shift(), forceDownload = options.forceDownload || file.forceDownload; if (forceDownload) downloadFile(file, display, options, getNextFile); else checkExisting(file, display, options, function ifExists() { getNextFile(); }, function ifNotExists() { downloadFile(file, display, options, getNextFile); }); } getNextFile(); } SqueakJS.runSqueak = function(imageUrl, canvas, options={}) { if (!canvas) { canvas = document.createElement("canvas"); canvas.style.position = "absolute"; canvas.style.left = "0"; canvas.style.top = "0"; canvas.style.width = "100%"; canvas.style.height = "100%"; document.body.appendChild(canvas); } // we need to fetch all files first, then run the image processOptions(options); if (imageUrl && imageUrl.endsWith(".zip")) { options.zip = imageUrl.match(/[^\/]*$/)[0]; options.url = imageUrl.replace(/[^\/]*$/, ""); imageUrl = null; } if (!imageUrl && options.image) imageUrl = options.image; var baseUrl = options.url || ""; if (!baseUrl && imageUrl && imageUrl.replace(/[^\/]*$/, "")) { baseUrl = imageUrl.replace(/[^\/]*$/, ""); imageUrl = imageUrl.replace(/^.*\//, ""); } options.url = baseUrl; if (baseUrl[0] === "/" && baseUrl[1] !== "/" && baseUrl.length > 1 && options.root === "/") { options.root = baseUrl; } fetchTemplates(options); var display = createSqueakDisplay(canvas, options), image = {url: null, name: null, image: true, data: null}, files = []; display.argv = options.argv; if (imageUrl) { var url = Squeak.splitUrl(imageUrl, baseUrl); image.url = url.full; image.name = url.filename; } if (options.files) { options.files.forEach(function(f) { var url = Squeak.splitUrl(f, baseUrl); if (image.name === url.filename) ; else if (!image.url && f.match(/\.image$/)) { image.name = url.filename; image.url = url.full; } else { files.push({url: url.full, name: url.filename}); } }); } if (options.zip) { var zips = typeof options.zip === "string" ? [options.zip] : options.zip; zips.forEach(function(zip) { var url = Squeak.splitUrl(zip, baseUrl); var prefix = ""; // if filename has no version info, but full url has it, use full url as prefix if (!url.filename.match(/[0-9]/) && url.uptoslash.match(/[0-9]/)) { prefix = url.uptoslash.replace(/^[^:]+:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_"); } files.push({url: url.full, name: prefix + url.filename, zip: true}); }); } if (image.url) files.push(image); if (options.document) { var url = Squeak.splitUrl(options.document, baseUrl); files.push({url: url.full, name: url.filename, forceDownload: options.forceDownload !== false}); display.documentName = options.root + url.filename; } options.image = image; fetchFiles(files, display, options, function thenDo() { Squeak.fsck(); // will run async var image = options.image; if (!image.name) return alert("could not find an image"); if (!image.data) return alert("could not find image " + image.name); SqueakJS.appName = options.appName || image.name.replace(/(.*\/|\.image$)/g, ""); SqueakJS.runImage(image.data, options.root + image.name, display, options); }); return display; }; SqueakJS.quitSqueak = function() { SqueakJS.vm.quitFlag = true; }; SqueakJS.onQuit = function(vm, display, options) { window.onbeforeunload = null; display.vm = null; if (options.spinner) options.spinner.style.display = "none"; if (options.onQuit) options.onQuit(vm, display, options); else display.showBanner(SqueakJS.appName + " stopped."); }; ////////////////////////////////////////////////////////////////////////////// // browser stuff ////////////////////////////////////////////////////////////////////////////// if (window.applicationCache) { applicationCache.addEventListener('updateready', function() { // use original appName from options var appName = window.SqueakJS && SqueakJS.options && SqueakJS.options.appName || "SqueakJS"; if (confirm(appName + ' has been updated. Restart now?')) { window.onbeforeunload = null; window.location.reload(); } }); } })();