diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..a60525a8225e75638d7f0dd3467c13dea2f3a5fa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +benchmark/benchmark.image filter=lfs diff=lfs merge=lfs -text +demo/mini.image filter=lfs diff=lfs merge=lfs -text +demo/squeakjs.image filter=lfs diff=lfs merge=lfs -text +headless/headless.image filter=lfs diff=lfs merge=lfs -text +ws/client/cuis.image filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..245b454ea7f92180f4a536f3154a7a645b983bbb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules/ +gh-pages +dist/*.min.js* +*.code-workspace diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000000000000000000000000000000000000..1283c15cd2e6a4fc318c80de626889eda8cf49f7 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,2 @@ +tasks: + - init: nvm install && npm install && npm run build diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000000000000000000000000000000000..517f38666b4bdd12a4bc40ce6a349bba06cf0ca8 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22.14.0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..37036d98d8d546dce2fedcace88ef56db3c25f98 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: node_js +node_js: + - "6" +before_install: + - export CHROME_URL=https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64 + - export CHROME_REV=$(curl -s ${CHROME_URL}/LAST_CHANGE) + - curl ${CHROME_URL}/${CHROME_REV}/chrome-linux.zip --create-dirs -o out/chrome-linux.zip + - unzip -q out/chrome-linux.zip -d out + - export CHROME_CANARY_BIN=$PWD/out/chrome-linux/chrome + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +install: + - cd tests + - npm install +before_script: + - curl -f -s -L --retry 3 -o Squeak-5.1.tgz https://dl.bintray.com/squeakjs/testing/Squeak-5.1.tgz + - tar xzf Squeak-5.1.tgz -C ./resources +script: npm test +cache: + directories: + - tests/node_modules diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..413a09ba61509cd4c9571890aaa66f4b34997727 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2013-2025 Vanessa Freudenberg +Copyright (c) 2016 Fabio Niephaus, Google Inc. + +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. diff --git a/README.md b/README.md index a76ddc98f32cb46759c190cff731f689c07a425e..2c215b13657d3bab860cdfd6deb74a672cc6d4c4 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,229 @@ ---- -title: Scratch0 5 -emoji: 🦀 -colorFrom: blue -colorTo: green -sdk: static -pinned: false ---- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +SqueakJS: A Squeak VM for the Web and Node.js +============================================= + +SqueakJS is a runtime engine for [Squeak][squeak] Smalltalk written in pure JavaScript. It also works for many other OpenSmalltalk-compatible images. + +Embedding a Smalltalk application in your webpage can be as simple as: + + SqueakJS.runSqueak(imageUrl); + +but you probably want to give it some more options (refer to the examples). + +The interpreter core is divided in a number of `vm.*.js` modules, internal plugins in `vm.plugins.*.js` modules and external plugins in the "plugins" directory. The Just-in-Time compiler is optional ("jit.js") and can be replaced with your own. + +There are a number of interfaces: +* browser: the regular HTML interface lets you use SqueakJS on your own web page. Just include "squeak.js". +* headless browser: a headless VM. It lets you use SqueakJS in your browser without a direct UI (you can create your own UI with a plugin). Include "squeak_headless.js" and add an "imageName" parameter to your website URL (eg. https://example.com/my/page.html?imageName=./example.image) or call the Javascript function "fetchImageAndRun('https://example.com/my/example.image')" to start the specified image. +* Node.js: another headless VM. It lets you use SqueakJS as a Node.js application via "node squeak_node.js ". + +For discussions, please use the [vm-dev mailing list][vm-dev]. Also, please visit the [project home page][homepage]! + +Running it +---------- +**Simplest** + +* [Run a minimal image][mini]. This is the simple demo included in this repo. +* Or run [Etoys][etoys]. Everything except the image and template files is in this repo. +* Or similarly, [Scratch][scratch], also in here. + +**Run your own Squeak image in the browser** + +* Drag an image from your local files into the [launcher][run]. +* ... and all the other demo pages (see above) accept dropped images, too. + +**Run your own Squeak image from the command line** + +* Install a recent version of Node.js +* Run example image: `node squeak_node.js headless/headless.image` + +**Run an interactive shell based on WebSocket communication with Cuis image** + +* Install a recent version of Node.js +* Go to [ws][ws] and execute `start_server.sh` in a first shell and `start_client.sh` in a second shell. +* After initialization it should be possible to issue Smalltalk statements which will be executed in the Smalltalk image. +* Try commands like: `Object allSubclasses size` `1837468731248764723 * 321653125376153761` `Collection allSubclasses collect: [ :c | c name ]` + +**Which Browser** + +All modern desktop browsers should work. Mobile browsers work too, but most Squeak images assume a keyboard and mouse. YMMV. + +Fixes to improve browser compatibility are highly welcome! + +If your browser does not support ES6 modules try the full or headless SqueakJS VM as a single file (aka bundle) in the [Distribution][dist] directory. + + +Installing locally +------------------ +* clone the [github repo][repo]: + ``` + git clone https://github.com/codefrau/SqueakJS.git + ``` + or download and unpack the [ZIP archive][zip] +* serve the SqueakJS directory using a local web server. + + TIP: If you have Node.js, try + ``` + cd SqueakJS + npx serve + ``` + which will run a webserver on port 3000. +* in a web browser, open http://localhost:3000/run/ and pick one of the images, or drag and drop your own + +Now Squeak should be running. +The reason for having to run from a web server is because the image is loaded with an XMLHttpRequest which does not work with a file URL. Alternatively, you could just open SqueakJS/run/index.html and drop in a local image. + +Using (self contained) bundled files +------------------------------------ +* select your preferred type of interface (browser or headless) +* use the appropriate file (`squeak_bundle.js` resp. `squeak_headless_bundle.js`) from the [Distribution][dist] directory +* you can also build minified bundles using `npm run build` + +How to modify it +---------------- +* use any text editor +* you have to reload the page for your changes to take effect + +How to share your changes +------------------------- +* easiest for me is if you create a [pull request][pullreq] +* otherwise, send me patches + +Contributions are very welcome! + +Things to work on +----------------- +SqueakJS is intended to run any Squeak image. It can already load any image from the original 1996 Squeak release to the latest Cog-Spur release, including 64-bit and Sista variants. But various pieces (primitives in various plugins) are still missing, in particular 3D graphics and networking (however, see [Croquet][jasmine] which supports both, but should be generalized). Also, we should make pre-Spur 64 bit images load. And, it would be nice to make it work on as many browsers as possible, especially on mobile touch devices. + +As for optimizing the way to go is an optimizing JIT compiler. The current JIT is very simple and does not optimize at all, it only eloiminates the interpreter's instruction decoding overhead. Since we can't access or manipulate the JavaScript stack, we might want that compiler to inline as much as possible, but keep the call sequence flat so we can return to the browser at any time. Even better (but potentially more complicated) is actually using the JavaScript stack, just like Eliot's Stack VM uses the C stack. I have done some [advanced JIT mockups][jit]. To make BitBlt fast, we could probably use WASM or even WebGL. + +To make SqueakJS useful beyond running existing Squeak images, we should use the JavaScript bridge to write a native HTML UI which would certainly be much faster than BitBlt (Craig Latta has done some interesting work towards that in [Caffeine][caffeine]). + +Better Networking would be interesting, too. The SocketPlugin currently does allows HTTP(S) requests and WebSockets. How about implementing low level Socket support based on HTTP-tunneling? The VM can run in a WebWorker. How about parallelizing the VM with WebWorkers? + +Also interesting would be wrapping it in a native app, maybe via [Electron][electron] similar to [Sugarizer][sugarizer], which uses SqueakJS to run Etoys. + +There's a gazillion exciting things to do :) + + -- Vanessa Freudenberg (codefrau) + + [squeak]: https://squeak.org/ + [repo]: https://github.com/codefrau/SqueakJS + [vm-dev]: http://lists.squeakfoundation.org/mailman/listinfo/vm-dev + [homepage]: https://squeak.js.org/ + [run]: https://squeak.js.org/run/ + [mini]: https://squeak.js.org/demo/simple.html + [etoys]: https://squeak.js.org/etoys/ + [scratch]: https://squeak.js.org/scratch/ + [jasmine]: https://github.com/codefrau/jasmine + [caffeine]: https://caffeine.js.org/ + [jit]: https://squeak.js.org/docs/jit.md.html + [ws]: https://github.com/codefrau/SqueakJS/tree/main/ws + [dist]: https://github.com/codefrau/SqueakJS/tree/main/dist + [zip]: https://github.com/codefrau/SqueakJS/archive/main.zip + [pullreq]: https://help.github.com/articles/using-pull-requests + [electron]: https://www.electronjs.org + [sugarizer]: https://github.com/llaske/sugarizer + + +Changelog +--------- + 2025-05-04: 1.3.3 minor FFI, OpenGL, and other fixes/improvements + 2025-04-06: 1.3.2 use our own CORS proxy, add welcome=false option, minor fixes + 2025-03-29: 1.3.1 add 'w', 'h', 'embedded' canvas options, minor fixes + 2025-03-28: 1.3.0 add OpenGL support, canvas is optional, fix socket plugin bug + 2025-02-19: 1.2.4 fix isAssociation for JS Bridge, optimize loading image with many objects + 2024-09-28: 1.2.3 fix primitiveInputSemaphore, fix iOS keyboard + 2024-06-22: 1.2.2 make copy/paste work on mobile + 2024-05-27: 1.2.1 add virtual cmd button, fix touch events + 2024-03-25: 1.2.0 add FFI and MIDI plugins, JIT for Sista bytecodes, JPEG write prim, fix keyboard input, copy/paste, scroll wheel, highdpi, allow ES6 in source + 2023-11-24: 1.1.2 fixed BitBlt bug (symptom reported 9 years ago, thanks to Agustin Martinez for narrowing it down), add object pinning, support keyboard in ancient Scratch images + 2023-10-24: 1.1.1 workarounds for Cuis 6 + 2023-10-23: 1.1.0 implement Etoys project saving (image segment export), drag-n-drop directories + 2023-09-30: 1.0.6 fixes + 2022-11-19: 1.0.5 fixes, add highdpi mode, add image format for Squeak 6 + 2021-05-31: 1.0.4 fixes + 2021-03-21: 1.0.3 headless fixes (Erik Stel); fixes object-as-method + 2021-02-07: 1.0.2 new one-way become prim (Christoph Tiede); JIT-compile Array at:/at:put: + 2021-01-05: 1.0.1 fixes some primitives to properly pop the stack + 2020-12-20: 1.0 supports 64 bits and Sista + 2020-06-20: renamed "master" branch to "main" + 2020-06-20: 0.9.9 JSBridge additions (Bill Burdick), fixes + 2020-04-08: renamed github account to "codefrau" + 2020-01-26: 0.9.8 split into modules (Erik Stel), fixes + 2019-01-03: 0.9.7 minor fixes + 2018-03-13: 0.9.6 minor fixes + 2016-11-08: 0.9.5 more fixes + 2016-10-20: 0.9.4 fixes + 2016-09-08: 0.9.3 add partial GC (5x faster become / allInstances) + 2016-08-25: 0.9.2 add keyboard on iOS + 2016-08-03: 0.9.1 fixes + 2016-07-29: 0.9 Spur support, stdout, SpeechPlugin, zipped images + 2016-06-28: 0.8.3 add SocketPlugin for http/https connections + 2016-04-07: 0.8.2 better touch handling, debugging, CORS, lint + 2016-01-08: 0.8.1 windows keyboard fixes, 'new' operator fixed + 2015-11-24: 0.8 minor fixes + 2015-08-13: 0.7.9 make work on iOS again + 2015-07-18: 0.7.8 fix keyboard + 2015-06-09: 0.7.7 fix thisContext + 2015-04-27: 0.7.6 revert JIT, minor fixes + 2015-04-14: 0.7.5 JIT optimizations by HPI students (reverted in 0.7.6) + 2015-02-18: 0.7.4 make pre-release image work + 2015-01-30: 0.7.3 JSBridge: fix closure callbacks + 2015-01-25: 0.7.2 JSBridge: add asJSObject + 2014-12-22: 0.7.1 cursor shapes + 2014-12-04: 0.7 support finalization of weak references + 2014-11-28: 0.6.8 JSBridge with callbacks + 2014-11-20: 0.6.7 implement JavaScriptPlugin + 2014-11-18: 0.6.6 implement DropPlugin + 2014-11-14: 0.6.5 add generated Balloon2D plugin + 2014-11-06: 0.6.4 add generic run page + 2014-10-28: 0.6.3 pass options via URL + 2014-10-27: add JPEG plugin + 2014-10-25: add template files + 2014-10-23: 0.6.2 fixes + 2014-10-21: 0.6.1 add image segment loading + 2014-10-18: 0.6 move squeak.js out of lib dir + 2014-10-13: 0.5.9 microphone support + 2014-10-09: 0.5.8 fixes + 2014-10-07: 0.5.7 even more plugins generated + 2014-10-07: 0.5.6 add quitSqueak and onQuit + 2014-10-07: 0.5.5 generated ScratchPlugin + 2014-10-06: 0.5.4 replace BitBltPlugin by generated + 2014-10-06: 0.5.3 SoundGenerationPlugin, Matrix2x3Plugin, FloatArrayPlugin + 2014-10-05: ZipPlugin + 2014-10-04: MiscPrimitivePlugin + 2014-10-03: VMMakerJS generates LargeIntegersPlugin + 2014-09-30: 0.5.2 more JIT + 2014-09-28: 0.5.1 JIT fixes + 2014-09-26: 0.5 add JIT compiler + 2014-09-22: v8 optimizations + 2014-09-20: 0.4.6 sound output support + 2014-09-13: 0.4.5 clipboartd fixes + 2014-09-12: 0.4.4 cut/copy/paste in stand-alone + 2014-09-09: 0.4.3 some scratch prims + 2014-09-09: 0.4.2 idle fixes + 2014-09-05: 0.4.1 scratch fixes + 2014-09-04: 0.4.0 runs scratch + 2014-08-31: switch old/new primitives + 2014-08-27: event-based input + 2014-08-21: exception handling + 2014-07-25: 0.3.3 fullscreen support + 2014-07-18: 0.3.2 benchmarking (timfel) + 2014-07-18: 0.3.1 deferred display + 2014-07-16: 0.3.0 closure support + 2014-07-14: 0.2.3 IE optimization (timfel) + 2014-07-11: 0.2.2 drag-n-drop + 2014-07-07: 0.2.1 fixes for IE11 (timfel) + 2014-07-04: 0.2 runs Etoys + 2014-06-27: Balloon2D (krono) + 2014-06-03: stand-alone version + 2014-05-29: 0.1 added version number + 2014-05-27: WarpBlt + 2014-05-07: image saving + 2014-04-23: file support + 2013-12-20: public release + 2013-12-14: colored bitblt + 2013-12-03: first pixels on screen + 2013-11-29: GC + 2013-11-22: runs 43 byte codes and 8 sends successfully + 2013-11-07: initial commit diff --git a/benchmark/benchmark.css b/benchmark/benchmark.css new file mode 100644 index 0000000000000000000000000000000000000000..d93a9b9fa601fd1d31bcbfbf984ca2a766e9502a --- /dev/null +++ b/benchmark/benchmark.css @@ -0,0 +1,36 @@ +body { + background-color: #eae6d1; + font-family: sans-serif; +} +canvas { + display: block; + margin-left: auto; + margin-right: auto; + cursor: default; +} +canvas.pixelated { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} +div#sqSpinner { + position: fixed; + margin: auto; top: 0; left: 0; bottom: 0; right: 0; + width: 100px; + height: 100px; + border-radius: 50px; + background: rgba(0, 0, 0, 0.3); + box-shadow: 0 0 5px 5px #F90; + display: none; + transform: rotate(30deg); +} +div#sqSpinner > div { + position: absolute; + top: 45px; + left: 5px; + width: 90px; + height: 10px; + border-radius: 5px; + box-shadow: 0 0 5px 5px #F90; +} diff --git a/benchmark/benchmark.html b/benchmark/benchmark.html new file mode 100644 index 0000000000000000000000000000000000000000..10470e9c38ef7fb64c7d10da03acb629ba872b99 --- /dev/null +++ b/benchmark/benchmark.html @@ -0,0 +1,41 @@ + + + + +SqueakJS Benchmarks + + + + + + + +

SqueakJS Benchmarks

+

+ This is Tim Felgentreff's page for SqueakJS benchmarking. + Please wait until the runs are finished. +

+ +
+ + diff --git a/benchmark/benchmark.image b/benchmark/benchmark.image new file mode 100644 index 0000000000000000000000000000000000000000..3ffae87f4bbd2b6b1fe392f2f73c7fa5bb0a0a8c --- /dev/null +++ b/benchmark/benchmark.image @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f9252e14a52e916675451f46e3505d1f659e902e3f2defc9aa2993c2b1565d86 +size 650948 diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js new file mode 100644 index 0000000000000000000000000000000000000000..1b53f597fb36c4f81cde1f9c47269bf673eb7c44 --- /dev/null +++ b/benchmark/benchmark.js @@ -0,0 +1,72 @@ +/* + * 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. + */ + +window.stopVM = false; + +window.onload = function() { + + // Wrap the file close primitive to stop after the benchmark + // output file has been written + var origFileClose = Squeak.Primitives.prototype.fileClose + Squeak.Primitives.prototype.fileClose = (function (file) { + var contents = Squeak.bytesAsString(new Uint8Array(file.contents.buffer)); + var r = document.getElementById("results"); + r.innerHTML = "Your machine: " + navigator.userAgent + "
" + + "Your results:
" + contents.replace(/\r/g, "
") + "
" + saveToLively(contents); + SqueakJS.quitSqueak(); + return origFileClose.apply(this, arguments); + }); + + if (!Date.now) { + Date.now = function now() { + return new Date().getTime(); + }; + } + + function saveToLively(contents) { + var address = (window.google && + google.loader && + google.loader.ClientLocation && + google.loader.ClientLocation.address) || {city: "unknown city", country: "unknown country"}; + contents = navigator.userAgent + "\n" + + address.city + "\n" + + address.country + "\n" + + Date.now() + "\n" + + Squeak.vmVersion + "\n" + + contents; + var oReq = new XMLHttpRequest(); + oReq.open( + "get", + "http://www.lively-kernel.org/babelsberg/nodejs/SqueakJSServer/?benchmarkResults=" + + encodeURIComponent(contents), + true); + oReq.send(); + }; + + SqueakJS.runSqueak('benchmark.image', sqCanvas, { + spinner: sqSpinner, + onQuit: function() { + sqCanvas.style.display = "none"; + }, + }); +}; diff --git a/demo/JSBridge.st b/demo/JSBridge.st new file mode 100644 index 0000000000000000000000000000000000000000..11e605980e0ce6ac59aa1ab54f32389f153c4fc9 --- /dev/null +++ b/demo/JSBridge.st @@ -0,0 +1 @@ +Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: 'bf 07/12/2016 21:05' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" | h1 | h1 := JS document createElement: 'h1'. h1 innerHTML: 'Hello World!!'. h1 style: 'position: absolute; color: blue'. JS document body append: h1. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. ' + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..bb638e5decddeeaca1911e8f21cf192473cfdd73 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1416 @@ +{ + "name": "@codefrau/squeakjs", + "version": "1.3.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@codefrau/squeakjs", + "version": "1.3.3", + "license": "MIT", + "devDependencies": { + "http-server": "^14.1.1", + "rimraf": "^6.0.1", + "rollup": "^4.37.0", + "uglify-js": "^3.19.3" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.37.0.tgz", + "integrity": "sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.37.0.tgz", + "integrity": "sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.37.0.tgz", + "integrity": "sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.37.0.tgz", + "integrity": "sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.37.0.tgz", + "integrity": "sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.37.0.tgz", + "integrity": "sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.37.0.tgz", + "integrity": "sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.37.0.tgz", + "integrity": "sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.37.0.tgz", + "integrity": "sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.37.0.tgz", + "integrity": "sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.37.0.tgz", + "integrity": "sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.37.0.tgz", + "integrity": "sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.37.0.tgz", + "integrity": "sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.37.0.tgz", + "integrity": "sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.37.0.tgz", + "integrity": "sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz", + "integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.37.0.tgz", + "integrity": "sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.37.0.tgz", + "integrity": "sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.37.0.tgz", + "integrity": "sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.37.0.tgz", + "integrity": "sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.37.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.37.0.tgz", + "integrity": "sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.37.0", + "@rollup/rollup-android-arm64": "4.37.0", + "@rollup/rollup-darwin-arm64": "4.37.0", + "@rollup/rollup-darwin-x64": "4.37.0", + "@rollup/rollup-freebsd-arm64": "4.37.0", + "@rollup/rollup-freebsd-x64": "4.37.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.37.0", + "@rollup/rollup-linux-arm-musleabihf": "4.37.0", + "@rollup/rollup-linux-arm64-gnu": "4.37.0", + "@rollup/rollup-linux-arm64-musl": "4.37.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.37.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.37.0", + "@rollup/rollup-linux-riscv64-gnu": "4.37.0", + "@rollup/rollup-linux-riscv64-musl": "4.37.0", + "@rollup/rollup-linux-s390x-gnu": "4.37.0", + "@rollup/rollup-linux-x64-gnu": "4.37.0", + "@rollup/rollup-linux-x64-musl": "4.37.0", + "@rollup/rollup-win32-arm64-msvc": "4.37.0", + "@rollup/rollup-win32-ia32-msvc": "4.37.0", + "@rollup/rollup-win32-x64-msvc": "4.37.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..672d54c00c3c4e8c20a0dfc7ad8a5c9539a8c768 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "@codefrau/squeakjs", + "version": "1.3.3", + "description": "Virtual Machine for Squeak Smalltalk and derivatives", + "author": "Vanessa Freudenberg (https://twitter.com/codefrau)", + "repository": "https://github.com/codefrau/SqueakJS", + "license": "MIT", + "browser": "squeak.js", + "main": "squeak_node.js", + "unpkg": "dist/squeak_bundle.js", + "jsdelivr": "dist/squeak_bundle.js", + "files": [ + "*.js", + "plugins/", + "ffi/", + "lib/", + "lib_node/", + "dist/squeak_bundle.js" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "start": "http-server -o run/", + "dev": "http-server", + "build:cleanup": "rimraf dist", + "build:bundle": "rollup squeak.js --file dist/squeak_bundle.js --format iife && rollup squeak_headless.js --file dist/squeak_headless_bundle.js", + "build:minify": "uglifyjs dist/squeak_bundle.js -o dist/squeak_bundle.min.js -c -m --source-map && uglifyjs dist/squeak_headless_bundle.js -o dist/squeak_headless_bundle.min.js -c -m --source-map", + "build": "npm run build:cleanup && npm run build:bundle && npm run build:minify" + }, + "devDependencies": { + "http-server": "^14.1.1", + "rimraf": "^6.0.1", + "rollup": "^4.37.0", + "uglify-js": "^3.19.3" + } +} diff --git a/plugins/ADPCMCodecPlugin.js b/plugins/ADPCMCodecPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..d7ba826bc8994ec5972ada2ffe82cc03ee752496 --- /dev/null +++ b/plugins/ADPCMCodecPlugin.js @@ -0,0 +1,590 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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 coded so that plugins can be run from Squeak. */ + +function getInterpreter() { + return interpreterProxy; +} + + +/* 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 halt() { + ; +} + + +/* 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; +} + +function msg(s) { + console.log(moduleName + ": " + s); +} + + +/* 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; + var count; + + rcvr = interpreterProxy.stackValue(1); + count = 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 diff --git a/plugins/B2DPlugin.js b/plugins/B2DPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..837fce55d441afb2b0d188fbaa973fe8b271f03f --- /dev/null +++ b/plugins/B2DPlugin.js @@ -0,0 +1,7740 @@ +/* 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() { +"use strict"; + +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); } +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, -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, -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) & ~GEEdgeFillsInvalid); +} + +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); +} + + +/* Ignore dispatch errors when translating to C + (since we have no entry point for #error in the VM proxy) */ + +function errorWrongIndex() { + ; +} + + +/* 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: + errorWrongIndex(); + 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: + errorWrongIndex(); + 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: + errorWrongIndex(); + 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 halt() { + ; +} + +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(engine); +} + + +/* 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(engine); + 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); + } + if ((leftFill === rightFill) && false) { + return interpreterProxy.pop(6); + } + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + } + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + interpreterProxy.pop(1); +} + + +/* Primitive. Set the BitBlt plugin to use. */ + +function primitiveSetBitBltPlugin() { + var i; + var length; + var needReload; + var pluginName; + var ptr; + + + /* 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(); + } + ptr = 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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(engine); + 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, i); + } + 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, i); + } + 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, yValue); + + /* 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(engine); + 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: + errorWrongIndex(); + break; + case 2: + returnWideLineWidth(); + break; + case 3: + returnWideBezierWidth(); + break; + } + lineWidth = dispatchReturnValue; + switch (type) { + case 0: + case 1: + errorWrongIndex(); + 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 diff --git a/plugins/B3DAcceleratorPlugin.js b/plugins/B3DAcceleratorPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..2f50dbce73b833c83a476f1dedd31d87559c0e51 --- /dev/null +++ b/plugins/B3DAcceleratorPlugin.js @@ -0,0 +1,622 @@ +function B3DAcceleratorPlugin() { + "use strict"; + + var DEBUG = 0; // 0 = off, 1 = some, 2 = lots + var DEBUG_WAIT = false; // wait after each frame + + 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() + + /* Renderer creation flags: + B3D_SOFTWARE_RENDERER: Enable use of software renderers + B3D_HARDWARE_RENDERER: Enable use of hardware renderers + B3D_STENCIL_BUFFER: Request stencil buffer + B3D_ANTIALIASING: Request antialiasing in the renderer. + B3D_STEREO: Request stereo visual from the renderer + B3D_SYNCVBL: Request VBL sync + More flags may be added - if they are not supported by the platform + code the creation primitive should fail. + */ + var B3D_SOFTWARE_RENDERER = 0x0001; + var B3D_HARDWARE_RENDERER = 0x0002; + var B3D_STENCIL_BUFFER = 0x0004; + var B3D_ANTIALIASING = 0x0008; // ignored + var B3D_STEREO = 0x0010; // ignored + var B3D_SYNCVBL = 0x0020; // ignored + + 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); + var d = 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 */ + DEBUG > 0 && console.log("B3DAccel: primitiveAllocateTexture", w, h, d); + 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; + var level = this.interpreterProxy.stackIntegerValue(0); + DEBUG > 0 && console.log("B3DAccel: primitiveSetVerboseLevel", level); + 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; + DEBUG > 1 && console.log("B3DAccel: primitiveCompositeTexture", texHandle, x, y, w, h, translucent); + 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 & ~(B3D_HARDWARE_RENDERER | B3D_SOFTWARE_RENDERER | B3D_STENCIL_BUFFER)) + return false; + DEBUG > 0 && console.log("B3DAccel: primitiveCreateRendererFlags", x, y, w, h, flags); + // 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); + DEBUG > 0 && console.log("B3DAccel: created renderer", rendererId); + return this.primHandler.popNandPushIfOK(argCount + 1, renderer); + }, + + primitiveDestroyRenderer: function(argCount) { + if (argCount !== 1) return false; + if (!this.currentFromStack(0)) return false; + DEBUG > 0 && console.log("B3DAccel: primitiveDestroyRenderer", currentRenderer.rendererId); + 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; + DEBUG > 0 && console.log("B3DAccel: destroyed renderer", rendererId); + 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; + DEBUG > 0 && console.log("B3DAccel: primitiveDestroyTexture", texture); + OpenGL.glDeleteTextures(1, [texture]); + this.interpreterProxy.pop(argCount); + return true; + }, + + primitiveFinishRenderer: function(argCount) { + if (argCount !== 1) return false; + if (!this.currentFromStack(0)) return false; + DEBUG > 1 && console.log("B3DAccel: primitiveFinishRenderer", currentRenderer); + OpenGL.glFinish(); + this.interpreterProxy.pop(argCount); + return true; + }, + + primitiveFlushRenderer: function(argCount) { + if (argCount !== 1) return false; + if (!this.currentFromStack(0)) return false; + DEBUG > 1 && console.log("B3DAccel: primitiveFlushRenderer", currentRenderer); + 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; + DEBUG > 1 && console.log("B3DAccel: UNIMPLEMENTED primitiveGetRendererSurfaceHandle", currentRenderer); + return false; + }, + + primitiveGetIntProperty: function(argCount) { + if (argCount !== 2) return false; + var property = this.interpreterProxy.stackIntegerValue(0); + if (!this.currentFromStack(1)) return false; + DEBUG > 1 && console.log("B3DAccel: primitiveGetIntProperty", property); + 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; + DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererSurfaceWidth", 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; + DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererSurfaceHeight", 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; + DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererSurfaceDepth", depth); + 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]); + } + DEBUG > 0 && console.log("B3DAccel: primitiveGetRendererColorMasks", masks); + 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); + DEBUG > 1 && console.log("B3DAccel: primitiveSetBufferRect", x, y, w, h); + 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); + DEBUG > 1 && console.log("B3DAccel: primitiveSetViewport", x, y, w, h); + 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; + DEBUG > 0 && console.log("B3DAccel: primitiveSetTransform", projectionMatrix, modelViewMatrix); + 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; + DEBUG > 1 && console.log("B3DAccel: UNIMPLEMENTED primitiveSetLights " + lightArray); + 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); + DEBUG > 0 && console.log("B3DAccel: primitiveSetMaterial", renderer, material); + 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"); + DEBUG > 1 && console.log("B3DAccel: UNIMPLEMENTED primitiveSetMaterial", material); + } + this.interpreterProxy.pop(argCount); + return true; + }, + + primitiveSwapRendererBuffers: function(argCount) { + if (argCount !== 1) return false; + if (!this.currentFromStack(0)) return false; + DEBUG > 1 && console.log("B3DAccel: primitiveSwapRendererBuffers", currentRenderer); + // let browser display the rendered frame + this.interpreterProxy.vm.breakNow(); + // tell the spinner we have been rendering + this.primHandler.display.lastTick = this.vm.lastTick; + + if (DEBUG_WAIT) debugger; // wait after each frame + this.interpreterProxy.pop(argCount); + return true; + }, + + primitiveTextureDepth: function(argCount) { + if (argCount !== 2) return false; + if (!this.currentFromStack(1)) return false; + var depth = 32; + DEBUG > 0 && console.log("B3DAccel: primitiveTextureDepth", depth); + return this.primHandler.popNandPushIfOK(argCount + 1, depth); + }, + + primitiveTextureGetColorMasks: function(argCount) { + if (argCount !== 3) return false; + if (!this.currentFromStack(2)) return false; + var texture = 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]); + } + DEBUG > 0 && console.log("B3DAccel: primitiveTextureGetColorMasks", texture, masks); + 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; + DEBUG > 0 && console.log("B3DAccel: primitiveTextureByteSex", byteSex); + 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; + DEBUG > 1 && console.log("B3DAccel: primitiveTextureUpload", texture, w, h, d, bits); + 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) { + DEBUG > 1 && console.log("B3DAccel: b3dxCompositeTexture", 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.0/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) { + DEBUG > 0 && console.log("B3DAccel: b3dxLoadLight", 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; + DEBUG > 0 && console.log("B3DAccel: fetchLightSource", index, light); + 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(); diff --git a/plugins/BitBltPlugin.js b/plugins/BitBltPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..1f67e565656639ad3549da18c4c74a0a5bbf978d --- /dev/null +++ b/plugins/BitBltPlugin.js @@ -0,0 +1,4722 @@ +/* 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() { +"use strict"; + +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 BinaryPoint = 14; +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 & ~ColorMapNewStyle; + + /* 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(); +} + + +/* Recover from the fast path specialised code saying Help-I-cant-cope */ + +function copyBitsFallback(op, flags) { + var done; + + // skipping ifdef ENABLE_FAST_BLT +} + + +/* Perform the actual copyBits operation using the fast path specialised code; fail some cases by falling back to normal code. + Assume: Surfaces have been locked and clipping was performed. */ + +function copyBitsFastPathSpecialised() { + // skipping ifdef ENABLE_FAST_BLT +} + + +/* 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]; + mergeFnwith; + + /* 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]; + mergeFnwith; + 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]; + mergeFnwith; + sourcePPW = DIV(32, sourceDepth); + sourcePixMask = maskTable[sourceDepth]; + destPixMask = maskTable[destDepth]; + mapperFlags = cmFlags & ~ColorMapNewStyle; + 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 (!((-2.147483648e9 <= 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 (!((-2.147483648e9 <= 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 halt() { + ; +} + +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, mapperFlags); + 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 & ~ColorMapNewStyle; + + /* 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]; + mergeFnwith; + 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 & ~ColorMapNewStyle; + 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 diff --git a/plugins/ConsolePlugin.js b/plugins/ConsolePlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..9fb53ca89ceeca6dcc3697e121802185e952e100 --- /dev/null +++ b/plugins/ConsolePlugin.js @@ -0,0 +1,48 @@ +/* + * This plugin simply adds "console.log" functionality. + * + * Add the following method to the Smalltalk image (to Object for example) to use it: + * primLog: messageString level: levelString + * + * "Log messageString to the console. The specified level should be one of: + * 'log' + * 'info' + * 'warn' + * 'error' + * " + * + * + * ^ self + */ + +function ConsolePlugin() { + "use strict"; + + return { + getModuleName: function() { return "ConsolePlugin"; }, + interpreterProxy: null, + + setInterpreter: function(anInterpreter) { + this.interpreterProxy = anInterpreter; + return true; + }, + + // Logging + "primitiveLog:level:": function(argCount) { + if (argCount !== 2) return false; + var message = this.interpreterProxy.stackValue(1).bytesAsString(); + var level = this.interpreterProxy.stackValue(0).bytesAsString(); + console[level](message); + this.interpreterProxy.pop(argCount); // Answer self + return true; + } + }; +} + +function registerConsolePlugin() { + if (typeof Squeak === "object" && Squeak.registerExternalModule) { + Squeak.registerExternalModule("ConsolePlugin", ConsolePlugin()); + } else self.setTimeout(registerConsolePlugin, 100); +}; + +registerConsolePlugin(); diff --git a/plugins/CroquetPlugin.js b/plugins/CroquetPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..97a49c94ccf1264a2f45ae8351239329b229d75b --- /dev/null +++ b/plugins/CroquetPlugin.js @@ -0,0 +1,34 @@ +function CroquetPlugin() { + "use strict"; + + 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(); diff --git a/plugins/ExamplePlugin.js b/plugins/ExamplePlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..ee9b90f8e7d961d21be23a99f3ea45965157b478 --- /dev/null +++ b/plugins/ExamplePlugin.js @@ -0,0 +1,47 @@ +/* + * This is a template for a SqueakJS plugin + * + * The file would need to be imported like the other plugins, + * see squeak.js + * + * The plugin could be used from the image like this: + * + * primXyz: arg1 with: arg2 + * + * ^self primitiveFailed + */ + +function ExamplePlugin() { + "use strict"; + + return { + getModuleName: function() { return 'ExamplePlugin'; }, + interpreterProxy: null, + primHandler: null, + + setInterpreter: function(anInterpreter) { + this.interpreterProxy = anInterpreter; + this.primHandler = this.interpreterProxy.vm.primHandler; + return true; + }, + + primitiveXYZ: function(argCount) { + if (argCount !== 2) return false; + var arg1 = this.interpreterProxy.stackObjectValue(1); + var arg2 = this.interpreterProxy.stackIntegerValue(0); + if (this.interpreterProxy.failed()) return false; + // see vm.interpreter.proxy.js for available proxy methods + var result = this.doSomething(arg1, arg2); + this.interpreterProxy.popthenPush(argCount + 1, result); + return true; + }, + }; +} + +function registerExamplePlugin() { + if (typeof Squeak === "object" && Squeak.registerExternalModule) { + Squeak.registerExternalModule('ExamplePlugin', ExamplePlugin()); + } else self.setTimeout(registerExamplePlugin, 100); +}; + +registerExamplePlugin(); diff --git a/plugins/FFTPlugin.js b/plugins/FFTPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..267754331f0fdd496949085381838e83bbcbcd78 --- /dev/null +++ b/plugins/FFTPlugin.js @@ -0,0 +1,285 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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 halt() { + ; +} + +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 diff --git a/plugins/FloatArrayPlugin.js b/plugins/FloatArrayPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..25a0a61b6156b8b5a88bd2f399704b08c1db4ca1 --- /dev/null +++ b/plugins/FloatArrayPlugin.js @@ -0,0 +1,582 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; +} + +function halt() { + ; +} + + +/* 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 diff --git a/plugins/GeniePlugin.js b/plugins/GeniePlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..532ffbd0cebe0dc75ab497a559144037ae597f07 --- /dev/null +++ b/plugins/GeniePlugin.js @@ -0,0 +1,335 @@ +/* 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() { +"use strict"; + +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); } +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); } + +/*** 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 halt() { + ; +} + +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 diff --git a/plugins/JPEGReaderPlugin.js b/plugins/JPEGReaderPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..8afaab0435d27c04fbe8e929cf2f1d9528bef528 --- /dev/null +++ b/plugins/JPEGReaderPlugin.js @@ -0,0 +1,927 @@ +/* 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() { +"use strict"; + +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 BlockWidthIndex = 5; +var BlueIndex = 2; +var ConstBits = 13; +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 Pass1Bits = 2; +var Pass1Div = 2048; +var Pass2Div = 262144; +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 cbSampleStream = 0; +var crBlocks = new Array(128); +var crComponent = new Array(11); +var crSampleStream = 0; +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); +var ySampleStream = 0; + + +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 halt() { + ; +} + +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; + + stInit(); + 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; + + stInit(); + 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 stInit() { + ; +} + +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 diff --git a/plugins/KedamaPlugin.js b/plugins/KedamaPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..2feb84567c850c3fad3c8144ad36f2105e41882b --- /dev/null +++ b/plugins/KedamaPlugin.js @@ -0,0 +1,2598 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; + + /* inline: true */; + 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); + } + } + return 0.0; +} + +function degreesToRadians(degrees) { + var deg; + var headingRadians; + var q; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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 halt() { + ; +} + +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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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 diff --git a/plugins/KedamaPlugin2.js b/plugins/KedamaPlugin2.js new file mode 100644 index 0000000000000000000000000000000000000000..3b9e49064bcc85d665e98b4c1df7a5c6b00291a7 --- /dev/null +++ b/plugins/KedamaPlugin2.js @@ -0,0 +1,4249 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; + + /* inline: true */; + 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); + } + } + return 0.0; +} + +function degreesToRadians(degrees) { + var deg; + var headingRadians; + var q; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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 halt() { + ; +} + +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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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; + + /* inline: true */; + 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 diff --git a/plugins/Klatt.js b/plugins/Klatt.js new file mode 100644 index 0000000000000000000000000000000000000000..bca16b7e22259c7deabeefd05612dcc96fc8a231 --- /dev/null +++ b/plugins/Klatt.js @@ -0,0 +1,987 @@ +/* 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() { +"use strict"; + +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 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 halt() { + ; +} + +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.0 / (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 diff --git a/plugins/LargeIntegers.js b/plugins/LargeIntegers.js new file mode 100644 index 0000000000000000000000000000000000000000..0a325a85f75dcfc2f143f5f5faefc33b25b4f7c9 --- /dev/null +++ b/plugins/LargeIntegers.js @@ -0,0 +1,2278 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; +} + + +/* Precondition: bytesOop is not anInteger and a bytes object. */ +/* Function #byteSizeOf: is used by the interpreter, be careful with name + clashes... */ + +function byteSizeOfBytes(bytesOop) { + return SIZEOF(bytesOop); +} + + +/* 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 halt() { + ; +} + +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; +} + +function msg(s) { + console.log(moduleName + ": " + s); +} + + +/* 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 = (true? interpreterProxy.trueObject() : interpreterProxy.falseObject()); + 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) { + var pointer; + + return ((pointer = 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 diff --git a/plugins/MIDIPlugin.js b/plugins/MIDIPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..6f94aa5c032d7b8ba652b54c62fef2d8f4eeff8d --- /dev/null +++ b/plugins/MIDIPlugin.js @@ -0,0 +1,412 @@ +"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. + */ + + +function MIDIPlugin() { + "use strict"; + + 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' ? ~1 : ~2; + } + 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(); diff --git a/plugins/Matrix2x3Plugin.js b/plugins/Matrix2x3Plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..d0981ffe886259890d0e3dd43d664d92e1aab045 --- /dev/null +++ b/plugins/Matrix2x3Plugin.js @@ -0,0 +1,466 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; +} + +function halt() { + ; +} + + +/* 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 diff --git a/plugins/MiscPrimitivePlugin.js b/plugins/MiscPrimitivePlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..017a6335bf2699d456c0979597e4265a20ecf1e3 --- /dev/null +++ b/plugins/MiscPrimitivePlugin.js @@ -0,0 +1,640 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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 coded so that plugins can be run from Squeak. */ + +function getInterpreter() { + return interpreterProxy; +} + + +/* 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 halt() { + ; +} + +function msg(s) { + console.log(moduleName + ": " + s); +} + + +/* 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 rcvr; + var string1; + var string2; + var order; + var c1; + var c2; + var i; + var len1; + var len2; + + rcvr = 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; + } + if (interpreterProxy.failed()) { + return null; + } + interpreterProxy.pop(argCount); +} + + +/* 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 rcvr; + var bm; + var ba; + var eqBytes; + var i; + var j; + var k; + var lowByte; + var m; + var size; + var word; + + rcvr = 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 rcvr; + var aByteArray; + var aSoundBuffer; + var i; + var n; + var s; + + rcvr = 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 rcvr; + 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; + + rcvr = 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 === 0) { + + /* skip */ + + null; + } + 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 rcvr; + var aString; + var inclusionMap; + var start; + var i; + var stringSize; + + rcvr = 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 rcvr; + var key; + var body; + var start; + var matchTable; + var index; + var startIndex; + + rcvr = 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 rcvr; + var anInteger; + var aString; + var start; + var pos; + var stringSize; + + rcvr = 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 rcvr; + var aByteArray; + var speciesHash; + var byteArraySize; + var hash; + var low; + var pos; + + rcvr = 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 rcvr; + var aString; + var start; + var stop; + var table; + var i; + + rcvr = 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 diff --git a/plugins/ScratchPlugin.js b/plugins/ScratchPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..f056ae607dbd5e3697a799ad24fb1009e4cb7c00 --- /dev/null +++ b/plugins/ScratchPlugin.js @@ -0,0 +1,1260 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; +} + +function halt() { + ; +} + + +/* 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 diff --git a/plugins/SocketPlugin.js b/plugins/SocketPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..bd308d3e83f1b7ec0a69b120522fbed526992f13 --- /dev/null +++ b/plugins/SocketPlugin.js @@ -0,0 +1,1020 @@ +/* + * 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() { + "use strict"; + + 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 finalBit = firstByte >>> 7; + 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(); diff --git a/plugins/SoundGenerationPlugin.js b/plugins/SoundGenerationPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..3f943cdcff0612bdef3dd4bc705db5efbce70fdf --- /dev/null +++ b/plugins/SoundGenerationPlugin.js @@ -0,0 +1,634 @@ +/* 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() { +"use strict"; + +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 IncrementFractionBits = 16; +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 coded so that plugins can be run from Squeak. */ + +function getInterpreter() { + return interpreterProxy; +} + + +/* 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 halt() { + ; +} + +function msg(s) { + console.log(moduleName + ": " + s); +} + +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 diff --git a/plugins/SpeechPlugin.js b/plugins/SpeechPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..0defb2767cc87a9b031bb82856138c5724c5a985 --- /dev/null +++ b/plugins/SpeechPlugin.js @@ -0,0 +1,133 @@ +function SpeechPlugin() { + 'use strict'; + + 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; + var now = Date.now(); + 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(); diff --git a/plugins/SqueakSSL.js b/plugins/SqueakSSL.js new file mode 100644 index 0000000000000000000000000000000000000000..75e970f47ff63ac77b6cb203f846e71feb995188 --- /dev/null +++ b/plugins/SqueakSSL.js @@ -0,0 +1,137 @@ +/* + * This is a fake SSL plugin, actual authentication and crypto + * is handled by browser and SocketPlugin + */ + +function SqueakSSL() { + "use strict"; + + 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); + var start = 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); + var start = 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(); diff --git a/plugins/StarSqueakPlugin.js b/plugins/StarSqueakPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..86b6c2d6aa4c10626366f485d8e077d6671e0ca5 --- /dev/null +++ b/plugins/StarSqueakPlugin.js @@ -0,0 +1,244 @@ +/* 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() { +"use strict"; + +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); } + +/*** 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; +} + +function halt() { + ; +} + + +/* 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 diff --git a/plugins/UnixOSProcessPlugin.js b/plugins/UnixOSProcessPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..ef0adbc30633576d69e495182bab7769df5edb14 --- /dev/null +++ b/plugins/UnixOSProcessPlugin.js @@ -0,0 +1,32 @@ +/* + * This plugin is only here for retrieving the current working directory (for finding .changes and .sources files) + */ + +function UnixOSProcessPlugin() { + "use strict"; + + return { + getModuleName: function() { return 'UnixOSProcessPlugin'; }, + interpreterProxy: null, + primHandler: null, + + setInterpreter: function(anInterpreter) { + this.interpreterProxy = anInterpreter; + this.primHandler = this.interpreterProxy.vm.primHandler; + return true; + }, + + primitiveGetCurrentWorkingDirectory: function(argCount) { + this.interpreterProxy.popthenPush(argCount + 1, this.primHandler.makeStString(require("process").cwd())); + return true; + }, + }; +} + +function registerUnixOSProcessPlugin() { + if (typeof Squeak === "object" && Squeak.registerExternalModule) { + Squeak.registerExternalModule('UnixOSProcessPlugin', UnixOSProcessPlugin()); + } else setTimeout(registerUnixOSProcessPlugin, 100); +}; + +registerUnixOSProcessPlugin(); diff --git a/plugins/ZipPlugin.js b/plugins/ZipPlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..fb620edf0b25033f95e0d0283dd26662ba498cd5 --- /dev/null +++ b/plugins/ZipPlugin.js @@ -0,0 +1,1199 @@ +/* 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() { +"use strict"; + +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 DeflateHashMask = 32767; +var DeflateHashShift = 5; +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 zipBlockStart = 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; +} + +function halt() { + ; +} + + +/* 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 diff --git a/run/index.html b/run/index.html new file mode 100644 index 0000000000000000000000000000000000000000..dee087e363dde7cc068c99c62e1e42bc3f4091e6 --- /dev/null +++ b/run/index.html @@ -0,0 +1,309 @@ + + + + +SqueakJS + + + + + + + +
+

SqueakJS

+ + SqueakJS is an HTML5 runtime engine for Squeak Smalltalk written in pure JavaScript by Vanessa Freudenberg. + See the SqueakJS Project page for more information. + +

Run Squeak images from your local machine

+ Use drag-and-drop to run a Squeak image from your machine: drop the image (perhaps together with a changes and sources file) into this page. +
+ All images and related files are stored persistently in a database inside your browser. + The box below shows the files that are currently in your database. Inside Squeak, you can use a FileList + to manage them. +
Drop Squeak images and other files here, or +
+
+
+ Clicking on a name in the box will export that file to your browser's downloads folder. + +

Run Squeak images from the internet

+ Construct a URL linking to this page and pass the image and options. + Beware that the server needs to allow script access via CORS. +
Here are a few examples: +
    +
  • Squeak 1.13 (1996) link
  • +
  • Squeak 2.8 (2000) link
  • +
  • Squeak 3.8 (2006) link
  • +
  • Squeak 3.9 (2008) link
  • +
  • Pharo 1.0 (2010) link
  • +
  • Squeak 4.5 (2014) link
  • +
  • Squeak 5.0 (2015) link
  • +
  • SpeechPlugin Demo (2016) link
  • +
  • Squeak 6.0 64 bit (2022) link
  • +
  • Cuis 6.2 (2023) link
  • +
  • Squeak Trunk 6.1α (2024) link
  • +
  • Squeak Trunk 6.1α (hi-res: save image after first load for faster startup) link
  • +
+ On the first run these will be stored locally. Subsequent starts are much faster since there is no download. + +

Run SqueakJS apps

+ SqueakJS can be used to run Squeak apps. Here are some examples. + Note how they differ when you resize the browser window—Etoys is scaled, + whereas Scratch is resized. + The apps are configured to use template files (e.g. example projects and artwork) + that are loaded from a server on demand. + + On some mobile devices you can save these apps to the home screen, and run them like a real app. + +

Use SqueakJS on your own website

+ Instead of passing options by URL to this page, you can + download SqueakJS + from GitHub and use it on your own website like this: +
+        <html>
+            <head>
+                <script src="squeak.js"></script>
+                <script>
+                    window.onload = function() {
+                        SqueakJS.runSqueak("my.image", sqCanvas, { /*put options here*/ });
+                    }
+                </script>
+            </head>
+            <body>
+                <canvas id="sqCanvas"></canvas>
+            </body>
+        </html>
+        
+ Options include display resolution, template file URLs, etc. + For example usage take a look at the demo pages included in the GitHub repo: + Etoys + (source) or + Scratch + (source) + +

Modify SqueakJS

+ I am developing SqueakJS using “Lively”, a browser-based development environment for JavaScript inspired by Smalltalk. + Instead of having to constantly reload the page after every source code change, + I am executing Squeak in my Lively SqueakJS Debugger + and can change its code while it is running. + That's why the SqueakJS source code has a somewhat unusual layout, it fits the Lively way of developing. + You can still use a plain text editor if you feel that's simpler. + +

Contribute to SqueakJS

+ SqueakJS is free software (MIT license). + You're very welcome to discuss, + report bugs, + and contribute code. + +

Have fun! — Vanessa Freudenberg

+
+ +
+ + diff --git a/run/squeakjs.css b/run/squeakjs.css new file mode 100644 index 0000000000000000000000000000000000000000..80b5129824de2ffd062e4d65b9fcf54f19ef4ac8 --- /dev/null +++ b/run/squeakjs.css @@ -0,0 +1,59 @@ +body { + background-color: #eae6d1; + font-family: sans-serif; +} +canvas { + position: fixed; + background: #000; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; +} +canvas.pixelated { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} +h1 { + color: #F90; +} +#logo { + border-radius: 20px; + box-shadow: 0 0 5px 5px #F90; + margin: 0 20px 0 5px; +} +div#sqSpinner { + position: fixed; + margin: auto; top: 0; left: 0; bottom: 0; right: 0; + width: 100px; + height: 100px; + border-radius: 50px; + background: rgba(0, 0, 0, 0.3); + box-shadow: 0 0 5px 5px #F90; + display: none; + transform: rotate(30deg); +} +div#sqSpinner > div { + position: absolute; + top: 45px; + left: 5px; + width: 90px; + height: 10px; + border-radius: 5px; + box-shadow: 0 0 5px 5px #F90; +} +#drop { + color: #9b988a; + border: 6px dashed #9b988a; + min-height: 50px; + max-width: 600px; + margin: 10px 0; + padding: 10px; +} +.filelist > ul { + max-height: 75px; + overflow: scroll; + margin: 10px 0; + padding: 10px; +} diff --git a/run/squeakjs.png b/run/squeakjs.png new file mode 100644 index 0000000000000000000000000000000000000000..08a21d82e59ecd3ab4bc9ab927b10defd6a89fad Binary files /dev/null and b/run/squeakjs.png differ diff --git a/scratch/index.html b/scratch/index.html new file mode 100644 index 0000000000000000000000000000000000000000..eb674f43d0ff08306a8011ad457386b0378b1a0c --- /dev/null +++ b/scratch/index.html @@ -0,0 +1,45 @@ + + + + +Scratch 1.4 + + + + + + + + + + + + + + + + + +
+ + diff --git a/scratch/scratch.css b/scratch/scratch.css new file mode 100644 index 0000000000000000000000000000000000000000..1682053082a86996030b2d2e6b482de243bf2521 --- /dev/null +++ b/scratch/scratch.css @@ -0,0 +1,38 @@ +body { + background-color: #333; + font-family: sans-serif; +} +canvas { + position: fixed; + background: #000; + cursor: default; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; +} +canvas.pixelated { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; +} +div#sqSpinner { + position: fixed; + margin: auto; top: 0; left: 0; bottom: 0; right: 0; + width: 100px; + height: 100px; + border-radius: 50px; + background: rgba(0, 0, 0, 0.3); + box-shadow: 0 0 5px 5px #F90; + display: none; + transform: rotate(30deg); +} +div#sqSpinner > div { + position: absolute; + top: 45px; + left: 5px; + width: 90px; + height: 10px; + border-radius: 5px; + box-shadow: 0 0 5px 5px #F90; +} diff --git a/scratch/scratch.js b/scratch/scratch.js new file mode 100644 index 0000000000000000000000000000000000000000..3249c2420e8e7a9481274fd806d4e1c8edd06172 --- /dev/null +++ b/scratch/scratch.js @@ -0,0 +1,38 @@ +/* + * 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. + */ + +var fullscreen = navigator.standalone || + window.matchMedia('(max-device-width: 800px) and (max-device-height: 800px)').matches; + +window.onload = function() { + var url = "https://freudenbergs.de/vanessa/squeakjs/scratch/Scratch.image"; + SqueakJS.runSqueak(url, sqCanvas, { + appName: "Scratch", + spinner: sqSpinner, + root: "/Scratch", + templates: ["Projects", "Media", "Help", "locale"], + }); +}; + +if (addToHomescreen.isStandalone) addToHomescreen({ + appID: 'squeakjs.scratch.add2home', +}); diff --git a/scratch/scratch.png b/scratch/scratch.png new file mode 100644 index 0000000000000000000000000000000000000000..3e54066d8f077d13522809ee0fca2aef192eec96 Binary files /dev/null and b/scratch/scratch.png differ diff --git a/squeak.js b/squeak.js new file mode 100644 index 0000000000000000000000000000000000000000..fa586fc6367434efa5c2b4c25afb8a9bd8d93591 --- /dev/null +++ b/squeak.js @@ -0,0 +1,1468 @@ +/* + * 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. + */ + +"use strict"; + +////////////////////////////////////////////////////////////////////////////// +// load vm, plugins, and other libraries +////////////////////////////////////////////////////////////////////////////// + +import "./globals.js"; +import "./vm.js"; +import "./vm.object.js"; +import "./vm.object.spur.js"; +import "./vm.image.js"; +import "./vm.interpreter.js"; +import "./vm.interpreter.proxy.js"; +import "./vm.instruction.stream.js"; +import "./vm.instruction.stream.sista.js"; +import "./vm.instruction.printer.js"; +import "./vm.primitives.js"; +import "./jit.js"; +import "./vm.audio.browser.js"; +import "./vm.display.js"; +import "./vm.display.browser.js"; +import "./vm.files.browser.js"; +import "./vm.input.js"; +import "./vm.input.browser.js"; +import "./vm.plugins.js"; +import "./vm.plugins.ffi.js"; +import "./vm.plugins.javascript.js"; +import "./vm.plugins.obsolete.js"; +import "./vm.plugins.drop.browser.js"; +import "./vm.plugins.file.browser.js"; +import "./vm.plugins.jpeg2.browser.js"; +import "./vm.plugins.scratch.browser.js"; +import "./vm.plugins.sound.browser.js"; +import "./plugins/ADPCMCodecPlugin.js"; +import "./plugins/B2DPlugin.js"; +import "./plugins/B3DAcceleratorPlugin.js"; +import "./plugins/BitBltPlugin.js"; +import "./plugins/CroquetPlugin.js"; +import "./plugins/FFTPlugin.js"; +import "./plugins/FloatArrayPlugin.js"; +import "./plugins/GeniePlugin.js"; +import "./plugins/JPEGReaderPlugin.js"; +import "./plugins/KedamaPlugin.js"; +import "./plugins/KedamaPlugin2.js"; +import "./plugins/Klatt.js"; +import "./plugins/LargeIntegers.js"; +import "./plugins/Matrix2x3Plugin.js"; +import "./plugins/MiscPrimitivePlugin.js"; +import "./plugins/MIDIPlugin.js"; +import "./plugins/ScratchPlugin.js"; +import "./plugins/SocketPlugin.js"; +import "./plugins/SpeechPlugin.js"; +import "./plugins/SqueakSSL.js"; +import "./plugins/SoundGenerationPlugin.js"; +import "./plugins/StarSqueakPlugin.js"; +import "./plugins/ZipPlugin.js"; +import "./ffi/libc.js"; +import "./ffi/opengl.js"; +import "./lib/lz-string.js"; +import "./lib/jszip.js"; +import "./lib/FileSaver.js"; +import "./lib/sha1.js"; + +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 dent(n, l, t, u) { return n < l ? n + t - l : n > u ? n + t - u : t; } + 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(evt); + 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) {/* pushed after other files */} + 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(); + } + }); +} diff --git a/squeak_headless.js b/squeak_headless.js new file mode 100644 index 0000000000000000000000000000000000000000..b7945c8e20d379edb5660119b4cbe6e416f41b82 --- /dev/null +++ b/squeak_headless.js @@ -0,0 +1,112 @@ +// This is a minimal headless SqueakJS VM +// +// The headless SqueakJS VM can be used inside a browser. +// The VM can run in the main (UI) thread or in a WebWorker thread. Some browsers +// do not support ES6 modules yet for WebWorker scripts. Please use or create +// a Javascript bundle of the VM in that case. +// +// A special ConsolePlugin is loaded which allows sending messages to the console. +// Add the following method to the Smalltalk image (to Object for example): +// +// primLog: messageString level: levelString +// +// "Log messageString to the console. The specified level should be one of: +// 'log' +// 'info' +// 'warn' +// 'error' +// " +// +// +// ^ self +// +// +// There is no default file support loaded. If needed add the relevant modules/plugins like: +// import "./vm.files.browser.js"; +// import "./vm.plugins.file.browser.js"; + +// Load VM and the internal plugins +import "./globals.js"; +import "./vm.js"; +import "./vm.object.js"; +import "./vm.object.spur.js"; +import "./vm.image.js"; +import "./vm.interpreter.js"; +import "./vm.interpreter.proxy.js"; +import "./vm.instruction.stream.js"; +import "./vm.instruction.stream.sista.js"; +import "./vm.instruction.printer.js"; +import "./vm.primitives.js"; +import "./jit.js"; +import "./vm.display.js"; +import "./vm.display.headless.js"; // use headless display to prevent image crashing/becoming unresponsive +import "./vm.input.js"; +import "./vm.input.headless.js"; // use headless input to prevent image crashing/becoming unresponsive +import "./vm.plugins.js"; +import "./plugins/ConsolePlugin.js"; + +// Run image by starting interpreter on it +function runImage(imageData, imageName, options) { + + // Create Squeak image from raw data + var image = new Squeak.Image(imageName.replace(/\.image$/i, "")); + image.readFromBuffer(imageData, function startRunning() { + + // Create fake display and create interpreter + var display = { vmOptions: [ "-vm-display-null", "-nodisplay" ] }; + var vm = new Squeak.Interpreter(image, display); + function run() { + try { + vm.interpret(50, function runAgain(ms) { + // Ignore display.quitFlag when requested. + // Some Smalltalk images quit when no display is found. + if (options.ignoreQuit || !display.quitFlag) { + setTimeout(run, ms === "sleep" ? 10 : ms); + } + }); + } catch(e) { + console.error("Failure during Squeak run: ", e); + } + } + + // Start the interpreter + run(); + }); +} + +function fetchImageAndRun(imageName, options) { + fetch(imageName, { + method: "GET", + mode: "cors", + cache: "no-store" + }).then(function(response) { + if (!response.ok) { + throw new Error("Response not OK: " + response.status); + } + return response.arrayBuffer(); + }).then(function(imageData) { + runImage(imageData, imageName, options); + }).catch(function(error) { + console.error("Failed to retrieve image", error); + }); +} + +// Extend Squeak with settings and options to fetch and run image +Object.extend(Squeak, { + vmPath: "/", + platformSubtype: "Browser", + osVersion: navigator.userAgent, // might want to parse + windowSystem: "headless", + fetchImageAndRun: fetchImageAndRun, +}); + + +// Retrieve image name from URL +var searchParams = (new URL(self.location)).searchParams; +var imageName = searchParams.get("imageName"); +if (imageName) { + var options = { + ignoreQuit: searchParams.get("ignoreQuit") !== null + }; + fetchImageAndRun(imageName, options); +} diff --git a/squeak_lively.js b/squeak_lively.js new file mode 100644 index 0000000000000000000000000000000000000000..d50109f5f8923ec29e360a89b1d6c133fd963ec1 --- /dev/null +++ b/squeak_lively.js @@ -0,0 +1,102 @@ +/* + * 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. + */ + +"use strict"; + +////////////////////////////////////////////////////////////////////////////// +// This loads SqueakJS as a Lively module +// Refer to lively/README.md for running the visual VM debugger +////////////////////////////////////////////////////////////////////////////// + +module('users.SqueakJS.squeak_lively').requires().toRun(function() { + Object.extend(users.SqueakJS.squeak_lively, { + loadSqueak: function(thenDo) { + // bail out if loaded already + if (Global.Squeak) return thenDo && thenDo(); + // otherwise, load all + var files = [ + "globals.js", + "vm.js", + "vm.object.js", + "vm.object.spur.js", + "vm.image.js", + "vm.interpreter.js", + "vm.interpreter.proxy.js", + "vm.instruction.stream.js", + "vm.instruction.stream.sista.js", + "vm.instruction.printer.js", + "vm.primitives.js", + "jit.js", + "vm.audio.browser.js", + "vm.display.js", + "vm.display.browser.js", + "vm.files.browser.js", + "vm.input.js", + "vm.input.browser.js", + "vm.plugins.js", + "vm.plugins.ffi.js", + "vm.plugins.javascript.js", + "vm.plugins.obsolete.js", + "vm.plugins.drop.browser.js", + "vm.plugins.file.browser.js", + "vm.plugins.jpeg2.browser.js", + "vm.plugins.scratch.browser.js", + "vm.plugins.sound.browser.js", + "plugins/ADPCMCodecPlugin.js", + "plugins/B2DPlugin.js", + "plugins/BitBltPlugin.js", + "plugins/CroquetPlugin.js", + "plugins/FFTPlugin.js", + "plugins/FloatArrayPlugin.js", + "plugins/GeniePlugin.js", + "plugins/JPEGReaderPlugin.js", + "plugins/KedamaPlugin.js", + "plugins/KedamaPlugin2.js", + "plugins/Klatt.js", + "plugins/LargeIntegers.js", + "plugins/Matrix2x3Plugin.js", + "plugins/MiscPrimitivePlugin.js", + "plugins/ScratchPlugin.js", + "plugins/SocketPlugin.js", + "plugins/SpeechPlugin.js", + "plugins/SqueakSSL.js", + "plugins/SoundGenerationPlugin.js", + "plugins/StarSqueakPlugin.js", + "plugins/ZipPlugin.js", + "lib/lz-string.js", + "lib/jszip.js", + "lib/FileSaver.js", + "lib/sha1.js", + ]; + var base = URL.root.withFilename("users/SqueakJS/"); + JSLoader.resolveAndLoadAll(base, files, function() { + Object.extend(Squeak, { + vmPath: "/", + platformSubtype: "Browser", + osVersion: navigator.userAgent, // might want to parse + windowSystem: "HTML", + }); + if (thenDo) thenDo(); + }); + }, + }); +}); diff --git a/squeak_node.js b/squeak_node.js new file mode 100644 index 0000000000000000000000000000000000000000..505373c2c1467ba7efa2be901ee1a948756be28a --- /dev/null +++ b/squeak_node.js @@ -0,0 +1,155 @@ +// This is a SqueakJS VM for use with node +// +// To start an image use: node squeak_node.js [-ignoreQuit] +// +// To start the minimal headless image present in the folder "headless" use: +// node squeak_node.js headless/headless.image +// +// Option "-ignoreQuit" is present to prevent some images from quiting when +// no GUI (support) is found. The image will not be able to quit from within +// the image and needs to be quit by stopping the process itself. +// In some situations adding "-ignoreQuit" can make some minimal images crash +// when no more processes are running (ie when no bytecode is left to execute). +// +// A special ConsolePlugin is loaded which allows sending messages to the console. +// Add the following method to the Smalltalk image (to Object for example): +// +// primLog: messageString level: levelString +// +// "Log messageString to the console. The specified level should be one of: +// 'log' +// 'info' +// 'warn' +// 'error' +// " +// +// +// ^ self +// +// The VM will try to load plugins when named primitives are used for the first time. +// These plugins do not need to be imported up front. + +var os = require("os"); +var fs = require("fs"); +var process = require("process"); +var path = require("path"); + +// Retrieve image name and parameters from command line +var processArgs = process.argv.slice(2); +var ignoreQuit = processArgs[0] === "-ignoreQuit"; +if (ignoreQuit) { + processArgs = processArgs.slice(1); +} +var fullName = processArgs[0]; +if (!fullName) { + console.error("No image name specified."); + console.log("Usage (simplified): " + path.basename(process.argv0) + path.basename(process.argv[1]) + " [-ignoreQuit] "); + process.exit(1); +} +var root = path.dirname(fullName) + path.sep; +var imageName = path.basename(fullName, ".image"); + +// Create global 'self' resembling the global scope in the browser DOM +Object.assign(global, { + + // Add browser element 'self' for platform consistency + self: new Proxy({}, { + get: function(obj, prop) { + return global[prop]; + }, + set: function(obj, prop, value) { + global[prop] = value; + return true; + } + }) +}); + +// Extend the new global scope with a few browser/DOM classes and methods +Object.assign(self, { + localStorage: {}, + WebSocket: typeof WebSocket === "undefined" ? require("./lib_node/WebSocket") : WebSocket, + sha1: require("./lib/sha1"), + btoa: function(string) { + return Buffer.from(string, 'ascii').toString('base64'); + }, + atob: function(string) { + return Buffer.from(string, 'base64').toString('ascii'); + } +}); + +// Load VM and the internal plugins +require("./globals.js"); +require("./vm.js"); +require("./vm.object.js"); +require("./vm.object.spur.js"); +require("./vm.image.js"); +require("./vm.interpreter.js"); +require("./vm.interpreter.proxy.js"); +require("./vm.instruction.stream.js"); +require("./vm.instruction.stream.sista.js"); +require("./vm.instruction.printer.js"); +require("./vm.primitives.js"); +require("./jit.js"); +require("./vm.display.js"); +require("./vm.display.headless.js"); // use headless display to prevent image crashing/becoming unresponsive +require("./vm.input.js"); +require("./vm.input.headless.js"); // use headless input to prevent image crashing/becoming unresponsive +require("./vm.plugins.js"); +require("./vm.plugins.file.node"); + +// Set the appropriate VM and platform values +Object.extend(Squeak, { + vmPath: process.cwd() + path.sep, + platformSubtype: "Node.js", + osVersion: process.version + " " + os.platform() + " " + os.release() + " " + os.arch(), + windowSystem: "none", +}); + +// Extend the Squeak primitives with ability to load modules dynamically +Object.extend(Squeak.Primitives.prototype, { + loadModuleDynamically: function(modName) { + try { + require("./plugins/" + modName); + + // Modules register themselves, should be available now + return Squeak.externalModules[modName]; + } catch(e) { + console.error("Plugin " + modName + " could not be loaded"); + } + return undefined; + } +}); + +// Read raw image +fs.readFile(root + imageName + ".image", function(error, data) { + if (error) { + console.error("Failed to read image", error); + return; + } + + // Create Squeak image from raw data + var image = new Squeak.Image(root + imageName); + image.readFromBuffer(data.buffer, function startRunning() { + + // Create fake display and create interpreter + var display = { vmOptions: [ "-vm-display-null", "-nodisplay" ] }; + var vm = new Squeak.Interpreter(image, display); + function run() { + try { + vm.interpret(200, function runAgain(ms) { + + // Ignore display.quitFlag when requested. + // Some Smalltalk images quit when no display is found. + if (ignoreQuit || !display.quitFlag) { + setTimeout(run, ms === "sleep" ? 10 : ms); + } + }); + } catch(e) { + console.error("Failure during Squeak run: ", e); + } + } + + // Start the interpreter + run(); + }); +}); diff --git a/standalone/Makefile b/standalone/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..691e558afb75d4bdf231a1cdbc6ada3f187d9de9 --- /dev/null +++ b/standalone/Makefile @@ -0,0 +1,10 @@ +default: + cp ../squeak.js . + cp ../run/squeakjs.css . + mkdir lib/ + cp ../lib/*.js lib/ + +clean: + rm *.css + rm -rf lib/ + rm squeak.js diff --git a/standalone/nw.html b/standalone/nw.html new file mode 100644 index 0000000000000000000000000000000000000000..ccce5459ef75ed1b9630d2d679c8165d55904c95 --- /dev/null +++ b/standalone/nw.html @@ -0,0 +1,41 @@ + + + +SqueakJS + + + + + + + + +
+ + diff --git a/standalone/nw.js b/standalone/nw.js new file mode 100644 index 0000000000000000000000000000000000000000..135b814ceef15f5b49f777a131015838e4e0bd37 --- /dev/null +++ b/standalone/nw.js @@ -0,0 +1,74 @@ +function patchSqueakForNode(root) { + var fs = require("fs"); + var Squeak = window.Squeak; + Object.extend(Squeak, { + fileGet: (filepath, thenDo, errorDo) => { + fs.readFile(root + filepath, (err, data) => { + if (err) { + errorDo(err); + } else { + thenDo((data instanceof ArrayBuffer) ? data : data.buffer); + } + }); + }, + filePut: (filepath, contents, optSuccess) => { + var path = Squeak.splitFilePath(filepath); if (!path.basename) return null; + var now = Squeak.totalSeconds(); + var entry = [path.basename, now, 0, false, contents.byteLength || contents.length || 0]; + fs.writeFileSync(root + filepath, contents); + if (optSuccess) optSuccess(); + return entry; + }, + fileDelete: (filepath, entryOnly) => { + fs.unlinkSync(root + filepath); + }, + fileRename: (from, to) => { + fs.renameSync(root + from, root + to); + }, + fileExists: (filepath) => { + try { + fs.accessSync(root + filepath); + return true; + } catch(e) { + return false; + } + }, + dirCreate: (dirpath, withParents) => { + var path = Squeak.splitFilePath(dirpath); if (!path.basename) return false; + if (withParents && !Squeak.fileExists(path.dirname)) { + Squeak.dirCreate(path.dirname, true); + } + try { + fs.mkdirSync(root + dirpath); + return true; + } catch(e) { + return false; + } + }, + dirDelete: (dirpath) => { + try { + fs.rmdirSync(root + dirpath); + return true; + } catch(e) { + return false; + } + }, + dirList: (dirpath, includeTemplates) => { + try { + var dir = {}; + var files = fs.readdirSync(root + dirpath); + files.forEach(f => { + var stats = fs.statSync(root + dirpath + f); + dir[f] = [f, stats.ctime, stats.mtime, stats.isDirectory(), stats.size]; + }); + return dir; + } catch(e) { + return null; + } + }, + primitiveQuit: (argCount) => { + Squeak.flushAllFiles(); + nw.App.quit(); + }, + }); +} diff --git a/standalone/package.json b/standalone/package.json new file mode 100644 index 0000000000000000000000000000000000000000..435d7418490a779fb85b7e60239ac92459cbce91 --- /dev/null +++ b/standalone/package.json @@ -0,0 +1,4 @@ +{ + "name": "SqueakJS", + "main": "nw.html" +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..550aefcbebd5b25038985ed06e34cddf85ff086d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,5 @@ +node_modules +*.image +*.changes +*.sources +*.tgz diff --git a/tests/karma.conf.js b/tests/karma.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..ec172a77052ae11dca0ae9f9c9fea7946c661361 --- /dev/null +++ b/tests/karma.conf.js @@ -0,0 +1,88 @@ +// Karma configuration +// Generated on Tue Nov 01 2016 15:22:16 GMT+0100 (CET) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '..', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + //frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'squeak.js', + 'vm.js', + 'jit.js', + 'plugins/*.js', + 'lib/*.js', + 'tests/tests.js', + {pattern: 'tests/resources/*', included: false} + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + customLaunchers: { + ChromeCanary_Travis_CI: { + base: 'ChromeCanary', + flags: ['--no-sandbox'] + }, + }, + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + + browserDisconnectTimeout: 60000, + browserNoActivityTimeout: 60000 + }); + if (process.env.TRAVIS) { + config.browsers = ['ChromeCanary_Travis_CI']; + } +}; diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000000000000000000000000000000000000..6758cdfa2604b77b606fc8ba2095bd80787e8bda --- /dev/null +++ b/tests/package.json @@ -0,0 +1,20 @@ +{ + "author": "Vanessa Freudenberg ", + "name": "squeakjs-tests", + "description": "SqueakJS test runner", + "version": "0.1.0", + "homepage": "https://squeak.js.org/", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/codefrau/SqueakJS" + }, + "dependencies": {}, + "devDependencies": { + "karma": "^0.13.1", + "karma-chrome-launcher": "^2.0.0" + }, + "scripts": { + "test": "karma start karma.conf.js" + } +} diff --git a/tests/resources/tests.st b/tests/resources/tests.st new file mode 100644 index 0000000000000000000000000000000000000000..87ee585c67200d6d6e7ac799c92b6f295ed8d951 --- /dev/null +++ b/tests/resources/tests.st @@ -0,0 +1,3 @@ +FileStream stdout converter: UTF8TextConverter new. +SqueakJSTesting test: 'tests.ston'. +Smalltalk quitPrimitive \ No newline at end of file diff --git a/tests/resources/tests.ston b/tests/resources/tests.ston new file mode 100644 index 0000000000000000000000000000000000000000..52651bffac4fc5bfd540410fc8ad1e8dea33d4a3 --- /dev/null +++ b/tests/resources/tests.ston @@ -0,0 +1,17 @@ +SmalltalkCISpec { + #loading : [], + #testing : { + #include : { + #classes : [ + #BasicBehaviorClassMetaclassTest, + #CollectionTest, #ArrayTest, + #LinkedListTest, #OrderedCollectionTest, + #OrderedDictionaryTest, #SequenceableCollectionTest, + #SortedCollectionTest, + #StackTest, + #CharacterTest, #SymbolTest, + #BagTest, #SetTest + ] + } + } +} diff --git a/tests/tests.js b/tests/tests.js new file mode 100644 index 0000000000000000000000000000000000000000..fd72c7f166c425121b95cdaf787bdba4a76d73e3 --- /dev/null +++ b/tests/tests.js @@ -0,0 +1,17 @@ +__karma__.start = function() { + var canvas = document.createElement('canvas'); + canvas.width = 800; + canvas.height = 600; + canvas.style.backgroundColor = 'black'; + document.body.appendChild(canvas); + SqueakJS.runSqueak(null, canvas, { + appName: 'SqueakJS Tests', + url: 'base/tests/resources/', + files: ['test.image', 'test.changes', 'SqueakV50.sources', 'tests.ston'], + document: 'tests.st', + forceDownload: true, + onQuit: function() { + __karma__.complete(); + }, + }); +}; \ No newline at end of file diff --git a/utils/.filetree b/utils/.filetree new file mode 100644 index 0000000000000000000000000000000000000000..c6704e5144c5ceca5adcc258df5573a7b81175f3 --- /dev/null +++ b/utils/.filetree @@ -0,0 +1,2 @@ +{"packageExtension" : ".package", +"propertyFileExtension" : ".json" } \ No newline at end of file diff --git a/utils/BaselineOfSqueakJS.package/.filetree b/utils/BaselineOfSqueakJS.package/.filetree new file mode 100644 index 0000000000000000000000000000000000000000..8998102c2737008bad7a1c777e927676eb4c658e --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/.filetree @@ -0,0 +1,4 @@ +{ + "noMethodMetaData" : true, + "separateMethodMetaAndSource" : false, + "useCypressPropertiesFile" : true } diff --git a/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/README.md b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/instance/baseline..st b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/instance/baseline..st new file mode 100644 index 0000000000000000000000000000000000000000..b85ba27dbbd878511f1a0dc86218bbc201e52120 --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/instance/baseline..st @@ -0,0 +1,18 @@ +baseline +baseline: spec + + spec + for: #'common' + do: [ + spec + baseline: 'SmalltalkCI' with: + [ spec + loads: #('default'); + repository: 'github://hpi-swa/smalltalkCI:81b47d0982794601c3a085f98786c6384873a4e7/repository' ]; + package: 'JSBridge-Core'; + package: 'SqueakJS-Testing' with: [spec requires: #('SmalltalkCI')]; + package: 'VMMakerJS'; + yourself. + spec + group: 'tests' with: #('SqueakJS-Testing') ]; + yourself \ No newline at end of file diff --git a/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/methodProperties.json b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..1c2ed105a5e61640e4082391b150a1e2989beee5 --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "baseline:" : "ct 1/24/2024 17:41" } } diff --git a/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/properties.json b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..9c9ff367b3eff1c56207bd46bcc187261497df20 --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/BaselineOfSqueakJS.class/properties.json @@ -0,0 +1,14 @@ +{ + "category" : "BaselineOfSqueakJS", + "classinstvars" : [ + ], + "classvars" : [ + ], + "commentStamp" : "", + "instvars" : [ + ], + "name" : "BaselineOfSqueakJS", + "pools" : [ + ], + "super" : "BaselineOf", + "type" : "normal" } diff --git a/utils/BaselineOfSqueakJS.package/monticello.meta/categories.st b/utils/BaselineOfSqueakJS.package/monticello.meta/categories.st new file mode 100644 index 0000000000000000000000000000000000000000..57b89cecbfa626ab3fafb3287ca3234ee33c489b --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/monticello.meta/categories.st @@ -0,0 +1 @@ +SystemOrganization addCategory: #BaselineOfSqueakJS! diff --git a/utils/BaselineOfSqueakJS.package/monticello.meta/initializers.st b/utils/BaselineOfSqueakJS.package/monticello.meta/initializers.st new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/utils/BaselineOfSqueakJS.package/monticello.meta/package b/utils/BaselineOfSqueakJS.package/monticello.meta/package new file mode 100644 index 0000000000000000000000000000000000000000..44d8fac7ddb7a8b0a90a061b51840630e6fef11a --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/monticello.meta/package @@ -0,0 +1 @@ +(name 'BaselineOfSqueakJS') \ No newline at end of file diff --git a/utils/BaselineOfSqueakJS.package/monticello.meta/version b/utils/BaselineOfSqueakJS.package/monticello.meta/version new file mode 100644 index 0000000000000000000000000000000000000000..55b503b16c82e326077feb82307ce0c31a70b71b --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/monticello.meta/version @@ -0,0 +1 @@ +(name 'BaselineOfSqueakJS-ct.2' message 'Add all packages to baseline, rename baseline' id 'ddc88995-1718-c645-bc4a-e7cf478e1f35' date '24 January 2024' time '5:42:28.82634 pm' author 'ct' ancestors ((name 'BaselineOfSqueakJSTesting-fn.1' message 'Initial commit' id '5297d622-ab30-4b4e-a541-691b256b87ae' date '4 November 2016' time '3:39:49.022214 pm' author 'fn' ancestors () stepChildren ())) stepChildren ()) \ No newline at end of file diff --git a/utils/BaselineOfSqueakJS.package/properties.json b/utils/BaselineOfSqueakJS.package/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..f037444a7caa50a71595b61ace1fa91882512e9f --- /dev/null +++ b/utils/BaselineOfSqueakJS.package/properties.json @@ -0,0 +1,2 @@ +{ + } diff --git a/utils/JSBridge-Core.package/.filetree b/utils/JSBridge-Core.package/.filetree new file mode 100644 index 0000000000000000000000000000000000000000..8998102c2737008bad7a1c777e927676eb4c658e --- /dev/null +++ b/utils/JSBridge-Core.package/.filetree @@ -0,0 +1,4 @@ +{ + "noMethodMetaData" : true, + "separateMethodMetaAndSource" : false, + "useCypressPropertiesFile" : true } diff --git a/utils/JSBridge-Core.package/BlockClosure.extension/instance/asJSArgument.st b/utils/JSBridge-Core.package/BlockClosure.extension/instance/asJSArgument.st new file mode 100644 index 0000000000000000000000000000000000000000..366499156bd0095f85b8877cbf0e5687096b3c7b --- /dev/null +++ b/utils/JSBridge-Core.package/BlockClosure.extension/instance/asJSArgument.st @@ -0,0 +1,4 @@ +*jsbridge-core +asJSArgument + "converted to JS function by plugin" + ^ self diff --git a/utils/JSBridge-Core.package/BlockClosure.extension/methodProperties.json b/utils/JSBridge-Core.package/BlockClosure.extension/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..79e395568c59581d5915d153f532fc1ec597fc77 --- /dev/null +++ b/utils/JSBridge-Core.package/BlockClosure.extension/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "asJSArgument" : "bf 11/26/2014 18:20" } } diff --git a/utils/JSBridge-Core.package/BlockClosure.extension/properties.json b/utils/JSBridge-Core.package/BlockClosure.extension/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..1d6f4884e3fa498bb1c003df186025d226a8e5f8 --- /dev/null +++ b/utils/JSBridge-Core.package/BlockClosure.extension/properties.json @@ -0,0 +1,2 @@ +{ + "name" : "BlockClosure" } diff --git a/utils/JSBridge-Core.package/BlockContext.extension/instance/asJSArgument.st b/utils/JSBridge-Core.package/BlockContext.extension/instance/asJSArgument.st new file mode 100644 index 0000000000000000000000000000000000000000..e275f77c1a44394644481d1a33fd7978273b63e1 --- /dev/null +++ b/utils/JSBridge-Core.package/BlockContext.extension/instance/asJSArgument.st @@ -0,0 +1,4 @@ +*jsbridge-core +asJSArgument + "converted to JS function by plugin" + ^self diff --git a/utils/JSBridge-Core.package/BlockContext.extension/methodProperties.json b/utils/JSBridge-Core.package/BlockContext.extension/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..101abad9992466c032f3eee48f42a5a24d54cfa7 --- /dev/null +++ b/utils/JSBridge-Core.package/BlockContext.extension/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "asJSArgument" : "bf 11/25/2014 18:12" } } diff --git a/utils/JSBridge-Core.package/BlockContext.extension/properties.json b/utils/JSBridge-Core.package/BlockContext.extension/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..ea80c1e593be33041cbfd0ab6d26d5bba2b3b260 --- /dev/null +++ b/utils/JSBridge-Core.package/BlockContext.extension/properties.json @@ -0,0 +1,2 @@ +{ + "name" : "BlockContext" } diff --git a/utils/JSBridge-Core.package/Boolean.extension/instance/asJSArgument.st b/utils/JSBridge-Core.package/Boolean.extension/instance/asJSArgument.st new file mode 100644 index 0000000000000000000000000000000000000000..2fc5fcc84566076c2213ff5b70f733b1f7fd5d13 --- /dev/null +++ b/utils/JSBridge-Core.package/Boolean.extension/instance/asJSArgument.st @@ -0,0 +1,4 @@ +*jsbridge-core +asJSArgument + "converted to JS true/false by plugin" + ^self diff --git a/utils/JSBridge-Core.package/Boolean.extension/methodProperties.json b/utils/JSBridge-Core.package/Boolean.extension/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..101abad9992466c032f3eee48f42a5a24d54cfa7 --- /dev/null +++ b/utils/JSBridge-Core.package/Boolean.extension/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "asJSArgument" : "bf 11/25/2014 18:12" } } diff --git a/utils/JSBridge-Core.package/Boolean.extension/properties.json b/utils/JSBridge-Core.package/Boolean.extension/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..c15542b766bdaeb1a10d18c6cb24b0c72fa85f6a --- /dev/null +++ b/utils/JSBridge-Core.package/Boolean.extension/properties.json @@ -0,0 +1,2 @@ +{ + "name" : "Boolean" } diff --git a/utils/JSBridge-Core.package/Collection.extension/instance/asJSArgument.st b/utils/JSBridge-Core.package/Collection.extension/instance/asJSArgument.st new file mode 100644 index 0000000000000000000000000000000000000000..2972d888b52faf8467ebe324bb5db60fcf8e1da9 --- /dev/null +++ b/utils/JSBridge-Core.package/Collection.extension/instance/asJSArgument.st @@ -0,0 +1,8 @@ +*jsbridge-core +asJSArgument + "converted to JS array by plugin" + | array i | + array := Array new: self size. + i := 0. + self do: [:each | array at: (i := i + 1) put: each asJSArgument]. + ^ array diff --git a/utils/JSBridge-Core.package/Collection.extension/methodProperties.json b/utils/JSBridge-Core.package/Collection.extension/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..101abad9992466c032f3eee48f42a5a24d54cfa7 --- /dev/null +++ b/utils/JSBridge-Core.package/Collection.extension/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "asJSArgument" : "bf 11/25/2014 18:12" } } diff --git a/utils/JSBridge-Core.package/Collection.extension/properties.json b/utils/JSBridge-Core.package/Collection.extension/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..93b0dc328b87eee2a3c9f1b117fbe22102fd1b41 --- /dev/null +++ b/utils/JSBridge-Core.package/Collection.extension/properties.json @@ -0,0 +1,2 @@ +{ + "name" : "Collection" } diff --git a/utils/JSBridge-Core.package/Dictionary.extension/instance/asJSArgument.st b/utils/JSBridge-Core.package/Dictionary.extension/instance/asJSArgument.st new file mode 100644 index 0000000000000000000000000000000000000000..3e4bdc56981ea1a292b99b1319e36d7ff6163f41 --- /dev/null +++ b/utils/JSBridge-Core.package/Dictionary.extension/instance/asJSArgument.st @@ -0,0 +1,9 @@ +*jsbridge-core +asJSArgument + "converted to JS object by plugin" + | assocs i | + assocs := Array new: self size. + i := 0. + self associationsDo: [:a | + assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. + ^ assocs diff --git a/utils/JSBridge-Core.package/Dictionary.extension/methodProperties.json b/utils/JSBridge-Core.package/Dictionary.extension/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..101abad9992466c032f3eee48f42a5a24d54cfa7 --- /dev/null +++ b/utils/JSBridge-Core.package/Dictionary.extension/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "asJSArgument" : "bf 11/25/2014 18:12" } } diff --git a/utils/JSBridge-Core.package/Dictionary.extension/properties.json b/utils/JSBridge-Core.package/Dictionary.extension/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..cb1bf501dafc3bedd92192ab13ccd98a9b3fa8b0 --- /dev/null +++ b/utils/JSBridge-Core.package/Dictionary.extension/properties.json @@ -0,0 +1,2 @@ +{ + "name" : "Dictionary" } diff --git a/utils/JSBridge-Core.package/Float.extension/instance/asJSArgument.st b/utils/JSBridge-Core.package/Float.extension/instance/asJSArgument.st new file mode 100644 index 0000000000000000000000000000000000000000..46c64690ea058b1085b95db77aa064a2189f2d88 --- /dev/null +++ b/utils/JSBridge-Core.package/Float.extension/instance/asJSArgument.st @@ -0,0 +1,4 @@ +*jsbridge-core +asJSArgument + "converted to JS number by plugin" + ^self diff --git a/utils/JSBridge-Core.package/Float.extension/methodProperties.json b/utils/JSBridge-Core.package/Float.extension/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..101abad9992466c032f3eee48f42a5a24d54cfa7 --- /dev/null +++ b/utils/JSBridge-Core.package/Float.extension/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + }, + "instance" : { + "asJSArgument" : "bf 11/25/2014 18:12" } } diff --git a/utils/JSBridge-Core.package/Float.extension/properties.json b/utils/JSBridge-Core.package/Float.extension/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..761fb5650f92058c91d6bc8cce47e03ccd9d6cd0 --- /dev/null +++ b/utils/JSBridge-Core.package/Float.extension/properties.json @@ -0,0 +1,2 @@ +{ + "name" : "Float" } diff --git a/utils/JSBridge-Core.package/JSException.class/README.md b/utils/JSBridge-Core.package/JSException.class/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8985fd40bb94b96c4d915559f821f3a8d491ea1b --- /dev/null +++ b/utils/JSBridge-Core.package/JSException.class/README.md @@ -0,0 +1 @@ +An exception for JavaScript errors -- holds the error \ No newline at end of file diff --git a/utils/JSBridge-Core.package/JSException.class/class/error..st b/utils/JSBridge-Core.package/JSException.class/class/error..st new file mode 100644 index 0000000000000000000000000000000000000000..c6a1d8951e4746aafe474591e832e650766afcb7 --- /dev/null +++ b/utils/JSBridge-Core.package/JSException.class/class/error..st @@ -0,0 +1,3 @@ +exceptionInstantiator +error: jsError + JSException new error: jsError \ No newline at end of file diff --git a/utils/JSBridge-Core.package/JSException.class/instance/error..st b/utils/JSBridge-Core.package/JSException.class/instance/error..st new file mode 100644 index 0000000000000000000000000000000000000000..64c2fd6adbb619bcd2ac0f017f2fb6576a181b89 --- /dev/null +++ b/utils/JSBridge-Core.package/JSException.class/instance/error..st @@ -0,0 +1,4 @@ +signaling +error: error + jsError := error. + self signal: error message \ No newline at end of file diff --git a/utils/JSBridge-Core.package/JSException.class/methodProperties.json b/utils/JSBridge-Core.package/JSException.class/methodProperties.json new file mode 100644 index 0000000000000000000000000000000000000000..37f8e339bf6354f6c500427e0bf05d91c2b5ef78 --- /dev/null +++ b/utils/JSBridge-Core.package/JSException.class/methodProperties.json @@ -0,0 +1,5 @@ +{ + "class" : { + "error:" : "WRB 4/27/2020 10:10" }, + "instance" : { + "error:" : "WRB 4/27/2020 10:09" } } diff --git a/utils/JSBridge-Core.package/JSException.class/properties.json b/utils/JSBridge-Core.package/JSException.class/properties.json new file mode 100644 index 0000000000000000000000000000000000000000..aaece8d87e73d779372f6f53db7a0de5c367a218 --- /dev/null +++ b/utils/JSBridge-Core.package/JSException.class/properties.json @@ -0,0 +1,14 @@ +{ + "category" : "JSBridge-Core", + "classinstvars" : [ + ], + "classvars" : [ + ], + "commentStamp" : "WRB 4/27/2020 10:15", + "instvars" : [ + "jsError" ], + "name" : "JSException", + "pools" : [ + ], + "super" : "Exception", + "type" : "normal" } diff --git a/utils/JSBridge-Core.package/JSObjectProxy.class/README.md b/utils/JSBridge-Core.package/JSObjectProxy.class/README.md new file mode 100644 index 0000000000000000000000000000000000000000..13d076243ce106bc840d405ed28ab84ca35ebeef --- /dev/null +++ b/utils/JSBridge-Core.package/JSObjectProxy.class/README.md @@ -0,0 +1,66 @@ +A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. + +"Call global function" +JS alert: 'Squeak says Hello World!'. + +"Call function on global object (open console to see result)" +JS console log: 'Squeak says Hello World!'. + +"Modify DOM" +((JS document getElementsByTagName: 'h1') at: 0) + at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. + +"Create new Object, add properties and a method, retrieve property, call method" +| obj | +obj := JS Object new. +obj at: #someProp put: 42. +obj at: #complexProp put: {#a -> 3. #b -> 4}. +obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). +{obj someProp. obj complexProp. obj someMethod} + +"Inspect all properties in global window object" +| object propNames propValues | +object := JS window. +propNames := JS Object keys: object. +propValues := (0 to: propNames length - 1) collect: [:i | + (propNames at: i) -> (object at: (propNames at: i))]. +(propValues as: Dictionary) inspect + +"A Squeak block becomes a JavaScript function" +JS at: #sqPlus put: [:arg0 :arg1 | + Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. + arg0 + arg1]. + +"It can be called from JavaScript (open transcript to see)" +JS eval: 'sqPlus(3, 4)'. + +"It returns a Promise. When resolved, you can access the result" +JS eval: 'sqPlus(3, 4).then(function(result) { + console.log(result); +})'. + +"Which even works from Squeak ..." +(JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. + +"But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!" +JS await: (JS sqPlus: 3 and: 4). + +"If you don't need a result, just ignore the Promise" +JS setTimeout: [JS alert: 'Hi'] ms: 1000. + +"Now for some fun: Load jQuery, and compile a helper method" +| script | +(JS at: #jQuery) ifNil: [ + script := JS document createElement: 'SCRIPT'. + script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. + script at: 'type' put: 'text/javascript'. + ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. +]. +String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. + +"Use jQuery" +'canvas' asJQuery hide: 'slow'; show: 'fast'. + +'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. + +'