├── .gitignore ├── .travis.yml ├── CHANGES.md ├── CONTRIBUTING.md ├── Gruntfile.coffee ├── LICENSE ├── README.md ├── bower.json ├── bundles ├── gss.document.coffee ├── gss.document.parser.coffee ├── gss.engine.coffee └── gss.engine.parser.coffee ├── dist ├── gss.js └── gss.min.js ├── package.json ├── spec ├── all.coffee ├── cassowary.coffee ├── command-nested-rules.coffee ├── command.coffee ├── conditions.coffee ├── domain.coffee ├── end-to-end.coffee ├── engine.coffee ├── fixtures │ ├── external-file-2-3.gss │ ├── external-file-2.gss │ ├── external-file-3.gss │ ├── external-file-css1.gss │ └── external-file.gss ├── matrix.coffee ├── pages.coffee ├── pages │ ├── grandrop.html │ ├── grid_banner_cta.html │ ├── grid_head_cta.html │ ├── grid_multiple_media.html │ ├── grid_post_simple.html │ ├── grid_team.html │ ├── profile_card.html │ └── virtuals.html ├── perf.coffee ├── poly-test │ └── full.coffee ├── runner.html ├── selectors.coffee ├── signatures.coffee ├── specs.coffee ├── styles.coffee ├── stylesheet.coffee ├── thread.coffee ├── units.coffee ├── vendor │ └── MutationObserver.attributes.js └── view.coffee ├── src ├── Document.coffee ├── Engine.coffee ├── GSS.coffee ├── document │ ├── Style.coffee │ ├── commands │ │ ├── Selector.coffee │ │ ├── Stylesheet.coffee │ │ └── Unit.coffee │ ├── properties │ │ ├── Getters.coffee │ │ └── Styles.coffee │ └── types │ │ ├── Action.coffee │ │ ├── Color.coffee │ │ ├── Easing.coffee │ │ ├── Gradient.coffee │ │ ├── Matrix.coffee │ │ ├── Measurement.coffee │ │ ├── Primitive.coffee │ │ └── URL.coffee └── engine │ ├── Command.coffee │ ├── Domain.coffee │ ├── Query.coffee │ ├── Update.coffee │ ├── commands │ ├── Condition.coffee │ ├── Constraint.coffee │ ├── Iterator.coffee │ └── Variable.coffee │ ├── domains │ ├── Data.coffee │ ├── Finite.coffee │ ├── Input.coffee │ ├── Linear.coffee │ └── Output.coffee │ └── utilities │ ├── Console.coffee │ ├── Exporter.coffee │ └── Inspector.coffee └── vendor ├── MutationObserver.js ├── gl-matrix.js ├── observe.js └── weakmap.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /spec/js/**/*.js 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | before_script: 5 | - grunt build 6 | script: grunt crossbrowser 7 | deploy: 8 | provider: s3 9 | secret_access_key: 10 | secure: He1S0/sHSIFVuX55RDVGSdkkO8+gzolMYEHq+EiZBrC9VgXX8BcqS+dFVU2nwhNO1okTOMurCqBzTlKucwyH5GhbvYmxmHiPVWzWhjLiebuvStm6OZ75m8prkQlseuRkmArVJARDSKe2e9LRfNXam0pe+60BJpBc9v10VeByuCs= 11 | access_key_id: 12 | secure: BIylx5hpoOeaPT3mLiSX6iLGEFyazrThGooi0QTI+m6z2c1FgvGAGu6kp8bKvNlB6Tti92DxI2iiDFxl23exph3rpFZ/LSFb2j0b+e/B6ydaeA2pvYfVpuOLUT5ulGlSWMvMooBEZNgMArevq8OguhcGddJEP3KmEWg7sDszKME= 13 | bucket: cdn.thegrid.io 14 | skip_cleanup: true 15 | local-dir: dist/ 16 | upload-dir: gss/$TRAVIS_BRANCH/${TRAVIS_TAG:-HEAD} 17 | endpoint: s3-website-us-west-2.amazonaws.com 18 | region: us-west-2 19 | on: 20 | all_branches: true 21 | env: 22 | global: 23 | - secure: |- 24 | JQFbHURLGcXjfgSy3bf+8unRrMgbkvQ/MAkthK1N9dpVHBKwFQUHa7NR2N2Q 25 | lWbCvWQGwyguu91oPm1tQmHHRUDwzlJPXaNT2Dt5vxI7YPDtf6aPU8RkcqI9 26 | i5ZmNcDIAF8VQQYDf07ZStAp72T21zCOnsBEHC5OKeR8JW18qMo= 27 | - secure: |- 28 | AwXQ++vjwzi2YQLnrvlC0SExqCZh3nxVsYJNib8TR++UJJ2jkosfXeDUvvKk 29 | QrIpnMlHte1kn6KRcojm+HL9AkTHMWhnQE1aYAe12FwBCgD0AI3dD7162s5N 30 | VB9WEjNUagi6ppPs8e2IJ7D6r6qZUxCj9zixIg4RBkxu/gq87kA= 31 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | GSS Engine ChangeLog 2 | ==================== 3 | 4 | ## 1.0.3-beta (May 24th 2014) 5 | 6 | - Selectors other than id, class & tag can now be used in CCSS when surrounded by parans: 7 | 8 | ```css 9 | (.section > article:not(.featured))[font-size] == 100; 10 | ``` 11 | 12 | - Support for plural selectors on either side of a constraint operator: 13 | 14 | ```css 15 | .article[font-size] == .header[font-size]; 16 | 17 | @h |[.a][.b]| in(.cont) chain-width; 18 | 19 | .container { 20 | @h |[.itemA][.itemB]| in(::) chain-width; 21 | } 22 | ``` 23 | 24 | - 2D sugar props: `size`, `position`, `center`, `intrinsic-size`, `top-left`, `top-right`, `bottom-left`, `bottom-right` 25 | 26 | 27 | ## 1.0.2-beta (April 14th 2014) 28 | 29 | - VGL: `@grid-template` empty zones can be defined with `.` 30 | - VGL: added `in()` to `@grid-template` 31 | - VGL: added `h/v/top/right/bottom/left-gap()` to `@grid-template` 32 | 33 | VGL is still undocumented & under heavy dev. 34 | 35 | ## 1.0.1-beta (April 7th 2014) 36 | 37 | - VFL: **Point** support 38 | - VFL: shorthands `@h` & `@v` for `@horizontal` & `@vertical` 39 | - VFL: `outer-gap()` 40 | - VFL: Default containing element selector to `::this` 41 | 42 | ### New VFL Sugar 43 | 44 | With the new VFL API sugar, the following: 45 | 46 | ```css 47 | #container { 48 | @h |-[#a]-[#b]-| gap(10) outer-gap(20); 49 | } 50 | ``` 51 | 52 | is equivalent too: 53 | 54 | ```css 55 | @horizontal |-20-[#a]-10-[#b]-20-| in(#container); 56 | ``` 57 | 58 | ### VFL Points 59 | 60 | Elements can be aligned relative to arbitrary positioned points using `< Number | Constraint Variable | Element Property >` 61 | 62 | To horizontally align two buttons, each 8px from the center of the window: 63 | 64 | ```css 65 | /* VFL */ 66 | @h [#btn1]-<::window[center-x]>-[#btn2] gap(8); 67 | 68 | /* Equivalent CCSS */ 69 | #btn1[right] + 8 == ::window[center-x]; 70 | ::window[center-x] + 8 == #btn2[left]; 71 | ``` 72 | 73 | Alignments can be positioned within points: 74 | 75 | ```css 76 | /* VFL */ 77 | @h <#wall[center-x]>-[#poster]-[#clock]-<::window[right])> gap(7); 78 | 79 | /* Equivalent CCSS */ 80 | #wall[center-x] + 7 == #poster[left]; 81 | #poster[right] + 7 == #clock[left]; 82 | ::window[right] - 7 == #clock[right]; 83 | ``` 84 | 85 | Numbers, variables and arithmetic can be used: 86 | 87 | ```css 88 | /* VFL */ 89 | @v <100>[#box]<[row2]>; 90 | 91 | /* Equivalent CCSS */ 92 | 100 == #box[top]; 93 | #box[bottom] == [row2]; 94 | ```css 95 | 96 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Guidelines for contributing to GSS. 4 | 5 | 6 | ## Issues 7 | 8 | Issues are most appropriate for bugs and other problems encountered using GSS. Ideally, include a demo project (GitHub repo, CodePen, JSFiddle, etc) that focuses on the issue. 9 | 10 | Please search existing issues before filing new ones. 11 | 12 | 13 | ## Pull requests 14 | 15 | Pull requests should be issued from dedicated branches, as opposed to `master`. 16 | 17 | It may be worth opening an issue to discuss feature requests and major changes before attempting to implement them. 18 | 19 | Prefixing a pull request with `[WIP]` and committing early is a good way to get feedback without too much investment. 20 | 21 | 22 | ## Questions 23 | 24 | When questions are asked, consider providing an answer by opening a pull request against the GSS documentation. 25 | 26 | 27 | ## Dependencies 28 | 29 | Dependencies should be referenced by an appropriate version number or tag and never by overly-permissive references such as branch names or `*`. In the case that a dependency has no available versions or tags, use a git commit SHA. 30 | 31 | 32 | ## Releases 33 | 34 | Releases should always be made from `master` and follow [semantic versioning](http://semver.org/). 35 | 36 | References in code to the version number should be updated before building for distribution and tagging a new release. 37 | 38 | Prefer using the [GitHub UI](https://github.com/gss/engine/releases/new) and provide useful release notes. 39 | 40 | Lastly, releases should be published to npm by running `npm publish`. 41 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | # Project configuration 3 | @initConfig 4 | pkg: @file.readJSON 'package.json' 5 | 6 | clean: 7 | dist: 8 | src: ['dist/**/*.js'] 9 | spec: 10 | src: ['spec/js/**/*.js'] 11 | 12 | browserify: 13 | options: 14 | transform: ['coffeeify'] 15 | browserifyOptions: 16 | extensions: ['.coffee'] 17 | fullPaths: false 18 | dist: 19 | files: [{ 20 | expand: true 21 | cwd: 'bundles' 22 | src: '**/*.coffee' 23 | dest: 'dist' 24 | ext: '.js' 25 | extDot: 'last' 26 | }] 27 | spec: 28 | files: 29 | 'spec/js/specs.js': ['spec/specs.coffee'] 30 | 31 | # Automated recompilation and testing when developing 32 | watch: 33 | spec: 34 | files: ['spec/**/*.coffee'] 35 | tasks: ['browserify:spec'] 36 | src: 37 | files: ['src/**/*.coffee'] 38 | tasks: ['browserify:dist'] 39 | 40 | 41 | # JavaScript minification for the browser 42 | uglify: 43 | options: 44 | report: 'min' 45 | dist: 46 | files: [{ 47 | expand: true 48 | cwd: 'dist' 49 | src: '**/*.js' 50 | dest: 'dist' 51 | ext: '.min.js' 52 | extDot: 'last' 53 | }] 54 | 55 | # Adding version information to the generated files 56 | banner: '/* <%= pkg.name %> - version <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd") %>) - http://gridstylesheets.org */' 57 | usebanner: 58 | dist: 59 | options: 60 | position: 'top' 61 | banner: '<%= banner %>' 62 | files: 63 | src: ['dist/**/*.js'] 64 | 65 | # Syntax checking 66 | coffeelint: 67 | options: 68 | 'max_line_length': 69 | level: 'ignore' 70 | 'no_trailing_whitespace': 71 | level: 'ignore' 72 | 'no_backticks': 73 | level: 'ignore' 74 | bundle: 75 | files: 76 | src: ['src/**/*.coffee'] 77 | src: 78 | files: 79 | src: ['src/**/*.coffee'] 80 | spec: 81 | files: 82 | src: ['spec/*.coffee'] 83 | 84 | docco: 85 | src: 86 | src: ['src/**/*.coffee'] 87 | options: 88 | output: 'docs/' 89 | css: 'vendor/docs.css' 90 | 91 | # Cross-browser testing 92 | connect: 93 | server: 94 | options: 95 | base: '' 96 | port: 9999 97 | 98 | # BDD tests on browser 99 | mocha_phantomjs: 100 | all: 101 | options: 102 | reporter: 'node_modules/mocha/lib/reporters/spec.js' 103 | urls: ['http://127.0.0.1:9999/spec/runner.html'] 104 | 105 | 'saucelabs-mocha': 106 | all: 107 | options: 108 | urls: ['http://127.0.0.1:9999/spec/runner.html'] 109 | browsers: [ 110 | browserName: 'googlechrome' 111 | platform: 'OS X 10.8' 112 | version: '37' 113 | , 114 | browserName: 'firefox' 115 | platform: 'Windows 7', 116 | version: '33' 117 | , 118 | browserName: 'safari' 119 | platform: 'OS X 10.9' 120 | version: '7' 121 | #, 122 | # browserName: 'internet explorer' 123 | # platform: 'Windows 8.1', 124 | # version: '11' 125 | , 126 | browserName: 'internet explorer' 127 | version: '10' 128 | , 129 | browserName: 'internet explorer' 130 | version: '9' 131 | , 132 | browserName: 'iPhone' 133 | platform: "OS X 10.10" 134 | version: '8.0' 135 | , 136 | browserName: "android" 137 | ] 138 | build: process.env.TRAVIS_JOB_ID 139 | testname: 'GSS browser tests' 140 | tunnelTimeout: 20 141 | concurrency: 6 142 | 143 | 144 | # Grunt plugins used for building 145 | @loadNpmTasks 'grunt-browserify' 146 | @loadNpmTasks 'grunt-contrib-uglify' 147 | @loadNpmTasks 'grunt-contrib-clean' 148 | @loadNpmTasks 'grunt-docco' 149 | @loadNpmTasks 'grunt-banner' 150 | 151 | # Grunt plugins used for testing 152 | @loadNpmTasks 'grunt-coffeelint' 153 | @loadNpmTasks 'grunt-mocha-phantomjs' 154 | @loadNpmTasks 'grunt-contrib-watch' 155 | 156 | # Cross-browser testing in the cloud 157 | @loadNpmTasks 'grunt-contrib-connect' 158 | @loadNpmTasks 'grunt-saucelabs' 159 | 160 | @registerTask 'build', ['coffeelint:src', 'coffeelint:bundle', 'clean:dist', 'browserify:dist', 'uglify:dist', 'usebanner:dist'] 161 | @registerTask 'test', ['coffeelint:spec', 'clean:spec', 'browserify:spec', 'build', 'phantom'] 162 | @registerTask 'phantom', ['connect', 'mocha_phantomjs'] 163 | @registerTask 'crossbrowser', ['test', 'saucelabs-mocha'] 164 | @registerTask 'default', ['test'] 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014 The Grid 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GSS engine [![Build Status](https://travis-ci.org/gss/engine.png?branch=master)](https://travis-ci.org/gss/engine) 2 | ========== 3 | 4 | [![Cross-browser testing status](https://saucelabs.com/browser-matrix/gss-engine.svg)](https://saucelabs.com/u/gss-engine) 5 | 6 | Compiles and runs Grid Style Sheet (GSS) rules. GSS is an implementation of Badros & Borning's [Constraint Cascading Style Sheets](http://www.cs.washington.edu/research/constraints/web/ccss-uwtr.pdf), enabling far better layout control through building relational rules between different elements. 7 | 8 | GSS supports the following syntaxes for defining layout rules: 9 | 10 | * [CCSS](https://github.com/gss/ccss-compiler#readme) - direct constraints related to position and size of DOM elements 11 | * [VFL](https://github.com/gss/vfl-compiler#readme) - horizontal and vertical spacing constraints based on [Apple's Visual Format Language](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html) 12 | 13 | Please refer to for documentation and usage instructions. 14 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gss", 3 | "homepage": "http://gridstylesheets.org/", 4 | "authors": [ 5 | "Dan Tocchini " 6 | ], 7 | "description": "Grid Style Sheets Runtime", 8 | "main": [ 9 | "dist/gss.js" 10 | ], 11 | "keywords": [ 12 | "gss" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | ".gitignore", 17 | ".travis.yml", 18 | "/lib", 19 | "/node_modules", 20 | "/spec", 21 | "/src", 22 | "Gruntfile.coffee", 23 | "package.json" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /bundles/gss.document.coffee: -------------------------------------------------------------------------------- 1 | GSS = require './gss.engine' 2 | GSS.Document = require '../src/Document' 3 | module.exports = GSS 4 | -------------------------------------------------------------------------------- /bundles/gss.document.parser.coffee: -------------------------------------------------------------------------------- 1 | GSS = require './gss.document' 2 | GSS.Parser = require 'ccss-compiler' 3 | module.exports = GSS 4 | -------------------------------------------------------------------------------- /bundles/gss.engine.coffee: -------------------------------------------------------------------------------- 1 | GSS = require '../src/GSS' 2 | global.GSS = GSS 3 | module.exports = GSS 4 | -------------------------------------------------------------------------------- /bundles/gss.engine.parser.coffee: -------------------------------------------------------------------------------- 1 | GSS = require './gss.engine' 2 | GSS.Parser = require 'ccss-compiler' 3 | module.exports = GSS 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gss-engine", 3 | "description": "GSS styling engine", 4 | "version": "2.0.1", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/gss/engine.git" 8 | }, 9 | "licenses": [ 10 | { 11 | "type": "MIT", 12 | "url": "https://github.com/gss/engine/blob/master/LICENSE" 13 | } 14 | ], 15 | "devDependencies": { 16 | "chai": "~1.9.0", 17 | "coffeeify": "^1.0.0", 18 | "mocha": "~1.17.0", 19 | "grunt": "^0.4.5", 20 | "grunt-banner": "^0.2.1", 21 | "grunt-browserify": "^3.2.1", 22 | "grunt-cli": "^0.1.13", 23 | "grunt-coffeelint": "0.0.6", 24 | "grunt-contrib-watch": "~0.3.1", 25 | "grunt-contrib-uglify": "~0.2.1", 26 | "grunt-contrib-clean": "~0.5.0", 27 | "grunt-contrib-connect": "~0.3.0", 28 | "grunt-docco": "^0.3.3", 29 | "grunt-mocha-phantomjs": "~0.4.0", 30 | "grunt-saucelabs": "^8.4.1" 31 | }, 32 | "keywords": [], 33 | "scripts": { 34 | "test": "grunt test" 35 | }, 36 | "main": "./src/Engine", 37 | "paths": [ 38 | "./vendor" 39 | ], 40 | "dependencies": { 41 | "ccss-compiler": "~1.1.2", 42 | "vfl-compiler": "~1.1.3", 43 | "cassowary": "git://github.com/slightlyoff/cassowary.js.git#829729cb602d4a17341b4d1a5dba6767bf1cfd5c" 44 | }, 45 | "browserify": { 46 | "name": "gss-engine", 47 | "transform": [ 48 | "coffeeify" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spec/all.coffee: -------------------------------------------------------------------------------- 1 | require("./cassowary") 2 | require("./command-nested-rules") 3 | require("./selectors") 4 | require("./matrix") 5 | require("./conditions") 6 | require("./command") 7 | require("./domain") 8 | require("./end-to-end") 9 | require("./engine") 10 | require("./perf") 11 | require("./units") 12 | require("./matrix") 13 | require("./signatures") 14 | require("./styles") 15 | require("./thread") 16 | require("./stylesheet") 17 | require("./poly-test/full") 18 | -------------------------------------------------------------------------------- /spec/cassowary.coffee: -------------------------------------------------------------------------------- 1 | expect = chai.expect 2 | 3 | describe 'Cassowary', -> 4 | c = GSS.Engine.prototype.Solver::Engine 5 | it 'should be available', -> 6 | expect(c).to.be.a 'function' 7 | it 'var >= num', -> 8 | solver = new c.SimplexSolver() 9 | x = new c.Variable({ value: 10 }) 10 | ieq = new c.Inequality(x, c.GEQ, 100) 11 | solver.addConstraint(ieq) 12 | expect(x.value).to.equal 100 13 | it '[x]==7; [y]==5; [x] - [y] == [z] // z is 2', -> 14 | solver = new c.SimplexSolver() 15 | x = new c.Variable() 16 | y = new c.Variable() 17 | z = new c.Variable() 18 | eq1 = new c.Equation(x,7) 19 | eq2 = new c.Equation(y,5) 20 | eq3 = new c.Equation(c.minus(x,y),z) 21 | solver.addConstraint(eq1) 22 | solver.addConstraint(eq2) 23 | solver.addConstraint(eq3) 24 | expect(x.value).to.equal 7 25 | expect(y.value).to.equal 5 26 | expect(z.value).to.equal 2 27 | it 'top left right bottom // z is 2', -> 28 | solver = new c.SimplexSolver() 29 | x = new c.Variable() 30 | y = new c.Variable() 31 | z = new c.Variable() 32 | eq1 = new c.Equation(x,7) 33 | eq2 = new c.Equation(y,5) 34 | eq3 = new c.Equation(c.minus(x,y),z) 35 | solver.addConstraint(eq1) 36 | solver.addConstraint(eq2) 37 | solver.addConstraint(eq3) 38 | expect(x.value).to.equal 7 39 | expect(y.value).to.equal 5 40 | expect(z.value).to.equal 2 41 | it 'plus expression', -> 42 | solver = new c.SimplexSolver() 43 | solver.autoSolve = false 44 | aw = new c.Variable() 45 | tw = new c.Variable() 46 | pad = new c.Variable() 47 | eq1 = new c.Equation(tw,100,c.Strength.required) 48 | eq2 = new c.Equation(aw,c.plus(tw,pad),c.Strength.required) 49 | eq3 = new c.Equation(pad,2,c.Strength.required) 50 | solver.addConstraint(eq1).addConstraint(eq2).addConstraint(eq3) 51 | solver.solve() 52 | expect(aw.value).to.equal 102 53 | expect(tw.value).to.equal 100 54 | expect(pad.value).to.equal 2 55 | it 'times expression', -> 56 | solver = new c.SimplexSolver() 57 | solver.autoSolve = false 58 | aw = new c.Variable() 59 | tw = new c.Variable() 60 | zoom = new c.Variable() 61 | solver.addEditVar(zoom) 62 | solver.beginEdit() 63 | solver.suggestValue(zoom,2) 64 | solver.solve() 65 | # setting value on zoom so equation can be linear 66 | eq1 = new c.Equation(tw,100,c.Strength.required) 67 | eq2 = new c.Equation(aw,c.times(tw,zoom.value),c.Strength.required) 68 | solver.addConstraint(eq1).addConstraint(eq2) 69 | solver.solve() 70 | expect(aw.value).to.equal 200 71 | expect(tw.value).to.equal 100 72 | expect(zoom.value).to.equal 2 73 | it 'hierarchy', -> 74 | solver = new c.SimplexSolver() 75 | solver.autoSolve = false 76 | x = new c.Variable() 77 | eq1 = new c.Equation(x,100,c.Strength.strong) 78 | eq2 = new c.Equation(x,10,c.Strength.medium) 79 | eq3 = new c.Equation(x,1,c.Strength.weak) 80 | solver.addConstraint(eq1).addConstraint(eq2).addConstraint(eq3) 81 | solver.solve() 82 | expect(x.value).to.equal 100 83 | solver.removeConstraint eq1 84 | solver.solve() 85 | expect(x.value).to.equal 10 86 | solver.removeConstraint eq2 87 | solver.solve() 88 | expect(x.value).to.equal 1 89 | 90 | it 'weights', -> 91 | solver = new c.SimplexSolver() 92 | solver.autoSolve = false 93 | x = new c.Variable() 94 | eq1 = new c.Inequality(x, c.GEQ, 100, c.Strength.medium, 0.5) 95 | eq2 = new c.Inequality(x, c.GEQ, 10, c.Strength.medium, 0.3) 96 | solver.addConstraint(eq1).addConstraint(eq2) 97 | solver.solve() 98 | expect(x.value).to.equal 100 99 | solver.removeConstraint eq1 100 | solver.solve() 101 | expect(x.value).to.equal 10 102 | solver.addConstraint eq1 103 | solver.solve() 104 | expect(x.value).to.equal 100 105 | solver.solve() 106 | solver.removeConstraint eq2 107 | expect(x.value).to.equal 100 108 | solver.solve() 109 | solver.removeConstraint eq1 110 | solver.solve() 111 | expect(x.value).to.equal 0 112 | solver.addConstraint eq2 113 | solver.solve() 114 | expect(x.value).to.equal 10 115 | -------------------------------------------------------------------------------- /spec/conditions.coffee: -------------------------------------------------------------------------------- 1 | Engine = GSS #require 'gss-engine/lib/Engine.js' 2 | 3 | assert = chai.assert 4 | expect = chai.expect 5 | 6 | remove = (el) -> 7 | el?.parentNode?.removeChild(el) 8 | 9 | fixtures = document.getElementById 'fixtures' 10 | 11 | xdescribe 'Conditions', -> 12 | describe 'conditions that use', -> 13 | describe 'single selector', -> 14 | it 'should initialize condition once', -> 15 | container = document.createElement('div') 16 | container.innerHTML = """ 17 |
18 |
19 | """ 20 | window.engine = engine = new GSS(container, { 21 | A: 100 22 | }) 23 | solution = engine.solve [ 24 | ['==', ['get', ['tag', 'div'], 'x'], ['get', 'A']] 25 | 26 | ['if', ['>', ['get', ['tag', 'div'], 'x'], 50], 27 | ['==', ['get', 'b'], 1]] 28 | 29 | ['unless', ['>', ['get', ['#', 'div1'], 'x'], 50], 30 | ['==', ['get', 'c'], 2] 31 | ['==', ['get', 'c'], 3]] 32 | 33 | ['if', ['>', ['get', ['#', 'div1'], 'x'], 50], 34 | ['==', ['get', 'd'], 2] 35 | ['==', ['get', 'd'], 3]] 36 | ] 37 | expect(solution).to.eql 38 | b: 1, 39 | c: 3, 40 | d: 2, 41 | '$div1[x]': 100 42 | '$div2[x]': 100 43 | 44 | solution = engine.solve({A: 50}) 45 | expect(solution).to.eql 46 | A: 50 47 | b: null, 48 | c: 2, 49 | d: 3, 50 | '$div1[x]': 50 51 | '$div2[x]': 50 52 | 53 | solution = engine.solve({A: 100}) 54 | expect(solution).to.eql 55 | A: 100 56 | b: 1, 57 | c: 3, 58 | d: 2, 59 | '$div1[x]': 100 60 | '$div2[x]': 100 61 | solution = engine.solve({A: null}) 62 | expect(solution).to.eql {A: null, b: null, c: null} 63 | 64 | describe 'multiple selectors', -> 65 | 66 | describe 'multiple conditions that observe the same condition', -> 67 | it 'should reuse observers', -> 68 | 69 | window.engine = engine = new GSS({A: 100}) 70 | solution = engine.solve [ 71 | ['if', ['>', ['get', 'A'], 50], 72 | ['==', ['get', 'b'], 1]] 73 | ['if', ['>', ['get', 'A'], 50], 74 | ['==', ['get', 'c'], 3] 75 | ['==', ['get', 'c'], 2]] 76 | ] 77 | expect(solution).to.eql b: 1, c: 3 78 | solution = engine.solve({A: 50}) 79 | expect(solution).to.eql {A: 50, b: null, c: 2} 80 | solution = engine.solve({A: 100}) 81 | expect(solution).to.eql {A: 100, b: 1, c: 3} 82 | solution = engine.solve({A: null}) 83 | expect(solution).to.eql {A: null, b: null, c: null} 84 | 85 | describe 'Else', -> 86 | it 'should attach to a condition', -> 87 | window.engine = engine = new GSS({A: 100}) 88 | solution = engine.solve [ 89 | ['if', ['>', ['get', 'A'], 75], 90 | ['==', ['get', 'b'], 1]] 91 | ['elseif', ['>', ['get', 'A'], 50], 92 | ['==', ['get', 'c'], 2]] 93 | ['else', 94 | ['==', ['get', 'd'], 3]] 95 | ] 96 | 97 | expect(solution).to.eql b: 1 -------------------------------------------------------------------------------- /spec/fixtures/external-file-2-3.gss: -------------------------------------------------------------------------------- 1 | [external-file-2] == 2000; 2 | @import ./fixtures/external-file-3.gss; -------------------------------------------------------------------------------- /spec/fixtures/external-file-2.gss: -------------------------------------------------------------------------------- 1 | [external-file-2] == 2000; -------------------------------------------------------------------------------- /spec/fixtures/external-file-css1.gss: -------------------------------------------------------------------------------- 1 | button { 2 | width: == 100; 3 | z-index: 1; 4 | } 5 | -------------------------------------------------------------------------------- /spec/fixtures/external-file.gss: -------------------------------------------------------------------------------- 1 | 2 | [external-file] == 1000; 3 | 4 | -------------------------------------------------------------------------------- /spec/matrix.coffee: -------------------------------------------------------------------------------- 1 | 2 | expect = chai.expect 3 | assert = chai.assert 4 | 5 | 6 | describe 'Matrix', -> 7 | engine = null 8 | before -> 9 | container = document.createElement('div') 10 | engine = new GSS(container) 11 | engine.compile() 12 | 13 | describe 'dispatched by argument types', -> 14 | it 'should properly recognize matrix operations', -> 15 | expect(engine.output.Command(['translateX', 3])).to.be.an. 16 | instanceof(engine.output.Matrix.Transformation1) 17 | expect(engine.output.Command(['translateX', 3])).to.be.an. 18 | instanceof(engine.output.Matrix) 19 | 20 | expect(-> engine.output.Command(['translateX', 3, 3])).to. 21 | throw(/Too many/) 22 | 23 | expect(-> engine.output.Command(['translateX', 'a'])).to. 24 | throw(/Unexpected argument/) 25 | 26 | describe 'when executed', -> 27 | describe 'independently', -> 28 | it 'should initialize matrix', -> 29 | rotated = engine.output.Matrix::_mat4.create() 30 | rotated = engine.output.Matrix::_mat4.rotateZ(rotated, rotated, 180 * (Math.PI / 180)) 31 | rotate = ['rotateZ', 0.5] 32 | expect(engine.output.Command(rotate).solve(engine.output, rotate)). 33 | to.eql rotated 34 | 35 | describe 'as nested commands', -> 36 | it 'should return final matrix', -> 37 | rotated = engine.output.Matrix::_mat4.create() 38 | rotated = engine.output.Matrix::_mat4.rotateY(rotated, rotated, - 18 * (Math.PI / 180)) 39 | rotated = engine.output.Matrix::_mat4.rotateZ(rotated, rotated, 180 * (Math.PI / 180)) 40 | rotate = ['rotateZ', ['rotateY', -0.05], 0.5] 41 | expect(engine.output.Command(rotate).solve(engine.output, rotate)). 42 | to.eql rotated 43 | 44 | describe 'as flat commands', -> 45 | it 'should return final matrix', -> 46 | rotated = engine.output.Matrix::_mat4.create() 47 | rotated = engine.output.Matrix::_mat4.rotateY(rotated, rotated, - 18 * (Math.PI / 180)) 48 | rotated = engine.output.Matrix::_mat4.rotateZ(rotated, rotated, 180 * (Math.PI / 180)) 49 | rotate = [['rotateY', -0.05], ['rotateZ', 0.5]] 50 | expect(engine.output.Command(rotate).solve(engine.output, rotate)). 51 | to.eql rotated 52 | 53 | describe 'defined as a sequence of matrix operations', -> 54 | it 'should group together', -> 55 | sequence = [ 56 | ['translateX', 3], 57 | ['rotateZ', 2] 58 | ] 59 | expect(engine.output.Command(sequence)).to.be.an. 60 | instanceof(engine.output.Matrix::Sequence) 61 | expect(engine.output.Command(['translateX', 3])).to.eql(sequence[0].command) 62 | expect(engine.output.Command(['rotateZ', 3])).to.not.eql(sequence[1].command) 63 | expect(engine.output.Command(['rotateZ', ['translateX', 3], 3])).to.eql(sequence[1].command) 64 | expect(-> 65 | engine.output.Command([ 66 | 1, 67 | ['rotateZ', 2] 68 | ]) 69 | ).to.throw(/Undefined/) 70 | 71 | xdescribe 'when used with variables', -> 72 | it 'should update and recompute matrix', (done) -> 73 | expect(engine.solve([ 74 | ['translateX', 3] 75 | ['rotateZ', ['get', 'a']] 76 | ])).to.eql(1) 77 | -------------------------------------------------------------------------------- /spec/pages.coffee: -------------------------------------------------------------------------------- 1 | 2 | assert = chai.assert 3 | expect = chai.expect 4 | 5 | remove = (el) -> 6 | el?.parentNode?.removeChild(el) 7 | 8 | 9 | describe 'Standalone page tests', -> 10 | engine = container = iframe = null 11 | 12 | afterEach -> 13 | #remove(iframe) 14 | 15 | beforeEach -> 16 | iframe = document.createElement('iframe') 17 | document.body.appendChild(iframe) 18 | 19 | @timeout 100000 20 | 21 | describe 'Grid website', -> 22 | describe 'Virtuals demo', -> 23 | 24 | it 'should reorient', (done) -> 25 | i = 0 26 | listener = (e) -> 27 | if (e.origin == location.origin) 28 | 29 | expect() 30 | 31 | 32 | window.removeEventListener('message', listener) 33 | done() 34 | 35 | window.addEventListener('message', listener) 36 | 37 | iframe.width = 1024 38 | iframe.height = 768 39 | iframe.src = './pages/virtuals.html?log=0.5' 40 | 41 | 42 | 43 | describe 'Head cta section', -> 44 | 45 | it 'should reorient', (done) -> 46 | i = 0 47 | 48 | window.addEventListener('message', (e) -> 49 | if (e.origin == location.origin) 50 | 51 | expect() 52 | ) 53 | 54 | iframe.width = 1024 55 | iframe.height = 768 56 | iframe.src = './pages/grid_head_cta.html?log=0.5' 57 | 58 | 59 | describe 'Team section', -> 60 | 61 | it 'should reorient', (done) -> 62 | i = 0 63 | 64 | window.addEventListener('message', (e) -> 65 | if (e.origin == location.origin) 66 | i++ 67 | if i == 8 68 | return done() 69 | if i % 4 == 1 70 | expect(Math.floor e.data['$dan_tocchini[y]']).to.eql 228 71 | expect(Math.floor e.data['$dan_tocchini[x]']).to.eql 368 72 | expect(Math.floor e.data['$dan_tocchini[width]']).to.eql 288 73 | expect(Math.floor(e.data['$yaroslaff_fedin[y]'])).to.eql 1632 74 | expect(Math.floor(e.data['$yaroslaff_fedin[x]'])).to.eql 284 75 | expect(Math.floor(e.data['$yaroslaff_fedin[width]'])).to.eql 216 76 | expect(Math.floor e.data['$lost_cosmonaut[y]']).to.eql 2261 77 | expect(Math.floor e.data['$lost_cosmonaut[x]']).to.eql 642 78 | expect(Math.floor e.data['$lost_cosmonaut[width]']).to.eql 216 79 | iframe.width = 768 80 | else if i % 4 == 2 81 | expect(Math.floor e.data['$dan_tocchini[y]']).to.eql 0 82 | expect(Math.floor e.data['$dan_tocchini[x]']).to.eql 768 83 | expect(Math.floor e.data['$dan_tocchini[width]']).to.eql 768 84 | expect(Math.floor(e.data['$yaroslaff_fedin[y]'])).to.eql 0 85 | expect(Math.floor(e.data['$yaroslaff_fedin[x]'])).to.eql 6144 86 | expect(Math.floor(e.data['$yaroslaff_fedin[width]'])).to.eql 768 87 | expect(Math.floor e.data['$lost_cosmonaut[y]']).to.eql 0 88 | expect(Math.floor e.data['$lost_cosmonaut[x]']).to.eql 9983 89 | expect(Math.floor e.data['$lost_cosmonaut[width]']).to.eql 768 90 | iframe.width = 1024 91 | else if i % 4 == 3 92 | expect(Math.floor e.data['$dan_tocchini[y]']).to.eql 228 93 | expect(Math.floor e.data['$dan_tocchini[x]']).to.eql 368 94 | expect(Math.floor e.data['$dan_tocchini[width]']).to.eql 288 95 | expect(Math.floor(e.data['$yaroslaff_fedin[y]'])).to.eql 1632 96 | expect(Math.floor(e.data['$yaroslaff_fedin[x]'])).to.eql 284 97 | expect(Math.floor(e.data['$yaroslaff_fedin[width]'])).to.eql 216 98 | expect(Math.floor e.data['$lost_cosmonaut[y]']).to.eql 2261 99 | expect(Math.floor e.data['$lost_cosmonaut[x]']).to.eql 642 100 | expect(Math.floor e.data['$lost_cosmonaut[width]']).to.eql 216 101 | iframe.width = 320 102 | else 103 | expect(Math.floor e.data['$dan_tocchini[y]']).to.eql 218 104 | expect(Math.floor e.data['$dan_tocchini[x]']).to.eql 320 105 | expect(Math.floor e.data['$dan_tocchini[width]']).to.eql 320 106 | expect(Math.floor(e.data['$yaroslaff_fedin[y]'])).to.eql 218 107 | expect(Math.floor(e.data['$yaroslaff_fedin[x]'])).to.eql 2560 108 | expect(Math.floor(e.data['$yaroslaff_fedin[width]'])).to.eql 320 109 | expect(Math.floor e.data['$lost_cosmonaut[y]']).to.eql 218 110 | expect(Math.floor e.data['$lost_cosmonaut[x]']).to.eql 4160 111 | expect(Math.floor e.data['$lost_cosmonaut[width]']).to.eql 320 112 | iframe.width = 1024 113 | ) 114 | 115 | iframe.width = 1024 116 | iframe.height = 768 117 | iframe.src = './pages/grid_team.html?log=0.5' 118 | 119 | 120 | -------------------------------------------------------------------------------- /spec/pages/grandrop.html: -------------------------------------------------------------------------------- 1 | When gss processed commands, it generates variables for every property. Element with id #my-drag will have it's top/left location be stored in variables $my-drag[x] and $my-drag[y].Those are absolute values coming from top/left of the document. GSS will takes care of parent offsets automatically when it applies values. 2 | 3 | Applying a linear constraint will produce default values, that you can redefine via engine.solve(). 4 | 5 |
123
6 | 7 | 23 | 24 | 25 | 53 | 54 | -------------------------------------------------------------------------------- /spec/pages/grid_banner_cta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31 | 32 | 160 | 161 | 367 | 368 | 398 | 399 | -------------------------------------------------------------------------------- /spec/pages/grid_head_cta.html: -------------------------------------------------------------------------------- 1 | 3 | 82 | 83 | 84 | 114 | 115 | 116 | 293 | 294 |

Join the Evolution

AI Websites That Design Themselves

watch the video
-------------------------------------------------------------------------------- /spec/pages/grid_multiple_media.html: -------------------------------------------------------------------------------- 1 | 3 | 44 | 45 | 46 | 76 | 77 | 78 | 286 | 287 |

A Site As Colorful As You

Intelligent Color Detection & Correction

Our algorithms expertly analyze your media and apply color palettes that keep your messaging consistent and unique. The Grid also detects color contrasts, automatically adjusting typography color to maximize legibility.

-------------------------------------------------------------------------------- /spec/pages/grid_post_simple.html: -------------------------------------------------------------------------------- 1 | 2 | 260 | 261 | 321 | 322 |

The Right Shot Every Time

Intelligent Face Detection & Smart Cropping

Why waste time cropping by hand? The Grid automatically detects faces in your photos and crops images to fit any size on any display. All you have to do is find the perfect photo.

On-Demand E-Commerce

Add products and a shopping cart magically appears, no plugins or configuration required. Remove the products and the shopping cart disappears. No explanations necessary.

375 | 376 | 377 | 378 | 408 | 409 | -------------------------------------------------------------------------------- /spec/pages/profile_card.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 87 | 196 |
197 |
198 |
199 |
200 |

Dan Daniels

201 | 202 | 203 | 204 | 205 | 206 | 207 | 214 | -------------------------------------------------------------------------------- /spec/pages/virtuals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 | 24 | 47 | 48 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /spec/perf.coffee: -------------------------------------------------------------------------------- 1 | Engine = GSS.Engine #require 'gss-engine/lib/Engine.js' 2 | 3 | remove = (el) -> 4 | el.parentNode.removeChild(el) 5 | 6 | stringify = JSON.stringify 7 | stringify = (o) -> o 8 | expect = chai.expect 9 | assert = chai.assert 10 | 11 | describe 'Perf', -> 12 | ['with worker', 'without worker'].forEach (title, i) -> 13 | describe title, -> 14 | scope = null 15 | engine = null 16 | 17 | beforeEach -> 18 | fixtures = document.getElementById 'fixtures' 19 | scope = document.createElement 'div' 20 | fixtures.appendChild scope 21 | engine = new GSS(scope, i == 0) 22 | 23 | afterEach (done) -> 24 | remove(scope) 25 | engine.destroy() 26 | done() 27 | 28 | @timeout 15000 29 | 30 | 31 | describe 'live command perfs1', -> 32 | 33 | it '100 at once', (done) -> 34 | 35 | innerHTML = "" 36 | for i in [0...100] 37 | innerHTML += "
One
" 38 | scope.innerHTML = innerHTML 39 | #GSS.console.profile(123) 40 | 41 | engine.once 'solve', -> 42 | scope.innerHTML = "" 43 | engine.then -> 44 | done() 45 | 46 | engine.solve [ 47 | ['==', ['get', ['.','box'], 'width'], ['get', ['.','box'],'x']] 48 | ] 49 | 50 | 51 | it '100 intrinsics at once', (done) -> 52 | 53 | innerHTML = "" 54 | for i in [0...100] 55 | innerHTML += "
One
" 56 | scope.innerHTML = innerHTML 57 | 58 | engine.once 'solve', -> 59 | scope.innerHTML = "" 60 | engine.then -> 61 | done() 62 | 63 | engine.solve [ 64 | ['==', ['get', ['.','box'], 'width'], ['get', ['.','box'], 'intrinsic-width']] 65 | ] 66 | 67 | 68 | 69 | it '100 serially', (done) -> 70 | scope.innerHTML = "" 71 | 72 | 73 | count = 1 74 | 75 | # first one here otherwise, nothing to solve 76 | scope.insertAdjacentHTML 'beforeend', """ 77 |
One
78 | """ 79 | GSS.console.profile('100 serially') 80 | listener = (e) -> 81 | count++ 82 | #GSS.console.error(count) 83 | if count is 100 84 | engine.removeEventListener 'solve', listener 85 | GSS.console.profileEnd('100 serially') 86 | scope.innerHTML = "" 87 | engine.then -> 88 | done() 89 | else 90 | scope.insertAdjacentHTML 'beforeend', """ 91 |
One
92 | """ 93 | 94 | engine.addEventListener 'solve', listener 95 | 96 | 97 | engine.solve [ 98 | ['==', ['get', ['.','box'], 'width'], ['get', ['.','box'],'x']] 99 | ] 100 | 101 | it '100 intrinsics serially', (done) -> 102 | scope.innerHTML = "" 103 | 104 | count = 1 105 | 106 | # first one here otherwise, nothing to solve 107 | scope.insertAdjacentHTML 'beforeend', """ 108 |
One
109 | """ 110 | GSS.console.profile('100 intrinsics serially') 111 | listener = (e) -> 112 | count++ 113 | scope.insertAdjacentHTML 'beforeend', """ 114 |
One
115 | """ 116 | if count is 100 117 | engine.removeEventListener 'solve', listener 118 | GSS.console.profileEnd('100 intrinsics serially') 119 | scope.innerHTML = "" 120 | engine.then -> 121 | done() 122 | 123 | engine.addEventListener 'solve', listener 124 | 125 | engine.solve [ 126 | ['==', ['get', ['.','box'], 'width'], ['get', ['.','box'], 'intrinsic-width']] 127 | ] 128 | -------------------------------------------------------------------------------- /spec/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GSS engine in browser 6 | 7 | 12 | 13 | 14 | 15 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 30 | 39 | 40 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /spec/selectors.coffee: -------------------------------------------------------------------------------- 1 | 2 | expect = chai.expect 3 | assert = chai.assert 4 | 5 | describe 'Selectors', -> 6 | engine = null 7 | before -> 8 | container = document.createElement('div') 9 | engine = new GSS(container) 10 | engine.compile() 11 | 12 | describe 'dispatched by argument types', -> 13 | it 'should create command instance for each operation', -> 14 | expect(engine.input.Command(['tag', 'div'])).to.be.an.instanceof(engine.input.Selector.Selecter) 15 | expect(engine.input.Command(['tag', 'div'])).to.be.an.instanceof(engine.input.Selector) 16 | expect(engine.input.Command(['tag', 'div'])).to.be.an.instanceof(engine.input.Selector) 17 | expect(engine.input.Command(['tag', ['tag', 'div'], 'div'])).to.be.an.instanceof(engine.input.Selector.Qualifier) 18 | 19 | it 'should have absolute and relative path set for each command', -> 20 | expect(engine.input.Command(['tag', 'div']).path).to.eql('div') 21 | expect(engine.input.Command(['tag', 'h1']).key).to.eql('h1') 22 | expect(engine.input.Command(['tag', ['&'], 'h1']).path).to.eql('&h1') 23 | 24 | expect(engine.input.Command(['.', ['tag', 'p'], 'active']).path).to.eql('p.active') 25 | expect(engine.input.Command(['.', ['tag', 'p'], 'active']).key).to.eql('.active') 26 | 27 | expect(engine.input.Command(['tag', ['+', ['.', ['tag', 'p'], 'active']], 'em']).path).to.eql('p.active+em') 28 | 29 | expect(engine.input.Command(['[*=]', ['+', ['.', ['tag', 'p'], 'active']], 'em', 'v']).path).to.eql('p.active+[em*="v"]') 30 | 31 | it 'should group commands by tags', -> 32 | expect(engine.input.Command(['#', [' ', ['.', ['tag', ['>'], 'p'], 'active']], 'button']).selector).to.eql(' #button') 33 | expect(engine.input.Command(['#', [' ', ['.', ['tag', ['>'], 'p'], 'active']], 'button']).path).to.eql('>p.active #button') 34 | expect(engine.input.Command(['.', ['tag', 'p'], 'active']).selector).to.eql('p.active') 35 | expect(engine.input.Command(['.', ['tag', ['~~'], 'p'], 'active']).path).to.eql('~~p.active') 36 | expect(engine.input.Command(['.', ['tag', ['~~'], 'p'], 'active']).selector).to.eql('p.active') 37 | expect(engine.input.Command(['!~', ['.', ['tag', ['~~'], 'p'], 'active']]).selector).to.eql(undefined) 38 | expect(engine.input.Command(['!~', ['.', ['tag', ['~~'], 'p'], 'active']]).path).to.eql('~~p.active!~') 39 | expect(engine.input.Command(['.', ['tag', [' ', ['~~']], 'p'], 'active']).head.command.path).to.eql('~~ p.active') 40 | expect(engine.input.Command(['.', ['tag', [' ', ['~~']], 'p'], 'active']).tail.command.path).to.eql('~~ ') 41 | expect(engine.input.Command(['.', ['tag', [' ', ['~~']], 'p'], 'active']).selector).to.eql(' p.active') 42 | 43 | it 'should group elements in comma', -> 44 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active']]).path).to.eql('p.active') 45 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active']]).selector).to.eql('p.active') 46 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active']]).tail.command.path).to.eql('p') 47 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active']]).head.command.path).to.eql('p.active') 48 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active'], ['tag', 'p']]).selector).to.eql('p.active,p') 49 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active'], ['tag', 'p']]).path).to.eql('p.active,p') 50 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active'], ['~~', ['tag', 'p']]]).path).to.eql('p.active,p~~') 51 | expect(engine.input.Command([',', ['.', ['tag', 'p'], 'active'], ['~~', ['tag', 'p']]]).selector).to.eql(undefined) 52 | 53 | expect(engine.input.Command([',', ['.', ['~~'], 'active'], ['.', ['++'], 'active']]).selector).to.eql(undefined) 54 | expect(engine.input.Command([',', ['.', 'a'], ['.', [' ', ['$']], 'b']]).selector).to.eql(undefined) 55 | expect(engine.input.Command([',', ['.', 'a'], ['.', ['$'], 'b']]).selector).to.eql(undefined) -------------------------------------------------------------------------------- /spec/signatures.coffee: -------------------------------------------------------------------------------- 1 | expect = chai.expect 2 | assert = chai.assert 3 | 4 | 5 | 6 | describe 'Signatures', -> 7 | 8 | 9 | 10 | 11 | 12 | 13 | engine = null 14 | describe 'dispatched by argument types', -> 15 | PrimitiveCommand = GSS.Engine::Command.extend { 16 | signature: [ 17 | left: ['String', 'Variable'] 18 | right: ['Number'] 19 | ] 20 | }, { 21 | 'primitive': () -> 22 | } 23 | 24 | before -> 25 | engine = new GSS 26 | engine.input.PrimitiveCommand = PrimitiveCommand 27 | engine.compile() 28 | 29 | describe 'with primitive', -> 30 | it 'should match property function definition', -> 31 | expect(engine.input.Command(['primitive', 'test'])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 32 | expect(engine.input.Command(['primitive', 'test', 10])).to.be.an.instanceof(PrimitiveCommand.primitive) 33 | expect(engine.input.Command(['primitive', 'test', 'test'])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 34 | expect(engine.input.Command(['undeclared', 'test', 10])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 35 | expect(engine.input.Command(['undeclared', 'test', 'test'])).to.be.an.instanceof(engine.input.Default) 36 | 37 | describe 'with variables', -> 38 | it 'should match property function definition', -> 39 | expect(engine.input.Command(['primitive', ['get', 'test']])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 40 | expect(engine.input.Command(['primitive', ['get', 'test'], 10])).to.be.an.instanceof(PrimitiveCommand.primitive) 41 | expect(engine.input.Command(['primitive', ['get', 'test'], 'test'])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 42 | expect(engine.input.Command(['primitive', ['get', 'test'], ['get', 'test']])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 43 | expect(engine.input.Command(['undeclared', ['get', 'test'], 10])).to.be.an.instanceof(engine.input.Default) 44 | 45 | describe 'with expressions', -> 46 | it 'should match property function definition', -> 47 | expect(engine.input.Command(['primitive', ['+', ['get', 'test'], 1]])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 48 | expect(engine.input.Command(['primitive', ['+', ['get', 'test'], 1], 10])).to.be.an.instanceof(PrimitiveCommand.primitive) 49 | expect(engine.input.Command(['primitive', ['+', ['get', 'test'], 1], 'test'])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 50 | expect(engine.input.Command(['primitive', ['+', ['get', 'test'], 1], ['+', ['get', 'test'], 1]])).to.not.be.an.instanceof(PrimitiveCommand.primitive) 51 | expect(engine.input.Command(['undeclared', ['+', ['get', 'test'], 1], 10])).to.be.an.instanceof(engine.input.Default) 52 | 53 | 54 | 55 | describe 'dispatched with optional arguments', -> 56 | UnorderedCommand = GSS.Engine::Command.extend { 57 | signature: [[ 58 | left: ['String', 'Variable'] 59 | right: ['Number'] 60 | mode: ['Number'] 61 | ]] 62 | }, { 63 | 'unordered': () -> 64 | } 65 | 66 | before -> 67 | engine = new GSS 68 | engine.input.UnorderedCommand = UnorderedCommand 69 | 70 | engine.compile() 71 | 72 | describe 'and no required arguments', -> 73 | it 'should match property function definition', -> 74 | expect(engine.input.Command(['unordered', 'test'])).to.be.an.instanceof(UnorderedCommand.unordered) 75 | expect(engine.input.Command(['unordered', 'test', 10])).to.be.an.instanceof(UnorderedCommand.unordered) 76 | expect(engine.input.Command(['unordered', 'test', 10, 20])).to.be.an.instanceof(UnorderedCommand.unordered) 77 | expect(engine.input.Command(['unordered', 10, 'test', 20])).to.be.an.instanceof(UnorderedCommand.unordered) 78 | expect(engine.input.Command(['unordered', 10, 20, 'test'])).to.be.an.instanceof(UnorderedCommand.unordered) 79 | expect(engine.input.Command(['unordered', 10, 20, 'test', 30])).to.not.be.an.instanceof(UnorderedCommand.unordered) 80 | expect(engine.input.Command(['unordered', 'test', 'test'])).to.not.be.an.instanceof(UnorderedCommand.unordered) 81 | 82 | describe 'with variables', -> 83 | it 'should match property function definition', -> 84 | expect(engine.input.Command(['unordered', ['get', 'test']])).to.be.an.instanceof(UnorderedCommand.unordered) 85 | expect(engine.input.Command(['unordered', ['get', 'test'], 10])).to.be.an.instanceof(UnorderedCommand.unordered) 86 | expect(engine.input.Command(['unordered', ['get', 'test'], 'test'])).to.not.be.an.instanceof(UnorderedCommand.unordered) 87 | expect(engine.input.Command(['unordered', ['get', 'test'], ['get', 'test']])).to.not.be.an.instanceof(UnorderedCommand.unordered) 88 | expect(engine.input.Command(['undeclared', ['get', 'test'], 10])).to.be.an.instanceof(engine.input.Default) 89 | 90 | describe 'with expressions', -> 91 | it 'should match property function definition', -> 92 | expect(engine.input.Command(['unordered', ['+', ['get', 'test'], 1]])).to.be.an.instanceof(UnorderedCommand.unordered) 93 | expect(engine.input.Command(['unordered', ['+', ['get', 'test'], 1], 10])).to.be.an.instanceof(UnorderedCommand.unordered) 94 | expect(engine.input.Command(['unordered', ['+', ['get', 'test'], 1], 'test'])).to.not.be.an.instanceof(UnorderedCommand.unordered) 95 | expect(engine.input.Command(['unordered', ['+', ['get', 'test'], 1], ['+', ['get', 'test'], 1]])).to.not.be.an.instanceof(UnorderedCommand.unordered) 96 | expect(engine.input.Command(['undeclared', ['+', ['get', 'test'], 1], 10])).to.be.an.instanceof(engine.input.Default) 97 | 98 | describe 'optional group with order specific type declaration', -> 99 | before -> 100 | engine = new GSS 101 | engine.input.FancyTypes = GSS.Engine::Command.extend { 102 | signature: [[ 103 | left: ['String', 'Variable'] 104 | right: ['Number', 'String'] 105 | mode: ['Number', 'Variable'] 106 | ]] 107 | }, { 108 | 'fancy': () -> 109 | } 110 | engine.compile() 111 | 112 | 113 | it 'should respect type order', -> 114 | expect(engine.input.Command(['fancy', 'test']).permutation).to.eql([0]) 115 | expect(engine.input.Command(['fancy', 'test', 'test']).permutation).to.eql([0, 1]) 116 | expect(engine.input.Command(['fancy', 1]).permutation).to.eql([1]) 117 | expect(engine.input.Command(['fancy', 1, 1]).permutation).to.eql([1, 2]) 118 | expect(engine.input.Command(['fancy', 1, 'a']).permutation).to.eql([1, 0]) 119 | expect(engine.input.Command(['fancy', 1, 'a', 1]).permutation).to.eql([1, 0, 2]) 120 | #expect(engine.input.Command(['fancy', 1, 'a', 'b']).permutation).to.eql(undefined) 121 | expect(engine.input.Command(['fancy', 'a', 1]).permutation).to.eql([0, 1]) 122 | expect(engine.input.Command(['fancy', 'a', 1, 2]).permutation).to.eql([0, 1, 2]) 123 | 124 | describe 'optional groups and mixed with optional groups', -> 125 | OptionalGroupCommand = GSS.Engine::Command.extend { 126 | signature: [ 127 | left: ['Variable', 'String'] 128 | [ 129 | a: ['String'] 130 | b: ['Number'] 131 | ] 132 | right: ['Number'] 133 | [ 134 | c: ['Number'] 135 | ] 136 | ] 137 | }, { 138 | 'optional': () -> 139 | } 140 | 141 | before -> 142 | engine = new GSS 143 | engine.input.OptionalGroupCommand = OptionalGroupCommand 144 | engine.compile() 145 | 146 | describe 'and no required arguments', -> 147 | it 'should match property function definition', -> 148 | expect(engine.input.Command(['optional', 'test'])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 149 | expect(engine.input.Command(['optional', 'test', 10])).to.be.an.instanceof(OptionalGroupCommand.optional) 150 | expect(engine.input.Command(['optional', 'test', 10, 20])).to.be.an.instanceof(OptionalGroupCommand.optional) 151 | expect(engine.input.Command(['optional', 'test', 10, 'test', 20])).to.be.an.instanceof(OptionalGroupCommand.optional) 152 | expect(engine.input.Command(['optional', 'test', 10, 20, 'test'])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 153 | expect(engine.input.Command(['optional', 'test', 10, 'test', 20, 30])).to.be.an.instanceof(OptionalGroupCommand.optional) 154 | expect(engine.input.Command(['optional', 'test', 'test'])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 155 | expect(engine.input.Command(['optional', 'test', 10, 'test', 20, 30]).permutation).to.eql([0,2,1,3,4]) 156 | expect(engine.input.Command(['optional', 'test', 10, 'test', 20]).permutation).to.eql([0,2,1,3]) 157 | expect(engine.input.Command(['optional', 'test', 10, 20]).permutation).to.eql([0,2,3]) 158 | 159 | describe 'with variables', -> 160 | it 'should match property function definition', -> 161 | expect(engine.input.Command(['optional', ['get', 'test']])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 162 | expect(engine.input.Command(['optional', ['get', 'test'], 10])).to.be.an.instanceof(OptionalGroupCommand.optional) 163 | expect(engine.input.Command(['optional', ['get', 'test'], 'test'])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 164 | expect(engine.input.Command(['optional', ['get', 'test'], ['get', 'test']])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 165 | expect(engine.input.Command(['undeclared', ['get', 'test'], 10])).to.be.an.instanceof(engine.input.Default) 166 | 167 | describe 'with expressions', -> 168 | it 'should match property function definition', -> 169 | expect(engine.input.Command(['optional', ['+', ['get', 'test'], 1]])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 170 | expect(engine.input.Command(['optional', ['+', ['get', 'test'], 1], 10])).to.be.an.instanceof(OptionalGroupCommand.optional) 171 | expect(engine.input.Command(['optional', ['+', ['get', 'test'], 1], 'test'])).to.not.be.an.instanceof(OptionalGroupCommand.optional) 172 | expect(engine.input.Command(['optional', ['+', ['get', 'test'], 1], 'test', 10])).to.be.an.instanceof(OptionalGroupCommand.optional) 173 | 174 | describe 'dispatched subclassed with dynamic condition', -> 175 | 176 | WrapperCommand = GSS.Engine::Command.extend { 177 | signature: [ 178 | left: ['DynamicCommand'] 179 | right: ['Number'] 180 | ] 181 | }, { 182 | 'wrapper': (a) -> 183 | return ['wrapper', a] 184 | } 185 | 186 | 187 | DynamicCommand = GSS.Engine::Command.extend { 188 | type: 'DynamicCommand' 189 | signature: [] 190 | }, { 191 | 'dynamic': (a) -> 192 | return [666] 193 | } 194 | 195 | DynamicCommand.Positive = DynamicCommand.extend { 196 | kind: 'auto' 197 | condition: (engine, operation) -> 198 | return operation.parent[2] > 0 199 | 200 | } 201 | 202 | DynamicCommand.Negative = DynamicCommand.extend { 203 | kind: 'auto' 204 | condition: (engine, operation) -> 205 | return operation.parent[2] < 0 206 | } 207 | 208 | before -> 209 | engine = new GSS 210 | engine.input.WrapperCommand = WrapperCommand 211 | engine.input.DynamicCommand = DynamicCommand 212 | engine.compile() 213 | 214 | it 'should dispatch command', -> 215 | engine.input.Command(cmd = ['wrapper', ['dynamic'], 0]) 216 | expect(cmd[1].command).to.be.an.instanceof DynamicCommand.dynamic 217 | engine.input.Command(cmd = ['wrapper', ['dynamic'], +1]) 218 | expect(cmd[1].command).to.be.an.instanceof DynamicCommand.Positive 219 | engine.input.Command(cmd = ['wrapper', ['dynamic'], -1]) 220 | expect(cmd[1].command).to.be.an.instanceof DynamicCommand.Negative 221 | 222 | 223 | 224 | describe 'dispatched with object as callee', -> 225 | ObjectCommand = GSS.Engine::Command.extend { 226 | signature: [ 227 | left: ['Variable', 'String'] 228 | [ 229 | c: ['Number'] 230 | ] 231 | ] 232 | }, { 233 | 'object': (a,b,c) -> 234 | return [a,b,c] 235 | } 236 | 237 | before -> 238 | engine = new GSS 239 | engine.input.ObjectCommand = ObjectCommand 240 | engine.compile() 241 | 242 | it 'should dispatch command', -> 243 | z = {title: 'God Object'} 244 | expect(engine.input.Command([z, 1, 'v'])).to.not.be.an.instanceof ObjectCommand.object 245 | expect(engine.input.Command([z, 'v', 1])).to.be.an.instanceof ObjectCommand.object 246 | 247 | 248 | -------------------------------------------------------------------------------- /spec/specs.coffee: -------------------------------------------------------------------------------- 1 | require("./domain") 2 | require("./cassowary") 3 | require("./end-to-end") 4 | require("./command-nested-rules") 5 | require("./thread") 6 | require("./selectors") 7 | require("./matrix") 8 | require("./conditions") 9 | require("./command") 10 | require("./engine") 11 | require("./perf") 12 | require("./stylesheet") 13 | require("./units") 14 | require("./signatures") 15 | require("./styles") 16 | require("./poly-test/full") 17 | -------------------------------------------------------------------------------- /spec/styles.coffee: -------------------------------------------------------------------------------- 1 | 2 | expect = chai.expect 3 | assert = chai.assert 4 | 5 | 6 | describe 'Styles', -> 7 | doc = engine = null 8 | before -> 9 | engine ||= new GSS(document.createElement('div')) 10 | engine.compile() 11 | doc = {} 12 | for property, value of engine.output.properties 13 | do (property, value) -> 14 | doc[property] = -> 15 | value.apply(engine.output, arguments) 16 | doc[property].initial = value.initial 17 | 18 | describe 'simple properties', -> 19 | 20 | it 'numeric property', -> 21 | expect(doc['z-index'](10)).to.eql(10) 22 | expect(doc['z-index'](10.5)).to.eql(undefined) 23 | expect(doc['z-index']('ff')).to.eql(undefined) 24 | 25 | it 'length & percentage', -> 26 | expect(doc['font-size'](10)).to.eql(10) 27 | expect(doc['font-size'](['em', 10])).to.eql(['em', 10]) 28 | expect(doc['font-size'](['%', 10])).to.eql(['%', 10]) 29 | expect(doc['font-size'](['s', 10])).to.eql(undefined) 30 | 31 | it 'keywords', -> 32 | expect(doc['float']('none')).to.eql('none') 33 | expect(doc['float']('left')).to.eql('left') 34 | expect(doc['float']('reft')).to.eql(undefined) 35 | 36 | describe 'shorthand with no specific order of properties', -> 37 | it 'should expand properties', -> 38 | expect(doc['background'](['rgb',1,1,1])).to.eql new doc['background'].initial 39 | 'background-color': ['rgb',1,1,1] 40 | expect(doc['background']('no-repeat', ['hsla',3,2,1,0])).to.eql new doc['background'].initial 41 | 'background-repeat': 'no-repeat' 42 | 'background-color': ['hsla',3,2,1,0] 43 | expect(doc['background']('no-repeat', ['hsla',3,2,1,0], 'no-repeat')).to.eql undefined 44 | expect(doc['background']('no-repeat', ['hsla',3,2,1,0], 2, 'right')).to.eql new doc['background'].initial 45 | 'background-repeat': 'no-repeat' 46 | 'background-color': ['hsla',3,2,1,0] 47 | 'background-position-y': 2 48 | 'background-position-x': 'right' 49 | 50 | expect(doc['background']('top', 'right', 'padding-box', 'border-box',['linear-gradient', ['to', 'top', 'right']])).to.eql new doc['background'].initial 51 | 'background-position-y': 'top' 52 | 'background-position-x': 'right' 53 | 'background-origin': 'padding-box' 54 | 'background-clip': 'border-box' 55 | 'background-image': ['linear-gradient', ['to', 'top', 'right']] 56 | 57 | describe 'unordered shorthand with multiple values', -> 58 | it 'should expand each value', -> 59 | 60 | expect(doc['background'](['no-repeat'], ['repeat'], 'transparent')).to.eql new doc['background'].initial 61 | 0: new doc['background'].initial 62 | 'background-repeat': 'no-repeat' 63 | 1: new doc['background'].initial 64 | 'background-repeat': 'repeat' 65 | 'background-color': 'transparent' 66 | expect(doc['background'](['no-repeat'], ['repeat'], 'transparent').format()).to. 67 | eql 'no-repeat, repeat transparent' 68 | expect(doc['background'](['no-repeat'], ['repeat'], 'transparent').format( 69 | "background-image-2": ['url', 'abc'] 70 | )).to.eql 'no-repeat, url(abc) repeat transparent' 71 | expect(doc['background'](['no-repeat'], ['repeat'], 'transparent').format( 72 | "background-repeat-2": "repeat-y" 73 | )).to.eql 'no-repeat, repeat-y transparent' 74 | expect(doc['background'](['no-repeat'], ['repeat'], 'transparent').format( 75 | "background-position-y-1": "top" 76 | )).to.eql 'top no-repeat, repeat transparent' 77 | 78 | describe 'ordered shorthand with multiple values', -> 79 | it 'should do things', -> 80 | expect(doc['box-shadow']([1,1,'transparent'], [2,2,['rgba', 1,1,1]])).to.eql new doc['box-shadow'].initial 81 | 0: new doc['box-shadow'].initial 82 | 'box-shadow-offset-x': 1 83 | 'box-shadow-offset-y': 1 84 | 'box-shadow-color': 'transparent' 85 | 1: new doc['box-shadow'].initial 86 | 'box-shadow-offset-x': 2 87 | 'box-shadow-offset-y': 2 88 | 'box-shadow-color': ['rgba', 1,1,1] 89 | 90 | expect(doc['box-shadow']([1,1,'transparent'], [2,2,['rgba', 1,1,1]]).format()).to. 91 | eql '1px 1px transparent, 2px 2px rgba(1,1,1)' 92 | 93 | expect(doc['box-shadow']([1,1,'transparent'], [2,2,['rgba', 1,1,1]]).format( 94 | 'box-shadow-offset-x-1': -1 95 | 'box-shadow-offset-y-2': -2 96 | )).to.eql '-1px 1px transparent, 2px -2px rgba(1,1,1)' 97 | 98 | expect(doc['box-shadow']([1,1,'transparent'], [2,2,['rgba', 1,1,1]]).format( 99 | 'box-shadow-blur-1': ['em', 1] 100 | 'box-shadow-spread-2': ['cm', 2] # adds default blur value 101 | )).to.eql '1px 1px 1em transparent, 2px 2px 0 2cm rgba(1,1,1)' 102 | 103 | describe 'ordered shorthands', -> 104 | it 'should validate and expand value', -> 105 | expect(doc['border-top'](1, 'solid', 'transparent')).to.eql new doc['border-top'].initial 106 | 'border-top-width': 1 107 | 'border-top-style': 'solid' 108 | 'border-top-color': 'transparent' 109 | expect(doc['border-left'](1, 'solid', 'tranceparent')).to.eql undefined 110 | expect(doc['border-left'](1, 'zolid', ['rgb', 1,1,1])).to.eql undefined 111 | expect(doc['border-left'](['rgb', 1,1,1], ['em', 2])).to.eql new doc['border-left'].initial 112 | 'border-left-color': ['rgb', 1,1,1] 113 | 'border-left-width': ['em', 2] 114 | 115 | describe 'dimensional shorthands', -> 116 | it 'should validate, pad and expand values', -> 117 | expect(doc['margin'](1)).to.eql new doc['margin'].initial 118 | 'margin-top': 1, 119 | 'margin-right': 1 120 | 'margin-bottom': 1 121 | 'margin-left': 1 122 | expect(doc['padding'](1, ['cm', 2])).to.eql new doc['padding'].initial 123 | 'padding-top': 1, 124 | 'padding-right': ['cm', 2] 125 | 'padding-bottom': 1 126 | 'padding-left': ['cm', 2] 127 | expect(doc['border-width'](1, ['cm', 2], ['vh', 3])).to.eql new doc['border-width'].initial 128 | 'border-top-width': 1, 129 | 'border-right-width': ['cm', 2] 130 | 'border-bottom-width': ['vh', 3] 131 | 'border-left-width': ['cm', 2] 132 | expect(doc['border-style']('solid', 'dotted', 'double', 'ridge')).to.eql new doc['border-style'].initial 133 | 'border-top-style': 'solid', 134 | 'border-right-style': 'dotted' 135 | 'border-bottom-style': 'double' 136 | 'border-left-style': 'ridge' 137 | describe 'corner shorthands', -> 138 | it 'should validate, pad and expand values', -> 139 | expect(doc['border-radius'](1)).to.eql new doc['border-radius'].initial 140 | "border-top-left-radius": 1, 141 | "border-top-right-radius": 1 142 | "border-bottom-left-radius": 1 143 | "border-bottom-right-radius": 1 144 | expect(doc['border-radius'](1, 2)).to.eql new doc['border-radius'].initial 145 | "border-top-left-radius": 1, 146 | "border-top-right-radius": 2 147 | "border-bottom-left-radius": 1 148 | "border-bottom-right-radius": 2 149 | 150 | xdescribe 'transformations', -> 151 | it 'should generate matrix', -> 152 | engine.solve [ 153 | ['rotateX', ['deg', 10]] 154 | ['scaleZ', 2] 155 | ['translateY', -2] 156 | ] 157 | expect(doc['transform']()).to.eql 123 158 | -------------------------------------------------------------------------------- /spec/thread.coffee: -------------------------------------------------------------------------------- 1 | 2 | expect = chai.expect 3 | assert = chai.assert 4 | 5 | describe 'Cassowary Thread', -> 6 | it 'should instantiate', -> 7 | thread = new GSS 8 | it '[x]==7; [y]==5; [x] - [y] == [z] // z is 2', (done) -> 9 | thread = new GSS 10 | thread.solve [ 11 | ['==', 12 | ['get', 'z'], 13 | ['-', ['get', 'x'], ['get', 'y'] ] 14 | ] 15 | ['==', ['get', 'x'], 7] 16 | ['==', ['get', 'y'], 5] 17 | ] 18 | chai.expect(thread.values).to.eql 19 | x: 7 20 | y: 5 21 | z: 2 22 | done() 23 | 24 | it 'hierarchy', (done) -> 25 | thread = new GSS 26 | thread.solve [ 27 | ['==', ['get','x'],100,'strong'] 28 | ['==', ['get','x'],10,'medium'] 29 | ['==', ['get','x'],1,'weak'] 30 | ['==', ['get','y'],1,'weak'] 31 | ['==', ['get','y'],10,'medium'] 32 | ['==', ['get','y'],101,'strong'] 33 | ] 34 | chai.expect(thread.values).to.eql 35 | "x": 100 36 | "y": 101 37 | done() 38 | 39 | it 'order of operations', (done) -> 40 | thread = new GSS 41 | thread.solve [ 42 | ['==', ['get','w'], 100,'required'] 43 | ['==', ['get','igap'], 3,'required'] 44 | ['==', ['get','ogap'], 20,'required'] 45 | ['==', ['get','md'], ['/',['-',['get','w'],['*',['get','ogap'],2]], 4],'required'] 46 | ['==', ['get','span3'], ['+',['*',['get','md'],3],['*',['get','igap'],2]],'required'] 47 | ] 48 | chai.expect(thread.values).to.eql 49 | "w": 100 50 | "igap": 3 51 | "ogap": 20 52 | "md": 15 53 | "span3": 51 54 | done() 55 | 56 | it '$12322[width] == [grid-col]; ...', (done) -> 57 | thread = new GSS 58 | thread.solve [ 59 | ['==', ['get','$12322[width]'],['get','grid-col']] 60 | ['==', ['get','$34222[width]'],['get','grid-col']] 61 | ['==', 100,['get','grid-col']] 62 | ] 63 | chai.expect(thread.values).to.eql 64 | "$12322[width]": 100 65 | "$34222[width]": 100 66 | "grid-col": 100 67 | done() 68 | 69 | it 'Serial Suggests with plus expression', (done) -> 70 | thread = new GSS 71 | pad: 1 72 | thread.solve [ 73 | ['==', ['+',['get','target-width'],['get','pad']], ['get','actual-width']] 74 | ['==', ['get','target-width'],100] 75 | ] 76 | chai.expect(thread.values).to.eql 77 | "target-width": 100 78 | "actual-width": 101 79 | pad: 1 80 | 81 | thread.solve 82 | pad: 2 83 | chai.expect(thread.updated.solution).to.eql 84 | "pad": 2 85 | "actual-width": 102 86 | thread.solve 87 | pad: 4 88 | chai.expect(thread.updated.solution).to.eql 89 | "pad": 4 90 | "actual-width": 104 91 | done() 92 | 93 | 94 | it 'intrinsic mock', (done) -> 95 | thread = new GSS 96 | 'intrinsic-width': 999 97 | 98 | thread.solve [ 99 | ['==', ['get','width'],100, 'weak'] 100 | ['==', ['get','width'],['get','intrinsic-width'], 'require'] 101 | ] 102 | chai.expect(thread.values).to.eql 103 | 'intrinsic-width': 999 104 | "width": 999 105 | done() 106 | 107 | 108 | it 'intrinsic var is immutable with suggestion', () -> 109 | #c.trace = true 110 | thread = new GSS 111 | 'intrinsic-width': 100 112 | thread.solve [ 113 | ['==', ['get','hgap'], 20, 'required'] 114 | ['==', ['get','width'],['+',['get','intrinsic-width'],['get','hgap']],'required'] 115 | ['==', ['get','width'], 20, 'strong'] 116 | ] 117 | chai.expect(thread.values).to.eql 118 | "width": 120 119 | "hgap": 20 120 | 'intrinsic-width': 100 121 | #done() 122 | 123 | it 'tracking & removing by get tracker', (done) -> 124 | thread = new GSS() 125 | thread.solve [ 126 | ['==', ['get', 'x'],100,'strong'] 127 | ], 'x-tracker' 128 | thread.solve [ 129 | ['==', ['get','x'],10,'weak'] 130 | ] 131 | chai.expect(thread.values).to.eql 132 | "x": 100 133 | 134 | thread.solve ['remove', 'x-tracker'] 135 | chai.expect(thread.values).to.eql 136 | "x": 10 137 | done() 138 | 139 | 140 | # DOM Prop Helpers 141 | # --------------------------------------------------------------------- 142 | 143 | describe 'dom prop helpers', -> 144 | 145 | it 'varexp - right', () -> 146 | thread = new GSS() 147 | thread.solve [ 148 | ['==', ['get','$112[x]'],10] 149 | ['==', ['get','$112', 'right'],100] 150 | ] 151 | expect(thread.values).to.eql 152 | "$112[x]": 10 153 | "$112[width]": 90 154 | 155 | it 'varexp - center-x', () -> 156 | thread = new GSS() 157 | thread.solve [ 158 | ['==', ['get', '$112[x]'],10] 159 | ['==', ['get','$112', 'center-x'],110] 160 | ] 161 | expect(thread.values).to.eql 162 | "$112[x]": 10 163 | "$112[width]": 200 164 | 165 | it 'varexp - bottom', () -> 166 | thread = new GSS() 167 | thread.solve [ 168 | ['==', ['get','$112[height]'],10] 169 | ['==', ['get','$112', 'bottom'],100] 170 | ] 171 | expect(thread.values).to.eql 172 | "$112[height]": 10 173 | "$112[y]": 90 174 | 175 | it 'varexp - center-y', () -> 176 | thread = new GSS() 177 | thread.solve [ 178 | ['==', ['get', '$112[height]'],100] 179 | ['==', ['get', '$112', 'center-y'],51] 180 | ] 181 | expect(thread.values).to.eql 182 | "$112[height]": 100 183 | "$112[y]": 1 184 | 185 | 186 | 187 | # Tracking 188 | # --------------------------------------------------------------------- 189 | 190 | describe 'Tracking', -> 191 | 192 | it 'tracking by path', () -> 193 | thread = new GSS(document.createElement('div')) 194 | thread.solve [ 195 | ['==', ['get', '$222[line-height]'], 1.6] 196 | ] 197 | thread.solve [ 198 | ['==', ['get', '$112[x]'],10] 199 | ['==', ['get', '$112', 'right'],100] 200 | ], '.box' 201 | 202 | expect(thread.values).to.eql 203 | "$222[line-height]": 1.6 204 | "$112[x]": 10 205 | "$112[width]": 90 206 | 207 | thread.solve ['remove', '.box'] 208 | 209 | expect(thread.updated.solution).to.eql 210 | "$112[x]": null 211 | "$112[width]": null 212 | 213 | 214 | it 'tracking by selector', () -> 215 | thread = new GSS() 216 | thread.solve [ 217 | ['==', ['get','$112[x]'],50,'strong'] 218 | ], '.box$112' 219 | 220 | thread.solve [ 221 | ['==', ['get','$112[x]'],1000, 'required'] 222 | ], '.big-box$112' 223 | expect(thread.updated.solution).to.eql 224 | "$112[x]": 1000 225 | thread.solve [ 226 | ['remove', '.big-box$112'] 227 | ] 228 | expect(thread.updated.solution).to.eql 229 | "$112[x]": 50 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /spec/units.coffee: -------------------------------------------------------------------------------- 1 | 2 | expect = chai.expect 3 | assert = chai.assert 4 | 5 | remove = (el) -> 6 | el?.parentNode?.removeChild(el) 7 | 8 | 9 | 10 | describe 'Units', -> 11 | engine = container = null 12 | beforeEach -> 13 | container = document.createElement 'div' 14 | document.getElementById('fixtures').appendChild container 15 | window.$engine = engine = new GSS(container) 16 | 17 | afterEach -> 18 | remove(container) 19 | 20 | describe 'with conflicting constraints', -> 21 | describe 'without unit', -> 22 | it 'should solve constraints together and resolve conflict', (done) -> 23 | container.innerHTML = """ 24 | 31 | 32 | """ 33 | engine.then (solution)-> 34 | expect(solution).to.eql 35 | '$button1[width]': 40, 36 | '$button1[height]': 120 37 | done() 38 | 39 | describe 'with unit', -> 40 | it 'should resolve constraints separately and ignore conflict', (done) -> 41 | container.innerHTML = """ 42 | 49 | 50 | """ 51 | engine.then (solution)-> 52 | expect(solution).to.eql 53 | '$button1[width]': 50, 54 | '$button1[height]': 120 55 | done() 56 | 57 | describe 'with non-linear expressions', -> 58 | describe 'with unit', -> 59 | it 'should be able to compute ratios', (done) -> 60 | container.innerHTML = """ 61 | 73 | 74 | """ 75 | engine.then (solution)-> 76 | expect(solution).to.eql 77 | '$button1[width]': 50, 78 | '$button1[height]': 100, 79 | '$button1[ratio]': 0.5 80 | done() 81 | 82 | describe 'with dynamic units', -> 83 | describe 'bound to window', -> 84 | it 'should be able to compute width', (done) -> 85 | container.innerHTML = """ 86 | 95 | 96 | """ 97 | engine.then (solution)-> 98 | w = document.documentElement.clientWidth 99 | h = Math.min(window.innerHeight, document.documentElement.clientHeight) 100 | expect(Math.round solution['$button1[width]']).to.eql(Math.round w / 10) 101 | expect(Math.round solution['$button1[height]']).to.eql(Math.round h / 2) 102 | expect(Math.round solution['$button1[c]']).to.eql(Math.round Math.max(w, h) * 0.3) 103 | expect(Math.round solution['$button1[d]']).to.eql(Math.round Math.min(w, h) * 0.33) 104 | engine.then (solution) -> 105 | expect(Math.round solution['$button1[width]']).to.eql(Math.round 1000 / 10) 106 | if h < 1000 107 | expect(Math.round solution['$button1[c]']).to.eql(Math.round 1000 * 0.3) 108 | else 109 | expect(Math.round solution['$button1[d]']).to.eql(1000 * 0.33) 110 | 111 | engine.then (solution) -> 112 | expect(Math.round solution['$button1[d]']).to.eql(100 * 0.33) 113 | 114 | remove(engine.id('button1')) 115 | 116 | engine.then (solution)-> 117 | expect(solution['$button1[width]']).to.eql null 118 | expect(solution['$button1[height]']).to.eql null 119 | expect(solution['$button1[c]']).to.eql null 120 | expect(Object.keys(engine.data.watchers)).to.eql [] 121 | done() 122 | 123 | engine.data.properties['::window[height]'] = -> 124 | return 100 125 | engine.data.set('::window', 'height', 100) 126 | 127 | 128 | engine.data.properties['::window[width]'] = -> 129 | return 1000 130 | 131 | engine.data.set('::window', 'width', 1000) 132 | 133 | describe 'bound to font-size', -> 134 | it 'should be able to compute width', (done) -> 135 | container.innerHTML = """ 136 | 143 |
144 | 145 |
146 | """ 147 | engine.then (solution)-> 148 | expect(Math.round solution['$button1[width]']).to.eql(Math.round 200) 149 | expect(Math.round solution['$button1[height]']).to.eql(Math.round 40) 150 | engine.then (solution) -> 151 | expect(Math.round solution['$button1[width]']).to.eql(Math.round 300) 152 | expect(Math.round solution['$button1[height]']).to.eql(Math.round 60) 153 | 154 | remove(engine.id('button1')) 155 | 156 | engine.then (solution)-> 157 | expect(solution['$button1[width]']).to.eql null 158 | expect(solution['$button1[height]']).to.eql null 159 | expect(solution['$button1[c]']).to.eql null 160 | expect(Object.keys(engine.data.watchers)).to.eql [] 161 | done() 162 | 163 | engine.id('wrapper').style.fontSize = '30px' 164 | 165 | -------------------------------------------------------------------------------- /spec/vendor/MutationObserver.attributes.js: -------------------------------------------------------------------------------- 1 | 2 | if (typeof window != 'undefined') 3 | 4 | (function() { 5 | 6 | // MO is fired, revert overrided methods 7 | var listener = function(e){ 8 | if (e[0].attributeName != '___test___') return 9 | delete HTMLElement.prototype.removeAttribute 10 | delete HTMLElement.prototype.__removeAttribute 11 | delete HTMLElement.prototype.setAttribute 12 | delete HTMLElement.prototype.__setAttribute 13 | }; 14 | 15 | var observer = new this.MutationObserver(listener); 16 | var dummy = document.createElement('div') 17 | observer.observe(dummy, { 18 | attributes: true 19 | }); 20 | dummy.setAttribute("___test___", true); 21 | setTimeout(function() { 22 | 23 | 24 | observer.disconnect() 25 | dummy.removeAttribute('___test___') 26 | }, 10); 27 | 28 | HTMLElement.prototype.__removeAttribute = HTMLElement.prototype.removeAttribute; 29 | HTMLElement.prototype.removeAttribute = function(attrName) 30 | { 31 | var prevVal = this.getAttribute(attrName); 32 | this.__removeAttribute(attrName); 33 | var evt = document.createEvent("MutationEvent"); 34 | evt.initMutationEvent( 35 | "DOMAttrModified", 36 | true, 37 | false, 38 | this, 39 | prevVal, 40 | "", 41 | attrName, 42 | evt.REMOVAL 43 | ); 44 | this.dispatchEvent(evt); 45 | } 46 | 47 | HTMLElement.prototype.__setAttribute = HTMLElement.prototype.setAttribute 48 | 49 | HTMLElement.prototype.setAttribute = function(attrName, newVal) 50 | { 51 | var prevVal = this.getAttribute(attrName); 52 | this.__setAttribute(attrName, newVal); 53 | newVal = this.getAttribute(attrName); 54 | if (newVal !== prevVal) 55 | { 56 | var evt = document.createEvent("MutationEvent"); 57 | evt.initMutationEvent( 58 | "DOMAttrModified", 59 | true, 60 | false, 61 | this, 62 | prevVal || "", 63 | newVal || "", 64 | attrName, 65 | (prevVal == null) ? evt.ADDITION : evt.MODIFICATION 66 | ); 67 | evt.prevValue = prevVal 68 | evt.attrName = attrName 69 | this.dispatchEvent(evt); 70 | } 71 | } 72 | 73 | }).call(this); -------------------------------------------------------------------------------- /spec/view.coffee: -------------------------------------------------------------------------------- 1 | assert = chai.assert 2 | expect = chai.expect 3 | 4 | remove = (el) -> 5 | el?.parentNode?.removeChild(el) 6 | 7 | describe "GSS.View", -> 8 | 9 | engine = null 10 | container = null 11 | 12 | beforeEach -> 13 | container = document.createElement 'div' 14 | engine = new GSS(container) 15 | document.getElementById('fixtures').appendChild container 16 | 17 | afterEach -> 18 | remove(container) 19 | engine.destroy() 20 | 21 | describe 'Display Pass percolates downward through unconstrained views', -> 22 | 23 | it 'before & after', (done) -> 24 | onSolved = (e) -> 25 | values = e.detail.values 26 | assert target1.style['width'] is "88px","width should be 88px" 27 | assert target2.style['width'] is "88px","width should be 88px" 28 | container.removeEventListener 'solved', onSolved 29 | done() 30 | container.addEventListener 'solved', onSolved 31 | engine.solve [ 32 | ['==', ['get',['.', 'target'], 'width'], 88] 33 | ] 34 | 35 | container.innerHTML = """ 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | """ 49 | target1 = engine.class('target')[0] 50 | target2 = engine.class('target')[1] 51 | assert target1.style['width'] is "10px" 52 | assert target2.style['width'] is "10px" 53 | 54 | describe 'Display passes down translated offsets', -> 55 | 56 | it 'matrix3d & view:attach event', (done) -> 57 | container.innerHTML = """ 58 |
59 |
60 |
61 |
62 | """ 63 | ast = [ 64 | ['==', ['get',['.','target'],'y'], 100] 65 | ] 66 | 67 | q = document.getElementsByClassName('target') 68 | target1 = q[0] 69 | target2 = q[1] 70 | 71 | onSolved = (values) -> 72 | assert values['$target1[y]'] is 100, "solved value is 100. #{}" 73 | assert values['$target2[y]'] is 100, "solved value is 0. #{}" 74 | assert target1.style.top == '100px' 75 | assert target2.style.top == '0px' 76 | done() 77 | engine.once 'solved', onSolved 78 | engine.solve ast 79 | 80 | describe 'Elements can be positioned relative to', -> 81 | it 'after solving', (done) -> 82 | container.style.position = 'relative' 83 | 84 | ast = ['==', 85 | ['get', 86 | ['#', 'floater'], 87 | 'y'], 88 | ['+', 89 | ['get', 90 | ['#', 'anchor'], 91 | 'intrinsic-y'], 92 | 3]] 93 | 94 | 95 | engine.once 'solved', -> 96 | expect(engine.values['$floater[y]']).to.eql 20 97 | engine.id('pusher').setAttribute('style', 'padding-top: 11px; height: 17px;') 98 | 99 | engine.once 'solved', -> 100 | expect(engine.values['$floater[y]']).to.eql 31 101 | done() 102 | engine.solve ast 103 | container.innerHTML = """ 104 |
105 |
106 |
107 | """ 108 | 109 | 110 | describe 'Display Pass takes in account parent offsets when requested', -> 111 | 112 | it 'after solving', (done) -> 113 | 114 | engine.solve [ 115 | ['==', ['get',['.', 'target'],'y'], 100] 116 | ] 117 | 118 | 119 | container.innerHTML = """ 120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | """ 131 | 132 | q = document.getElementsByClassName('target') 133 | target1 = q[0] 134 | 135 | onSolved = (e) -> 136 | assert engine.values['$target1[y]'] is 100, "solved value is 100." 137 | assert target1.offsetTop == 92, "Top offset should match" 138 | assert target1.offsetLeft == 0, "Left offset should match" 139 | done() 140 | 141 | engine.once 'solved', onSolved 142 | 143 | xdescribe 'printCss', -> 144 | it 'prints css', (done) -> 145 | container.innerHTML = """ 146 | 154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | """ 163 | 164 | q = document.getElementsByClassName('target') 165 | target1 = q[0] 166 | target2 = q[1] 167 | 168 | GSS.config.defaultMatrixType = 'mat4' 169 | didAttach = false 170 | 171 | engine.once 'display', (values) -> 172 | css1 = target1.gssView.printCss() 173 | css2 = target2.gssView.printCss() 174 | cssRoot = GSS.printCss() 175 | m1 = "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 100, 0, 1)" 176 | m2 = "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)" 177 | expectedCss1 = "#target1{position:absolute;margin:0px;top:0px;left:0px;#{GSS._.dasherize(GSS._.transformPrefix)}:#{m1};margin-right:55px;}" 178 | expectedCss2 = "#target2{position:absolute;margin:0px;top:0px;left:0px;#{GSS._.dasherize(GSS._.transformPrefix)}:#{m2};margin-right:55px;}" 179 | assert css1 is expectedCss1,"wrong css1 #{css1}" 180 | assert css2 is expectedCss2,"wrong css2 #{css2}" 181 | assert( cssRoot is (expectedCss1 + expectedCss2), "wrong cssRoot, #{cssRoot}") 182 | 183 | done() 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/GSS.coffee: -------------------------------------------------------------------------------- 1 | ### Constructor: GSS 2 | Dispatches arguments by type, returns engine 3 | When scope is given, creates Document 4 | Otherwise abstract Engine ### 5 | GSS = -> #(data, url, scope) 6 | for argument, index in arguments 7 | continue unless argument 8 | switch typeof argument 9 | when 'object' 10 | if argument.nodeType 11 | scope = argument 12 | else 13 | data = argument 14 | when 'string', 'boolean' 15 | url = argument 16 | 17 | # **GSS()** attempts to find parent engine 18 | 19 | if !(@ instanceof GSS) && scope 20 | parent = scope 21 | while parent 22 | if id = GSS.identity.find(parent) 23 | if engine = GSS.Engine[id] 24 | return engine 25 | break unless parent.parentNode 26 | parent = parent.parentNode 27 | 28 | if scope && GSS.Document 29 | return new GSS.Document(data, url, scope) 30 | else 31 | return new GSS.Engine(data, url, scope) 32 | 33 | GSS.Engine = require('./Engine') 34 | GSS.identity = GSS.Engine::identity 35 | GSS.identify = GSS.Engine::identify 36 | GSS.console = GSS.Engine::console 37 | 38 | module.exports = GSS 39 | -------------------------------------------------------------------------------- /src/document/Style.coffee: -------------------------------------------------------------------------------- 1 | # Define a style (family) from JSON definition 2 | # Creates matcher function that validates and identifies 3 | # values against defined property types and keywords 4 | 5 | # Style family shorthands produce family object instance 6 | # that has default values set in the prototype. 7 | # It can be used to merge and serialize partial updates. 8 | 9 | 10 | Style = (definition, name, styles, 11 | keywords = {}, types = [], keys = [], properties = [], required = {} 12 | optional, depth = 0) -> 13 | 14 | requirement = true 15 | pad = initial = previous = undefined 16 | max = depth 17 | # Group of properties 18 | if definition.length == undefined 19 | for key, def of definition 20 | continue unless typeof def == 'object' 21 | property = key.indexOf('-') > -1 && styles[key] && key || name + '-' + key 22 | style = @Style(def, property, styles, null, null, null, null, null, null, null, depth) 23 | unless optional == true 24 | required[property] = optional || requirement 25 | requirement = property 26 | if style.types 27 | for type, index in style.types 28 | types.push type 29 | prop = style.keys?[index] || property 30 | keys.push prop 31 | if properties.indexOf(prop) == -1 32 | properties.push prop 33 | if style.keywords 34 | for prop, value of style.keywords 35 | for item in value 36 | for p in (item.push && item || [item]) 37 | if properties.indexOf(p) == -1 38 | properties.push p 39 | (keywords[prop] ||= []).push value 40 | else 41 | for property, index in definition 42 | # Optional group 43 | switch typeof property 44 | when "object" 45 | substyle = @Style(property, name, styles, keywords, types, keys, properties, required, 46 | (property.push && (requirement || true)) || optional, depth + 1) 47 | pad = property.pad || substyle.pad 48 | max = Math.max(substyle.depth, max) 49 | when "string" 50 | # Predefined value type 51 | if type = @[property] 52 | types.push(type) 53 | if storage = type.Keywords 54 | for key of storage 55 | initial = key 56 | break 57 | 58 | initial ?= 0 59 | # Keyword 60 | else 61 | initial ?= property 62 | (keywords[property] ||= []).push(name) 63 | else 64 | initial ?= property 65 | 66 | if typeof initial == 'function' 67 | callback = initial 68 | initial = undefined 69 | 70 | if initial == undefined 71 | initial = new Shorthand 72 | initial.displayName = initial::property = name 73 | for property in properties 74 | initial::[property] = styles[property].initial 75 | else if keys.length == 0 76 | keys = undefined 77 | 78 | matcher = new Matcher(name, keywords, types, keys, required, pad, max, initial, callback) 79 | if initial?.displayName 80 | initial::style = matcher 81 | initial::styles = styles 82 | initial::properties = properties 83 | 84 | 85 | matcher.format = (value) -> 86 | return Shorthand::toExpressionString(name, value, false, styles) 87 | 88 | return styles[name] = matcher 89 | 90 | # Class that holds matched properties. 91 | # Its prototype is extended with default values inferred from style definition 92 | # Called every time shorthand is parsed, can be a singleton 93 | class Shorthand 94 | constructor: (callback) -> 95 | callback ||= (options) -> 96 | if options 97 | for key, value of options 98 | @[key] = value 99 | @ 100 | callback.prototype = @ 101 | return callback 102 | 103 | # Serialize given styles respecting default and instance values. 104 | format: (styles, number) -> 105 | string = undefined 106 | if @style.keys 107 | while style = @[i = (i ? -1) + 1] 108 | string = (string && string + ', ' || '') + style.format(styles, i + 1) 109 | 110 | pad = @style.pad 111 | for key, index in keys = @properties 112 | if index && pad 113 | if index > 2 114 | continue if @equals(key, keys[1]) 115 | else if index > 1 116 | continue if @equals(key, keys[0]) && 117 | (!@hasOwnProperty[keys[3]] || @equals(keys[3], keys[1])) 118 | else 119 | continue if @equals(key, keys[0]) && 120 | @equals(keys[1], keys[2]) && 121 | @equals(keys[2], keys[3]) 122 | else 123 | 124 | if styles && number && (value = styles[key + '-' + number])? 125 | prefix = previous = undefined 126 | if typeof value != 'string' 127 | keys = @style.keys 128 | types = @style.types 129 | for index in [keys.indexOf(key) - 1 ... 0] by -1 130 | if (k = keys[index]) != previous 131 | break if @hasOwnProperty(k) 132 | if types[index] == @styles.engine.Length 133 | expression = @toExpressionString(k, @[k]) 134 | prefix = ((string || prefix) && ' ' || '') + expression + (prefix && ' ' + prefix || '') 135 | previous = k 136 | 137 | string += prefix if prefix 138 | 139 | 140 | else 141 | continue unless @hasOwnProperty(key) 142 | value = @[key] 143 | 144 | expression = @toExpressionString(key, value) 145 | string = (string && string + ' ' || '') + expression 146 | 147 | return string 148 | 149 | # Compare values of two properties 150 | equals: (first, second) -> 151 | a = @[first] 152 | b = @[second] 153 | if typeof a != 'object' 154 | return a == b 155 | else 156 | return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] 157 | 158 | # Serialize expression to string, cast integers to pixels when applicable 159 | toExpressionString: (key, operation, expression, styles = @styles) -> 160 | switch typeof operation 161 | when 'object' 162 | name = operation[0] 163 | if @styles.engine.signatures[name]?.Number?.resolved 164 | return @toExpressionString(key, operation[1], true) + name 165 | else 166 | string = name + '(' 167 | for index in [1 .. operation.length - 1] 168 | string += ',' unless index == 1 169 | string += @toExpressionString(key, operation[index], true) 170 | return string + ')' 171 | when 'number' 172 | if !expression 173 | types = styles[key].types 174 | if operation 175 | for type in types 176 | if type.formatNumber 177 | if (expression = type.formatNumber(operation))? 178 | return expression 179 | return operation 180 | return operation 181 | 182 | 183 | # Generate a function that will match set of parsed tokens against style definition 184 | Matcher = (name, keywords, types, keys, required, pad, depth, initial, callback) -> 185 | matcher = -> 186 | result = matched = undefined 187 | 188 | if pad && arguments.length < 4 189 | args = [ 190 | arguments[0] 191 | arguments[1] ? arguments[0] 192 | arguments[2] ? arguments[0] 193 | arguments[1] ? arguments[0] 194 | ] 195 | 196 | for argument, i in (args || arguments) 197 | # Match keyword 198 | switch typeof argument 199 | when 'object' 200 | if typeof argument[0] != 'string' || argument.length == 1 201 | if matched = matcher.apply(@, argument) 202 | (result ||= new initial)[i] = matched 203 | else return 204 | 205 | when 'string' 206 | if props = keywords[argument] 207 | if keys 208 | j = pad && i || 0 209 | while (property = props[j++])? 210 | if !result || !result.hasOwnProperty(property) 211 | if !required[property] || (result && result[required[property]] != undefined) 212 | matched = (result ||= new initial)[property] = argument 213 | break 214 | # Unique keyword for property resolved as value. Use keyword, re-match value 215 | else if props.length == 1 && argument != result[property] 216 | arg = argument 217 | argument = result[property] 218 | result[property] = arg 219 | if typeof argument == 'string' && (props = keywords[argument]) 220 | j = pad && i || 0 221 | continue 222 | break 223 | if pad 224 | break 225 | else 226 | return argument 227 | 228 | # Match argument by type 229 | if types && !matched? 230 | if keys 231 | for property, index in keys 232 | if !result || (!result.hasOwnProperty(property) && 233 | (!(req = required[property]) || result.hasOwnProperty(req))) 234 | if (matched = types[index](argument)) != undefined 235 | (result ||= new initial)[property] = argument 236 | break 237 | else 238 | for type, index in types 239 | if type(argument) != undefined 240 | return argument 241 | 242 | return unless matched? 243 | matched = undefined 244 | 245 | if callback && (returned = callback(result))? 246 | return returned 247 | return result 248 | matcher.matcher = true 249 | matcher.displayName = name 250 | matcher.keywords = keywords if keywords? 251 | matcher.types = types if types? 252 | matcher.keys = keys if keys? 253 | matcher.pad = pad if pad? 254 | matcher.depth = depth if depth? 255 | matcher.initial = initial if initial? 256 | matcher.callback = callback if callback? 257 | return matcher 258 | 259 | 260 | module.exports = Style 261 | 262 | -------------------------------------------------------------------------------- /src/document/commands/Unit.coffee: -------------------------------------------------------------------------------- 1 | Variable = require('../../engine/commands/Variable') 2 | 3 | class Unit extends Variable 4 | 5 | signature: [ 6 | value: ['Variable', 'Number'] 7 | ] 8 | 9 | # Dynamic lengths 10 | 11 | getProperty: (operation) -> 12 | parent = operation 13 | while parent = parent.parent 14 | if parent.command.type == 'Assignment' 15 | return parent[1] 16 | 17 | Dependencies: 18 | 'margin-top': 'containing-width' 19 | 'margin-top': 'containing-width' 20 | 'margin-right': 'containing-width' 21 | 'margin-left': 'containing-width' 22 | 'padding-top': 'containing-width' 23 | 'padding-top': 'containing-width' 24 | 'padding-right': 'containing-width' 25 | 'padding-left': 'containing-width' 26 | 'left': 'containing-width' 27 | 'right': 'containing-width' 28 | 'width': 'containing-width' 29 | 'min-width': 'containing-width' 30 | 'max-width': 'containing-width' 31 | 'text-width': 'containing-width' 32 | 'top': 'containing-height' 33 | 'bottom': 'containing-height' 34 | 'height': 'containing-height' 35 | 'font-size': 'containing-font-size' 36 | 'vertical-align': 'line-height' 37 | 'background-position-x': 'width' 38 | 'background-position-y': 'height' 39 | 40 | @define 41 | '%': (value, engine, operation, continuation, scope) -> 42 | property = @Dependencies[@getProperty(operation)] || 'containing-width' 43 | path = engine.getPath(scope, property) 44 | return ['*', ['px', value] , ['get', path]] 45 | 46 | em: (value, engine, operation, continuation, scope) -> 47 | path = engine.getPath(scope, 'computed-font-size') 48 | return ['*', ['px', value] , ['get', path]] 49 | 50 | rem: (value, engine, operation, continuation, scope) -> 51 | path = @engine.getPath(engine.scope._gss_id, 'computed-font-size') 52 | return ['*', ['px', value] , ['get', path]] 53 | 54 | vw: (value, engine, operation, continuation, scope) -> 55 | return ['*', ['/', ['px', value], 100] , ['get', '::window[width]']] 56 | 57 | vh: (value, engine, operation, continuation, scope) -> 58 | return ['*', ['/', ['px', value], 100] , ['get', '::window[height]']] 59 | 60 | vmin: (value, engine, operation, continuation, scope) -> 61 | return ['*', ['/', ['px', value], 100] , ['min', ['get', '::window[height]'], ['get', '::window[width]']]] 62 | 63 | vmax: (value, engine, operation, continuation, scope) -> 64 | return ['*', ['/', ['px', value], 100] , ['max', ['get', '::window[height]'], ['get', '::window[width]']]] 65 | 66 | module.exports = Unit -------------------------------------------------------------------------------- /src/document/properties/Getters.coffee: -------------------------------------------------------------------------------- 1 | class Getters 2 | # Global variables managed by the engine 3 | 4 | 5 | '::window': 6 | 7 | width: -> 8 | return Math.min(window.innerWidth, document.documentElement.clientWidth) 9 | 10 | height: -> 11 | return document.documentElement.clientHeight 12 | x: 0 13 | y: 0 14 | 15 | '::document': 16 | 17 | height: -> 18 | return document.documentElement.clientHeight 19 | 20 | width: -> 21 | return document.documentElement.clientWidth 22 | 23 | x: 0 24 | y: 0 25 | 26 | scroll: 27 | height: -> 28 | return document.body.scrollHeight 29 | 30 | width: -> 31 | return document.body.scrollWidth 32 | 33 | left: -> 34 | return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft 35 | 36 | top: -> 37 | return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop 38 | 39 | 40 | intrinsic: 41 | 42 | height: (element) -> 43 | return# element.offsetHeight 44 | 45 | width: (element) -> 46 | return# element.offsetWidth 47 | 48 | y: (element) -> 49 | return 50 | 51 | x: (element) -> 52 | return 53 | 54 | scroll: 55 | 56 | left: (element) -> 57 | return element.scrollLeft 58 | 59 | top: (element) -> 60 | return element.scrollTop 61 | 62 | height: (element) -> 63 | return element.scrollHeight 64 | 65 | width: (element) -> 66 | return element.scrollWidth 67 | 68 | client: 69 | 70 | left: (element) -> 71 | return element.clientLeft 72 | 73 | top: (element) -> 74 | return element.clientTop 75 | 76 | height: (element) -> 77 | return element.clientHeight 78 | 79 | width: (element) -> 80 | return element.clientWidth 81 | 82 | offset: 83 | 84 | left: (element) -> 85 | return element.offsetLeft 86 | 87 | top: (element) -> 88 | return element.offsetTop 89 | 90 | height: (element) -> 91 | return element.offsetHeight 92 | 93 | width: (element) -> 94 | return element.offsetWidth 95 | 96 | module.exports = Getters 97 | -------------------------------------------------------------------------------- /src/document/properties/Styles.coffee: -------------------------------------------------------------------------------- 1 | class Styles 2 | constructor: (@engine) -> 3 | 4 | transform: [[ 5 | -> mat4.create(), 6 | 'Matrix' 7 | ]] 8 | 9 | animation: [[[ 10 | name: ['none', 'String'] 11 | duration: ['Time'] 12 | delay: ['Time'] 13 | direction: ['normal', 'reverse', 'alternate'] 14 | 'timing-function': ['Easing'], 15 | 'iteration-count': [1, 'infinite', 'Number'] 16 | 'fill-mode': ['none', 'both', 'forwards', 'backwards'] 17 | 'play-state': ['running', 'paused'] 18 | ]]] 19 | 20 | transition: [[[ 21 | property: ['all', 'property', 'none'] 22 | duration: ['Time'] 23 | delay: ['Time'] 24 | direction: ['reverse', 'normal'] 25 | 'timing-function': ['Easing'] 26 | ]]] 27 | 28 | background: [[[ 29 | image: ['URL', 'Gradient', 'none'] 30 | position: 31 | x: ['Length', 'Percentage', 'center', 'left', 'right'] 32 | y: ['Length', 'Percentage', 'center', 'top', 'bottom'] 33 | size: 34 | x: ['Length', 'Percentage', 'cover', 'contain'] 35 | y: ['Length', 'Percentage'] 36 | repeat: ['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'space', 'round'] 37 | attachment: ['fixed', 'scroll', 'local'] 38 | origin: ['padding-box', 'border-box', 'content-box'] 39 | clip: ['border-box', 'content-box', 'padding-box'] 40 | ]] 41 | [ 42 | color: ['Color', 'transparent'] 43 | ]] 44 | 45 | text: 46 | shadow: [[[ 47 | offset: 48 | x: ['Length'] 49 | y: ['Length'] 50 | blur: ['Length'] 51 | color: ['Color'] 52 | ]]] 53 | 54 | decoration: ['none', 'capitalize', 'uppercase', 'lowercase'] 55 | align: ['left', 'right', 'center', 'justify'] 56 | ident: ['Length', 'Percentage'] 57 | 58 | box: 59 | shadow: [[ 60 | [ inset: ['inset'] ] 61 | offset: 62 | x: ['Length'] 63 | y: ['Length'] 64 | [ 65 | blur: ['Length'] 66 | spread: ['Length'] 67 | ] 68 | color: ['Color'] 69 | ]] 70 | 71 | sizing: ['padding-box', 'border-box', 'content-box'] 72 | 73 | outline: [ 74 | width: ['medium', 'Length'] 75 | style: ['none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'] 76 | color: ['invert', 'Color'] 77 | ] 78 | 79 | 80 | 'line-height': ['normal', 'Length', 'Number', 'Percentage'] 81 | 82 | font: [ 83 | [ 84 | style: ['normal', 'italic', 'oblique'] 85 | variant: ['normal', 'small-caps'] 86 | weight: ['normal', 'Number', 'bold'] 87 | ] 88 | size: ['Size', 'Length', 'Percentage'] 89 | [ 90 | #'/', 91 | 'line-height': ['normal', 'Length', 'Number', 'Percentage'] 92 | ] 93 | family: ['inherit', 'strings'] 94 | ] 95 | 96 | 'font-stretch': ['normal', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 97 | 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded' ] 98 | 'font-size-adjust': ['Number'] 99 | 100 | 'letter-spacing': ['normal', 'Length'], 101 | 102 | list: 103 | style: [ 104 | type: ['disc', 'circle', 'square', 'decimal', 'decimal-leading-zero', 'lower-roman', 'upper-roman', 105 | 'lower-greek', 'lower-latin', 'upper-latin', 'armenian', 'georgian', 'lower-alpha', 'none', 106 | 'upper-alpha'], 107 | image: ['none', 'URL'] 108 | position: ['outside', 'inside', 'none'] 109 | ] 110 | 111 | width: ['Length', 'auto'] 112 | height: ['Length', 'auto'] 113 | min: 114 | width: ['Length', 'auto'] 115 | height: ['Length', 'auto'] 116 | max: 117 | width: ['Length', 'auto'] 118 | height: ['Length', 'auto'] 119 | 120 | display: ['inline', 'inline-block', 'block', 'list-item', 'run-in', 'table', 'inline-table', 'none', 121 | 'table-row-group', 'table-header-group', 'table-footer-group', 'table-row', 122 | 'table-column-group', 'table-column', 'table-cell', 'table-caption'] 123 | visibility: ['visible', 'hidden'] 124 | float: ['none', 'left', 'right'] 125 | clear: ['none', 'left', 'right', 'both'] 126 | overflow: ['visible', 'hidden', 'scroll', 'auto'] 127 | 'overflow-x': ['visible', 'hidden', 'scroll', 'auto'] 128 | 'overflow-y': ['visible', 'hidden', 'scroll', 'auto'] 129 | position: ['static', 'relative', 'absolute', 'fixed', 'sticky'] 130 | top: ['Length', 'Percentage', 'auto'] 131 | left: ['Length', 'Percentage', 'auto'] 132 | right: ['Length', 'Percentage', 'auto'] 133 | bottom: ['Length', 'Percentage', 'auto'] 134 | opacity: ['Number'] 135 | 'z-index': ['Integer'] 136 | cursor: ['auto', 'crosshair', 'default', 'hand', 'move', 'e-resize', 'ne-resize', 'nw-resize', 137 | 'n-resize', 'se-resize', 'sw-resize', 's-resize', 'w-resize', 'text', 'wait', 'help'] 138 | color: ['color'] 139 | 140 | columns: ['length'] 141 | 'column-gap': ['length'] 142 | 'column-width': ['length'] 143 | 'column-count': ['Integer'] 144 | 145 | 146 | 147 | for side, index in sides = ['top', 'right', 'bottom', 'left'] 148 | (Styles::margin ||= [{'pad'}])[0][side] = ['Length', 'Percentage', 'auto'] 149 | (Styles::padding ||= [{'pad'}])[0][side] = ['Length', 'Percentage', 'auto'] 150 | (Styles::border ||= [{'pad'}])[0][side] = [[ 151 | width: ['Length', 'thin', 'thick', 'medium'], 152 | style: ['none', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset', 'none'] 153 | color: ['Color'] 154 | ]] 155 | for type in ['width', 'color', 'style'] 156 | (Styles::['border-' + type] ||= [[{'pad'}]])[0][+ 157 | 0]['border-' + side + '-' + type] = 158 | Styles::border[0][side][0][0][type] 159 | 160 | unless index % 2 161 | for i in [3 ... 0] by -2 162 | prop = 'border-' + side + '-' + (sides[i]) + '-radius' 163 | Styles::[prop] = ['Length', 'none'] 164 | 165 | (Styles::['border-radius'] ||= [{'pad'}])[0][prop] = ['Length', 'none'] 166 | 167 | module.exports = Styles -------------------------------------------------------------------------------- /src/document/types/Action.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gss/engine/be430ea249f8030e11c8e03a8e170dcc8b8cf5fb/src/document/types/Action.coffee -------------------------------------------------------------------------------- /src/document/types/Color.coffee: -------------------------------------------------------------------------------- 1 | # Algorithms are used as they are from chroma.js by Gregor Aisch (BSD License). 2 | 3 | Command = require '../../engine/Command' 4 | 5 | class Color extends Command 6 | @Keywords: {'transparent', 'currentColor'} 7 | 8 | constructor: (obj) -> 9 | switch typeof obj 10 | when 'string' 11 | if Color.Keywords[obj] 12 | return obj 13 | else if obj.charAt(0) == '#' 14 | return obj 15 | when 'object' 16 | if Color[obj[0]] 17 | return obj 18 | 19 | @define 20 | hsl: (h, s, l) -> 21 | if s == 0 22 | r = g = b = l*255 23 | else 24 | t3 = [0,0,0] 25 | c = [0,0,0] 26 | t2 = if l < 0.5 then l * (1+s) else l+s-l*s 27 | t1 = 2 * l - t2 28 | h /= 360 29 | t3[0] = h + 1/3 30 | t3[1] = h 31 | t3[2] = h - 1/3 32 | for i in [0..2] 33 | t3[i] += 1 if t3[i] < 0 34 | t3[i] -= 1 if t3[i] > 1 35 | if 6 * t3[i] < 1 36 | c[i] = t1 + (t2 - t1) * 6 * t3[i] 37 | else if 2 * t3[i] < 1 38 | c[i] = t2 39 | else if 3 * t3[i] < 2 40 | c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6 41 | else 42 | c[i] = t1 43 | [r,g,b] = [Math.round(c[0]*255),Math.round(c[1]*255),Math.round(c[2]*255)] 44 | ['rgb', r,g,b] 45 | 46 | hsla: (h, s, l, a) -> 47 | return Type.Color.hsl.execute(h, s, l).concat[a] 48 | 49 | rgb: (r, g, b) -> 50 | return ['rgb', r, g, b] 51 | 52 | rgba: (r, g, b, a) -> 53 | return ['rgba', r, g, b, a] 54 | 55 | hex: (hex) -> 56 | if hex.match /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/ 57 | if hex.length == 4 or hex.length == 7 58 | hex = hex.substr(1) 59 | if hex.length == 3 60 | hex = hex.split("") 61 | hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2] 62 | u = parseInt(hex, 16) 63 | r = u >> 16 64 | g = u >> 8 & 0xFF 65 | b = u & 0xFF 66 | return ['rgb', r,g,b] 67 | 68 | # match rgba hex format, eg #FF000077 69 | if hex.match /^#?([A-Fa-f0-9]{8})$/ 70 | if hex.length == 9 71 | hex = hex.substr(1) 72 | u = parseInt(hex, 16) 73 | r = u >> 24 & 0xFF 74 | g = u >> 16 & 0xFF 75 | b = u >> 8 & 0xFF 76 | a = u & 0xFF 77 | return ['rgba', r,g,b,a] 78 | 79 | module.exports = Color -------------------------------------------------------------------------------- /src/document/types/Easing.coffee: -------------------------------------------------------------------------------- 1 | Command = require '../../engine/commands/Variable' 2 | 3 | class Easing extends Command 4 | constructor: (obj) -> 5 | if typeof obj == 'string' 6 | if obj = @Type.Timings[obj] 7 | return obj 8 | else if obj[0] == 'steps' || obj[0] == 'cubic-bezier' 9 | return obj 10 | 11 | @define 12 | 'ease': ['cubic-bezier', .42, 0, 1, 1] 13 | 'ease-in': ['cubic-bezier', .42, 0, 1, 1] 14 | 'ease-out': ['cubic-bezier', 0, 0, .58, 1] 15 | 'ease-in-out': ['cubic-bezier', .42, 0, .58, 1] 16 | 'linear': ['cubic-bezier', 0, 0, 1, 1] 17 | 'step-start': 'step-start' 18 | 'step-end': 'step-end' 19 | 20 | module.exports = Easing -------------------------------------------------------------------------------- /src/document/types/Gradient.coffee: -------------------------------------------------------------------------------- 1 | Command = require('../../engine/Command') 2 | 3 | class Gradient extends Command 4 | type: 'Gradient' 5 | 6 | constructor: (obj) -> 7 | switch typeof obj 8 | when 'object' 9 | if Gradient[obj[0]] 10 | return obj 11 | 12 | @define 13 | 'linear-gradient': -> 14 | 'radial-gradient': -> 15 | 'repeating-linear-gradient': -> 16 | 'repeating-radial-gradient': -> 17 | 18 | module.exports = Gradient -------------------------------------------------------------------------------- /src/document/types/Matrix.coffee: -------------------------------------------------------------------------------- 1 | Command = require ('../../engine/Command') 2 | 3 | class Matrix extends Command 4 | type: 'Matrix' 5 | 6 | Library: require('../../../vendor/gl-matrix') 7 | 8 | matrix: -> 9 | 10 | matrix3d: -> 11 | 12 | mat3: (matrix = @_mat3.create(), method, a, b, c) -> 13 | if matrix.length == 9 14 | return @_mat3[method](matrix, matrix, a, b, c) 15 | else 16 | return @_mat4[method](matrix, matrix, a, b, c) 17 | 18 | mat4: (matrix = @_mat4.create(), method, a, b, c) -> 19 | if matrix.length == 9 20 | matrix = @_mat4.fromMat3(matrix) 21 | return @_mat4[method](matrix, matrix, a, b, c) 22 | 23 | for property, method of Matrix::Library 24 | Matrix::['_' + property] = method 25 | 26 | class Matrix::Sequence extends Command.Sequence 27 | 28 | 29 | # 1-axis transform 30 | class Matrix.Transformation1 extends Matrix 31 | signature: [ 32 | [matrix: ['Matrix']] 33 | x: ['Variable', 'Number'] 34 | ] 35 | 36 | @define 37 | 38 | translateX: (matrix, x) -> 39 | @mat3 matrix, 'translate', [x, 1, 1] 40 | 41 | translateY: (matrix, y) -> 42 | @mat3 matrix, 'translate', [1, y, 1] 43 | 44 | translateZ: (matrix, z) -> 45 | @mat4 matrix, 'translate', [1, 1, z] 46 | 47 | translate: (matrix, x) -> 48 | @mat3 matrix, 'translate', [x, x] 49 | 50 | rotateX: (matrix, angle) -> 51 | @mat4 matrix, 'rotateX', angle * 360 * (Math.PI / 180) 52 | 53 | rotateY: (matrix, angle) -> 54 | @mat4 matrix, 'rotateY', angle * 360 * (Math.PI / 180) 55 | 56 | rotateZ: (matrix, angle) -> 57 | @mat4 matrix, 'rotateZ', angle * 360 * (Math.PI / 180) 58 | 59 | scaleX: (matrix, x) -> 60 | @mat3 matrix, 'scale', [x, 1, 1] 61 | 62 | scaleY: (matrix, y) -> 63 | @mat3 matrix, 'scale', [1, y, 1] 64 | 65 | scaleZ: (matrix, z) -> 66 | @mat4 matrix, 'scale', [1, 1, z] 67 | 68 | scale: (matrix, x) -> 69 | @mat4 matrix, 'scale', [x, x, 1] 70 | 71 | rotate: (matrix, angle) -> 72 | @mat3 matrix, 'rotate', angle * 360 * (Math.PI / 180), [0, 0, 1] 73 | 74 | # 2 axis transforms 75 | class Matrix.Transformation2 extends Matrix 76 | signature: [ 77 | [matrix: ['Matrix']] 78 | x: ['Variable', 'Number'] 79 | y: ['Variable', 'Number'] 80 | ] 81 | 82 | @define 83 | translate: (matrix, x, y) -> 84 | @mat3 matrix, 'translate', [x, y] 85 | 86 | scale: (matrix, x, y) -> 87 | @mat3 matrix, 'translate', [x, y] 88 | 89 | # 3 axis transforms 90 | class Matrix.Transformation3 extends Matrix 91 | signature: [ 92 | [matrix: ['Matrix']] 93 | x: ['Variable', 'Number'] 94 | y: ['Variable', 'Number'] 95 | z: ['Variable', 'Number'] 96 | ] 97 | 98 | @define 99 | translate3d: (matrix, x, y, z) -> 100 | if z == 0 101 | @mat3 matrix, 'translate', [x, y] 102 | else 103 | @mat4 matrix, 'translate', [x, y, z] 104 | 105 | scale3d: (matrix, x, y, z) -> 106 | if z == 1 107 | @mat3 matrix, 'scale', [x, y] 108 | else 109 | @mat4 matrix, 'scale', [x, y, z] 110 | 111 | # 3 axis + angle 112 | class Matrix.Transformation3A extends Matrix 113 | signature: [ 114 | [matrix: ['Matrix']] 115 | x: ['Variable', 'Number'] 116 | y: ['Variable', 'Number'] 117 | z: ['Variable', 'Number'] 118 | a: ['Variable', 'Number'] 119 | ] 120 | 121 | @define 122 | rotate3d: (matrix, x, y = x, z = 0, angle) -> 123 | if z == 0 124 | @mat3 matrix, 'rotate', [x, y], angle * 360 125 | else 126 | @mat4 matrix, 'rotate', [x, y, z], angle * 360 127 | 128 | module.exports = Matrix 129 | -------------------------------------------------------------------------------- /src/document/types/Measurement.coffee: -------------------------------------------------------------------------------- 1 | Unit = require '../commands/Unit' 2 | 3 | 4 | 5 | class Measurement extends Unit 6 | 7 | signature: [ 8 | value: ['Variable', 'Number'] 9 | ] 10 | 11 | class Measurement.Percentage extends Measurement 12 | constructor: (obj) -> 13 | switch typeof obj 14 | when 'object' 15 | if obj[0] == '%' 16 | return @ 17 | 18 | 19 | class Measurement.Length extends Measurement 20 | constructor: (obj) -> 21 | switch typeof obj 22 | when 'number' 23 | return obj 24 | when 'object' 25 | if Measurement.Length[obj[0]] || (Unit[obj[0]] && obj[0] != '%') 26 | return @ 27 | 28 | @define 29 | 30 | # Static lengths 31 | 32 | px: (value) -> 33 | return value 34 | 35 | pt: (value) -> 36 | return value 37 | 38 | cm: (value) -> 39 | return value * 37.8 40 | 41 | mm: (value) -> 42 | return value * 3.78 43 | 44 | in: (value) -> 45 | return value * 96 46 | 47 | @formatNumber: (number) -> 48 | return number + 'px' 49 | 50 | 51 | # Rotations 52 | class Measurement.Angle extends Measurement 53 | constructor: (obj) -> 54 | switch typeof obj 55 | when 'number' 56 | return obj 57 | when 'object' 58 | if Measurement.Angle[obj[0]] 59 | return @ 60 | @define 61 | deg: (value) -> 62 | return value * 360 63 | 64 | grad: (value) -> 65 | return value / (360 / 400) 66 | 67 | rad: (value) -> 68 | return value * (Math.PI / 180) 69 | 70 | turn: (value) -> 71 | return value 72 | 73 | @formatNumber: (number) -> 74 | return number + 'rad' 75 | 76 | # Time 77 | class Measurement.Time extends Measurement 78 | constructor: (obj) -> 79 | switch typeof obj 80 | when 'number' 81 | return obj 82 | when 'object' 83 | if Measurement.Time[obj[0]] 84 | return @ 85 | 86 | @define 87 | h: (value) -> 88 | return value * 60 * 60 * 1000 89 | 90 | min: (value) -> 91 | return value * 60 * 1000 92 | 93 | s: (value) -> 94 | return value * 1000 95 | 96 | ms: (value) -> 97 | return value 98 | 99 | @formatNumber: (number) -> 100 | return number + 'ms' 101 | 102 | # Frequency 103 | class Measurement.Frequency extends Measurement 104 | constructor: (obj) -> 105 | switch typeof obj 106 | when 'number' 107 | return obj 108 | when 'object' 109 | if Measurement.Frequency[obj[0]] 110 | return @ 111 | 112 | @define 113 | mhz: (value) -> 114 | return value * 1000 * 1000 115 | 116 | khz: (value) -> 117 | return value * 1000 118 | 119 | hz: (value) -> 120 | return value 121 | 122 | @formatNumber: (number) -> 123 | return number + 'hz' 124 | 125 | module.exports = Measurement -------------------------------------------------------------------------------- /src/document/types/Primitive.coffee: -------------------------------------------------------------------------------- 1 | Command = require('../../engine/Command') 2 | 3 | class Primitive extends Command 4 | 5 | # Decimal value (e.g. line-height: 1.0) 6 | class Primitive.Number extends Primitive 7 | type: 'Number' 8 | constructor: (obj) -> 9 | parsed = parseFloat(obj) 10 | if parsed == obj 11 | return parsed 12 | 13 | @formatNumber: (number) -> 14 | return number 15 | 16 | class Primitive.Integer extends Primitive 17 | type: 'Integer' 18 | constructor: (obj) -> 19 | parsed = parseInt(obj) 20 | if String(parsed) == String(obj) 21 | return parsed 22 | 23 | class Primitive.String extends Primitive 24 | type: 'String' 25 | constructor: (obj) -> 26 | if typeof obj == 'string' 27 | return obj 28 | 29 | # Array of strings (e.g. font-family) 30 | class Primitive.Strings extends Primitive 31 | type: 'Strings' 32 | constructor: (obj) -> 33 | if typeof obj == 'string' || obj instanceof Array 34 | return obj 35 | 36 | class Primitive.Size extends Primitive 37 | type: 'Size' 38 | constructor: (obj) -> 39 | if typeof obj == 'string' && Primitive.Size.Keywords?[obj] 40 | return obj 41 | @Keywords: {'medium', 'xx-small', 'x-small', 'small', 'large', 'x-large', 'xx-large', 'smaller', 'larger' } 42 | 43 | # Keywords for background-position and alike 44 | class Primitive.Position extends Primitive 45 | type: 'Position' 46 | constructor: (obj) -> 47 | if typeof obj == 'string' && Primitive.Position.Keywords?[obj] 48 | return obj 49 | @Keywords: {"top", "bottom", "left", "right"} 50 | 51 | class Primitive.Property extends Primitive 52 | type: 'Property' 53 | Property: (obj) -> 54 | if @properties[obj] 55 | return obj 56 | 57 | module.exports = Primitive -------------------------------------------------------------------------------- /src/document/types/URL.coffee: -------------------------------------------------------------------------------- 1 | Command = require('../../engine/Command') 2 | 3 | class URL extends Command 4 | type: 'URL' 5 | 6 | constructor: (obj) -> 7 | switch typeof obj 8 | when 'object' 9 | if URL[obj[0]] 10 | return obj 11 | 12 | @define 13 | 'url': -> 14 | 'src': -> 15 | 'canvas': -> 16 | 17 | 18 | module.exports = URL 19 | -------------------------------------------------------------------------------- /src/engine/commands/Condition.coffee: -------------------------------------------------------------------------------- 1 | Query = require('../Query') 2 | 3 | class Condition extends Query 4 | type: 'Condition' 5 | 6 | signature: [ 7 | if: ['Query', 'Selector', 'Variable', 'Constraint', 'Default'], 8 | then: ['Any'], 9 | [ 10 | else: ['Any'] 11 | ] 12 | ] 13 | 14 | List: true 15 | 16 | 17 | cleaning: true 18 | 19 | conditional: 1 20 | boundaries: true 21 | domains: 22 | 1: 'output' 23 | 24 | constructor: (operation, engine) -> 25 | @path = @key = @serialize(operation, engine) 26 | 27 | if @linked 28 | if parent = operation.parent 29 | previous = parent[parent.indexOf(operation) - 1] 30 | if command = previous.command 31 | if command.type == 'Condition' 32 | command.next = operation 33 | @previous = command 34 | 35 | # Condition was not evaluated yet 36 | descend: (engine, operation, continuation, scope) -> 37 | continuation = @delimit(continuation, @DESCEND) 38 | 39 | if @conditional 40 | path = continuation + @key 41 | unless engine.queries.hasOwnProperty(path) 42 | engine.queries[path] = 0 43 | branch = operation[@conditional] 44 | branch.command.solve(engine, branch, continuation, scope) 45 | 46 | @after([], engine.queries[path], engine, operation, continuation, scope) 47 | 48 | return false 49 | 50 | execute: (value) -> 51 | return value 52 | 53 | serialize: (operation, engine) -> 54 | return '@' + @toExpression(operation[1]) 55 | 56 | getOldValue: (engine, continuation) -> 57 | old = engine.updating.collections?[continuation] ? 0 58 | return old > 0 || (old == 0 && 1 / old != -Infinity) 59 | 60 | ascend: (engine, operation, continuation, scope, result) -> 61 | if conditions = (engine.updating.branches ||= []) 62 | if engine.indexOfTriplet(conditions, operation, continuation, scope) == -1 63 | length = continuation.length 64 | for condition, index in conditions by 3 65 | contd = conditions[index + 1] 66 | if contd.length >= length 67 | break 68 | # Top branch is switching 69 | else if continuation.substring(0, contd.length) == contd 70 | return 71 | 72 | conditions.splice(index || 0, 0, operation, continuation, scope) 73 | 74 | rebranch: (engine, operation, continuation, scope) -> 75 | increment = if @getOldValue(engine, continuation) then -1 else 1 76 | engine.queries[continuation] = (engine.queries[continuation] || 0) + increment 77 | 78 | inverted = operation[0] == 'unless' 79 | index = @conditional + 1 + ((increment == -1) ^ inverted) 80 | 81 | if branch = operation[index] 82 | engine.console.start(index == 2 && 'if' || 'else', operation[index], continuation) 83 | result = engine.input.Command(branch).solve(engine.input, branch, @delimit(continuation, @DESCEND), scope) 84 | engine.console.end(result) 85 | 86 | unbranch: (engine, operation, continuation, scope) -> 87 | if old = engine.updating.collections?[continuation] 88 | increment = if @getOldValue(engine, continuation) then -1 else 1 89 | if (engine.queries[continuation] += increment) == 0 90 | @clean(engine, continuation, continuation, operation, scope) 91 | return true 92 | 93 | # Capture commands generated by evaluation of arguments 94 | yield: (result, engine, operation, continuation, scope) -> 95 | # Condition result bubbled up, pick a branch 96 | unless operation.parent.indexOf(operation) > 1 97 | if operation[0].key? 98 | continuation = operation[0].key 99 | if scoped = operation[0].scope 100 | scope = engine.identity[scoped] 101 | 102 | if @bound 103 | continuation = @getPrefixPath(engine, continuation) 104 | 105 | path = @delimit(continuation, @DESCEND) + @key 106 | 107 | if !(value = engine.queries[path]) && result 108 | value = -0 109 | (engine.updating.collections ||= {})[path] = value 110 | 111 | if old = engine.updating.collections?[path] 112 | if @getOldValue(engine, path) == !!result 113 | return true 114 | 115 | @notify(engine, path, scope, result) 116 | 117 | 118 | return true 119 | 120 | # Detect condition that only observes variables outside of current scope 121 | Condition.Global = Condition.extend 122 | 123 | condition: (engine, operation, command) -> 124 | if command 125 | operation = operation[1] 126 | if operation[0] == 'get' || operation[1] == 'virtual' 127 | if operation.length == 2 128 | return false 129 | else if operation[0] == '&' 130 | return false 131 | for argument in operation 132 | if argument && argument.push && @condition(engine, argument) == false 133 | return false 134 | return true 135 | 136 | global: true 137 | 138 | # Detect condition that observes selectors 139 | Condition.Selector = Condition.extend 140 | 141 | condition: (engine, operation, command) -> 142 | if command 143 | operation = operation[1] 144 | if operation.command.type == 'Selector' && 145 | (operation.length > 1 || 146 | (operation.parent.command.type == 'Selector' && 147 | operation.parent.command.type == 'Iterator')) 148 | return true 149 | for argument in operation 150 | if argument && argument.push && @condition(engine, argument) 151 | return true 152 | return false 153 | 154 | bound: true 155 | 156 | Condition::advices = [Condition.Selector, Condition.Global] 157 | 158 | Condition.define 'if', {} 159 | Condition.define 'unless', { 160 | inverted: true 161 | } 162 | Condition.define 'else', { 163 | signature: [ 164 | then: ['Any'] 165 | ] 166 | 167 | linked: true 168 | 169 | conditional: null 170 | domains: null 171 | } 172 | Condition.define 'elseif', { 173 | linked: true 174 | } 175 | Condition.define 'elsif', { 176 | } 177 | 178 | module.exports = Condition 179 | -------------------------------------------------------------------------------- /src/engine/commands/Constraint.coffee: -------------------------------------------------------------------------------- 1 | Command = require('../Command') 2 | 3 | Constraint = Command.extend 4 | type: 'Constraint' 5 | 6 | signature: [ 7 | left: ['Variable', 'Number'], 8 | right: ['Variable', 'Number'] 9 | [ 10 | strength: ['String'] 11 | weight: ['Number'] 12 | ] 13 | ] 14 | 15 | # Provide logging for an action 16 | log: (args, engine, operation, continuation, scope, name) -> 17 | engine.console.push(name || operation[0], args, operation.hash ||= @toExpression(operation)) 18 | 19 | # Create a hash that represents substituted variables 20 | toHash: (meta) -> 21 | hash = '' 22 | if meta.values 23 | for property of meta.values 24 | hash += property 25 | return hash 26 | 27 | # Shared interface: 28 | 29 | # Find applied constraint by expression ignoring input variables 30 | fetch: (engine, operation) -> 31 | if operations = engine.operations?[operation.hash ||= @toExpression(operation)] 32 | for signature, constraint of operations 33 | if engine.constraints?.indexOf(constraint) > -1 34 | return constraint 35 | 36 | # Register constraints in variables to handle external mutations 37 | declare: (engine, constraint) -> 38 | for path, op of constraint.variables 39 | if definition = engine.variables[path] 40 | constraints = definition.constraints ||= [] 41 | unless constraints[0]?.operations[0]?.parent.values?[path]? 42 | if constraints.indexOf(constraint) == -1 43 | constraints.push(constraint) 44 | return 45 | 46 | # Unregister constraint from variables 47 | undeclare: (engine, constraint, quick) -> 48 | for path, op of constraint.variables 49 | if object = engine.variables[path] 50 | if (i = object.constraints?.indexOf(constraint)) > -1 51 | object.constraints.splice(i, 1) 52 | matched = false 53 | for other in object.constraints 54 | if engine.constraints.indexOf(other) > -1 && !other.operations[0].parent[0].values?[path]? 55 | matched = true 56 | break 57 | unless matched 58 | op.command.undeclare(engine, object, quick) 59 | #if constraint = engine.editing?['%' + path] 60 | # engine.unedit(path) 61 | return 62 | 63 | # Add constraint by tracker if it wasnt added before 64 | add: (constraint, engine, operation, continuation) -> 65 | other = @fetch(engine, operation) 66 | 67 | operations = constraint.operations ||= other?.operations || [] 68 | constraint.variables = operation.variables 69 | if operations.indexOf(operation) == -1 70 | for op, i in operations by -1 71 | if op.hash == operation.hash && op.parent[0].key == continuation 72 | operations.splice(i, 1) 73 | @unwatch engine, op, continuation 74 | operations.push(operation) 75 | 76 | 77 | @watch engine, operation, continuation 78 | if other != constraint 79 | if other 80 | @unset engine, other 81 | @set engine, constraint 82 | 83 | 84 | return 85 | 86 | reset: (engine) -> 87 | if engine.constrained 88 | for constraint in engine.constrained 89 | engine.Constraint::declare engine, constraint 90 | 91 | if engine.unconstrained 92 | for constraint in engine.unconstrained 93 | engine.Constraint::undeclare engine, constraint 94 | 95 | 96 | # Throw old solver away and make another when replacing constraints 97 | # To recompute things from a clean state 98 | 99 | if engine.instance._changed && engine.constrained && engine.unconstrained 100 | engine.instance = undefined 101 | engine.construct() 102 | if editing = engine.editing 103 | engine.editing = undefined 104 | 105 | for property, constraint of editing 106 | engine.edit(engine.variables[property], engine.variables[property].value) 107 | if engine.constraints 108 | for constraint in engine.constraints 109 | engine.Constraint::inject engine, constraint 110 | else 111 | if engine.unconstrained 112 | for constraint in engine.unconstrained 113 | engine.Constraint::eject engine, constraint 114 | if engine.constrained 115 | for constraint in engine.constrained 116 | engine.Constraint::inject engine, constraint 117 | engine.constrained ||= [] 118 | 119 | 120 | engine.unconstrained = undefined 121 | 122 | 123 | # Register constraint in the domain 124 | set: (engine, constraint) -> 125 | if (engine.constraints ||= []).indexOf(constraint) == -1 126 | engine.constraints.push(constraint) 127 | (engine.constrained ||= []).push(constraint) 128 | if (index = engine.unconstrained?.indexOf(constraint)) > -1 129 | engine.unconstrained.splice(index, 1) 130 | 131 | # Unregister constraint in the domain 132 | unset: (engine, constraint) -> 133 | if (index = engine.constraints.indexOf(constraint)) > -1 134 | engine.constraints.splice(index, 1) 135 | if (index = engine.constrained?.indexOf(constraint)) > -1 136 | engine.constrained.splice(index, 1) 137 | else 138 | if (engine.unconstrained ||= []).indexOf(constraint) == -1 139 | engine.unconstrained.push(constraint) 140 | for operation in constraint.operations 141 | if (path = operation.parent[0].key)? 142 | @unwatch(engine, operation, path) 143 | return 144 | 145 | unwatch: (engine, operation, continuation) -> 146 | if paths = engine.paths[continuation] 147 | if (i = paths.indexOf(operation)) > -1 148 | paths.splice(i, 1) 149 | if paths.length == 0 150 | delete engine.paths[continuation] 151 | 152 | watch: (engine, operation, continuation) -> 153 | engine.add continuation, operation 154 | 155 | # Remove constraint from domain by tracker string 156 | remove: (engine, operation, continuation) -> 157 | if constraint = @fetch(engine, operation) 158 | if operations = constraint.operations 159 | if (index = operations.indexOf(operation)) > -1 160 | operations.splice(index, 1) 161 | if operations.length == 0 162 | @unset(engine, constraint) 163 | @unwatch engine, operation, continuation 164 | 165 | # Find constraint in the domain for given variable 166 | find: (engine, variable) -> 167 | for other in variable.constraints 168 | if other.operations[0].variables[variable.name].domain == engine 169 | if engine.constraints.indexOf(other) > -1 170 | return true 171 | 172 | # Find groups of constraints that dont reference each other 173 | group: (constraints) -> 174 | groups = [] 175 | for constraint in constraints 176 | groupped = undefined 177 | vars = constraint.variables 178 | 179 | for group in groups by -1 180 | for other in group 181 | others = other.variables 182 | for path of vars 183 | if others[path] 184 | if groupped && groupped != group 185 | groupped.push.apply(groupped, group) 186 | groups.splice(groups.indexOf(group), 1) 187 | else 188 | groupped = group 189 | break 190 | if groups.indexOf(group) == -1 191 | break 192 | unless groupped 193 | groups.push(groupped = []) 194 | groupped.push(constraint) 195 | return groups 196 | 197 | # Separate independent groups of constraints into multiple domains 198 | split: (engine) -> 199 | groups = @group(engine.constraints).sort (a, b) -> 200 | al = a.length 201 | bl = b.length 202 | return bl - al 203 | 204 | separated = groups.splice(1) 205 | commands = [] 206 | if separated.length 207 | shift = 0 208 | for group, index in separated 209 | for constraint, index in group 210 | @unset engine, constraint 211 | for operation in constraint.operations 212 | commands.push operation.parent 213 | 214 | if commands?.length 215 | if commands.length == 1 216 | commands = commands[0] 217 | args = arguments 218 | if args.length == 1 219 | args = args[0] 220 | if commands.length == args.length 221 | equal = true 222 | for arg, i in args 223 | if commands.indexOf(arg) == -1 224 | equal = false 225 | break 226 | if equal 227 | throw new Error 'Trying to separate what was just added. Means loop. ' 228 | return engine.Command.orphanize commands 229 | 230 | module.exports = Constraint 231 | -------------------------------------------------------------------------------- /src/engine/commands/Iterator.coffee: -------------------------------------------------------------------------------- 1 | Command = require('../Command') 2 | 3 | class Iterator extends Command 4 | type: 'Iterator' 5 | 6 | signature: [ 7 | collection: ['Query', 'Selector'], 8 | body: ['Any'] 9 | ] 10 | 11 | List: true 12 | 13 | # Capture commands generated by css rule block 14 | yield: (result, engine, operation, continuation, scope) -> 15 | if operation.parent.indexOf(operation) == 1 16 | contd = @delimit(continuation, @DESCEND) 17 | op = operation.parent[2] 18 | op.command.solve engine, op, contd, result 19 | return true 20 | 21 | # Only evaluate first argument when going top down 22 | descend: (engine, operation, continuation, scope, ascender, ascending) -> 23 | argument = operation[1] 24 | command = argument.command || engine.Command(argument) 25 | argument.parent ||= operation 26 | 27 | command.solve(operation.domain || engine, argument, continuation, scope) 28 | return false 29 | 30 | 31 | Iterator.define 32 | # CSS rule 33 | 34 | "rule": 35 | index: 'rules' 36 | 37 | advices: [ 38 | (engine, operation, command) -> 39 | parent = operation 40 | while parent.parent 41 | parent = parent.parent 42 | operation.index = parent.rules = (parent.rules || 0) + 1 43 | return 44 | ] 45 | "each": {} 46 | 47 | module.exports = Iterator 48 | -------------------------------------------------------------------------------- /src/engine/commands/Variable.coffee: -------------------------------------------------------------------------------- 1 | Command = require('../Command') 2 | 3 | class Variable extends Command 4 | type: 'Variable' 5 | 6 | signature: [ 7 | property: ['String'] 8 | ] 9 | 10 | log: -> 11 | unlog: -> 12 | 13 | constructor: -> 14 | 15 | before: (args, engine, operation, continuation, scope, ascender, ascending) -> 16 | if (value = ascending?.values?[args[0]])? 17 | return value 18 | 19 | # Declare variable within domain, initial value is zero 20 | declare: (engine, name) -> 21 | variables = engine.variables 22 | unless variable = variables[name] 23 | variable = variables[name] = engine.variable(name) 24 | #if engine.nullified?[name] 25 | # delete engine.nullified[name] 26 | #if engine.replaced?[name] 27 | # delete engine.replaced[name] 28 | (engine.declared ||= {})[name] = variable 29 | return variable 30 | 31 | # Undeclare variable in given domain, outputs "null" once 32 | undeclare: (engine, variable, quick) -> 33 | if quick 34 | (engine.replaced ||= {})[variable.name] = variable 35 | else 36 | (engine.nullified ||= {})[variable.name] = variable 37 | if engine.declared?[variable.name] 38 | delete engine.declared[variable.name] 39 | 40 | delete engine.values[variable.name] 41 | engine.nullify(variable) 42 | engine.unedit(variable) 43 | 44 | # Algebraic expression 45 | class Variable.Expression extends Variable 46 | 47 | signature: [ 48 | left: ['Variable', 'Number'] 49 | right: ['Variable', 'Number'] 50 | ] 51 | 52 | Variable.Expression.algebra = 53 | '+': (left, right) -> 54 | return left + right 55 | 56 | '-': (left, right) -> 57 | return left - right 58 | 59 | '*': (left, right) -> 60 | return left * right 61 | 62 | '/': (left, right) -> 63 | return left / right 64 | 65 | 'min': (left, right) -> 66 | return Math.min(left, right) 67 | 68 | 'max': (left, right) -> 69 | return Math.max(left, right) 70 | 71 | module.exports = Variable -------------------------------------------------------------------------------- /src/engine/domains/Data.coffee: -------------------------------------------------------------------------------- 1 | ### Domain: Given values 2 | 3 | Provides values that don't need to be solved 4 | ### 5 | 6 | Domain = require('../Domain') 7 | Command = require('../Command') 8 | Variable = require('../commands/Variable') 9 | 10 | class Data extends Domain 11 | priority: 200 12 | static: true 13 | # Data domains usually dont use worker 14 | url: null 15 | 16 | # When should Data domain take ownership of variable? 17 | check: (id, property) -> 18 | return @output.properties[property] || # CSS property 19 | @properties[property]? || # Getter 20 | property.indexOf('intrinsic-') == 0 || # Explicitly intrinsic 21 | property.indexOf('computed-') == 0 || # Explicitly computed 22 | (@properties[id._gss_id || id] && # Known object + property pair 23 | @properties[(id._gss_id || id) + '[' + property + ']'])? 24 | 25 | verify: (object, property) -> 26 | path = @getPath(object, property) 27 | if @values.hasOwnProperty(path) 28 | @set(null, path, @fetch(path)) 29 | 30 | Data::Variable = Variable.extend {}, 31 | get: (path, engine, operation, continuation, scope) -> 32 | if meta = @getMeta(operation) 33 | continuation = meta.key 34 | scope ||= meta.scope && engine.identity[meta.scope] || engine.scope 35 | return engine.watch(null, path, operation, @delimit(continuation || ''), scope) 36 | 37 | Data::Variable.Expression = Variable.Expression.extend( 38 | before: (args, engine)-> 39 | for arg in args 40 | if !arg? || arg != arg 41 | return NaN 42 | ) 43 | Data::Variable.Expression.define(Variable.Expression.algebra) 44 | 45 | Data::Meta = Command.Meta.extend {}, 46 | 'object': 47 | 48 | execute: (result) -> 49 | return result 50 | 51 | descend: (engine, operation, continuation, scope, ascender, ascending) -> 52 | if ascender? 53 | return [ascending] 54 | meta = operation[0] 55 | scope = meta.scope && engine.identity[meta.scope] || engine.scope 56 | [operation[1].command.solve(engine, operation[1], meta.key, scope, undefined, operation[0])] 57 | 58 | module.exports = Data -------------------------------------------------------------------------------- /src/engine/domains/Finite.coffee: -------------------------------------------------------------------------------- 1 | Domain = require('../Domain') 2 | Command = require('../Command') 3 | Variable = require('../commands/Variable') 4 | Constraint = require('../commands/Constraint') 5 | 6 | class Finite extends Domain 7 | priority: -10 8 | 9 | #Solver: require('FD') 10 | 11 | constructor: -> 12 | super 13 | #@solver = new FD.Space 14 | 15 | variable: (name) -> 16 | return @solver.decl name 17 | 18 | Finite.Constraint = Constraint.extend {}, 19 | '==': (left, right) -> 20 | return @solver.eq left, right 21 | 22 | '!=': (left, right) -> 23 | return @solver.neq left, right 24 | 25 | 'distinct': () -> 26 | return @solver.distinct.apply(@solver, arguments) 27 | 28 | '<=': (left, right) -> 29 | return @solver.lte left, right 30 | 31 | '>=': (left, right) -> 32 | return @solver.gte left, right 33 | 34 | '<': (left, right) -> 35 | return @solver.lt left, right 36 | 37 | '>': (left, right) -> 38 | return @solver.gt left, right 39 | 40 | Finite.Variable = Variable.extend {group: 'finite'} 41 | Finite.Variable.Expression = Variable.Expression.extend {group: 'finite'}, 42 | 43 | '+': (left, right) -> 44 | return @solver.plus left, right 45 | 46 | '-': (left, right) -> 47 | return @solver.minus left, right 48 | 49 | '*': (left, right) -> 50 | return @solver.product left, right 51 | 52 | '/': (left, right) -> 53 | return @solver.divide left, right 54 | 55 | 56 | module.exports = Finite 57 | -------------------------------------------------------------------------------- /src/engine/domains/Input.coffee: -------------------------------------------------------------------------------- 1 | # Find, produce and observe variables 2 | Domain = require('../Domain') 3 | Command = require('../Command') 4 | 5 | Variable = require('../commands/Variable') 6 | Constraint = require('../commands/Constraint') 7 | 8 | class Input extends Domain 9 | displayName: 'Input' 10 | url: undefined 11 | helps: true 12 | 13 | Iterator: require('../commands/Iterator') 14 | Condition: require('../commands/Condition') 15 | 16 | Properties: class 17 | 18 | right: (scope) -> 19 | id = @identify(scope) 20 | return ['+', ['get', @getPath(id, 'x')], ['get', @getPath(id, 'width')]] 21 | 22 | bottom: (scope, path) -> 23 | id = @identify(scope) 24 | return ['+', ['get', @getPath(id, 'y')], ['get', @getPath(id, 'height')]] 25 | 26 | center: 27 | x: (scope, path) -> 28 | id = @identify(scope) 29 | return ['+', ['get', @getPath(id, 'x')], ['/', ['get', @getPath(id, 'width')], 2]] 30 | 31 | y: (scope, path) -> 32 | id = @identify(scope) 33 | return ['+', ['get', @getPath(id, 'y')], ['/', ['get', @getPath(id, 'height')], 2]] 34 | 35 | 36 | 37 | Input::Remove = Command.extend { 38 | signature: false 39 | 40 | extras: 1 41 | }, 42 | remove: (args..., engine)-> 43 | for path in args 44 | engine.triggerEvent('remove', path) 45 | return true 46 | 47 | 48 | # Catch-all class for unknown commands 49 | Input::Default = Command.Default.extend 50 | extras: 2 51 | 52 | execute: (args..., engine, operation) -> 53 | args.unshift operation[0] 54 | return args 55 | 56 | # Topmost unknown command returns processed operation back to engine 57 | Top = Input::Default.extend 58 | 59 | condition: (engine, operation) -> 60 | if parent = operation.parent 61 | if parent.command instanceof Input::Default 62 | return false 63 | 64 | operation.index ||= engine.input.index = (engine.input.index || 0) + 1 65 | return true 66 | 67 | extras: 4 68 | 69 | execute: (args..., engine, operation, continuation, scope) -> 70 | meta = 71 | key: @delimit(continuation) 72 | #index: operation.index 73 | 74 | if scope != engine.scope 75 | meta.scope = engine.identify(scope) 76 | 77 | args.unshift operation[0] 78 | wrapper = @produce(meta, args, operation) 79 | wrapper.index = operation.index 80 | args.parent = wrapper 81 | 82 | if domain = @domain?(engine, operation) 83 | wrapper.parent = operation.parent 84 | wrapper.domain ||= domain 85 | 86 | if engine.update(wrapper, undefined, undefined, domain) == undefined 87 | return engine.data.solve(args) 88 | 89 | produce: (meta, args)-> 90 | return [meta, args] 91 | 92 | domain: (engine, operation) -> 93 | if parent = operation.parent 94 | if domain = parent.command.domains?[parent.indexOf(operation)] 95 | return engine[domain] 96 | 97 | # Register subclasses to be dispatched by condition 98 | Input::Default::advices = [Top] 99 | 100 | # Array of commands, stops command propagation 101 | Input::List = Command.List 102 | 103 | # Global variable 104 | Input::Variable = Variable.extend { 105 | signature: [ 106 | property: ['String'] 107 | ], 108 | }, 109 | 'get': (property, engine, operation, continuation, scope) -> 110 | if engine.queries 111 | if scope == engine.scope 112 | scope = undefined 113 | object = engine.Query::getScope(engine, scope, continuation) 114 | return ['get', engine.getPath(object, property)] 115 | 116 | # Scoped variable 117 | Input::Variable.Getter = Input::Variable.extend { 118 | signature: [ 119 | object: ['Query', 'Selector', 'String'] 120 | property: ['String'] 121 | ] 122 | }, 123 | 'get': (object, property, engine, operation, continuation, scope) -> 124 | if engine.queries 125 | prefix = engine.Query::getScope(engine, object, continuation) 126 | 127 | if prop = engine.properties[property] 128 | unless prop.matcher 129 | return prop.call(engine, object, continuation) 130 | 131 | if !prefix && engine.data.check(engine.scope, property) 132 | prefix = engine.scope 133 | return ['get', engine.getPath(prefix, property)] 134 | 135 | # Proxy math that passes basic expressions along 136 | Input::Variable.Expression = Variable.Expression.extend {}, 137 | '+': (left, right) -> 138 | ['+', left, right] 139 | 140 | '-': (left, right) -> 141 | ['-', left, right] 142 | 143 | '/': (left, right) -> 144 | ['/', left, right] 145 | 146 | '*': (left, right) -> 147 | ['*', left, right] 148 | 149 | 150 | # Constant definition 151 | Input::Assignment = Command.extend { 152 | type: 'Assignment' 153 | 154 | signature: [ 155 | [object: ['Query', 'Selector']] 156 | property: ['String'] 157 | value: ['Variable'] 158 | ] 159 | }, 160 | '=': (object, name, value, engine) -> 161 | engine.input.set(object, name, value) 162 | 163 | # Style assignment 164 | Input::Assignment.Style = Input::Assignment.extend { 165 | signature: [ 166 | [object: ['Query', 'Selector']] 167 | property: ['String'] 168 | value: ['Any'] 169 | ] 170 | 171 | log: -> 172 | unlog: -> 173 | 174 | # Register assignment within parent rule 175 | # by its auto-incremented property local to operation list 176 | advices: [ 177 | (engine, operation, command) -> 178 | parent = operation 179 | rule = undefined 180 | while parent.parent 181 | if !rule && parent[0] == 'rule' 182 | rule = parent 183 | parent = parent.parent 184 | 185 | operation.index ||= parent.assignments = (parent.assignments || 0) + 1 186 | if rule 187 | (rule.properties ||= []).push(operation.index) 188 | return 189 | ] 190 | }, 191 | 'set': (object, property, value, engine, operation, continuation, scope) -> 192 | 193 | if engine.data 194 | engine.setStyle object || scope, property, value, continuation, operation 195 | else 196 | engine.input.set object || scope, property, value 197 | return 198 | 199 | module.exports = Input -------------------------------------------------------------------------------- /src/engine/domains/Linear.coffee: -------------------------------------------------------------------------------- 1 | Domain = require('../Domain') 2 | Command = require('../Command') 3 | Variable = require('../commands/Variable') 4 | Constraint = require('../commands/Constraint') 5 | c = require('cassowary') 6 | 7 | c.Strength.require = c.Strength.required 8 | 9 | class Linear extends Domain 10 | displayName: 'Linear' 11 | 12 | priority: 0 13 | 14 | Engine: c 15 | 16 | construct: () -> 17 | @paths ?= {} 18 | @instance = new c.SimplexSolver() 19 | @instance.autoSolve = false 20 | #@instance._store = [] 21 | if @console.level > 2 22 | c.debug = true 23 | c.trace = true 24 | 25 | perform: -> 26 | if @constrained 27 | @constrained = @suggested = undefined 28 | if @instance._needsSolving 29 | @instance.solve() 30 | return @instance._changed 31 | else if @suggested 32 | @suggested = undefined 33 | @instance.resolve() 34 | return @instance._changed 35 | 36 | unedit: (variable) -> 37 | if constraint = @editing?['%' + (variable.name || variable)] 38 | #@instance.removeConstraint(constraint) 39 | cei = @instance._editVarMap.get(constraint.variable) 40 | @instance.removeColumn(cei.editMinus) 41 | @instance._editVarMap.delete(constraint.variable) 42 | delete @editing[(variable.name || variable)] 43 | 44 | #@removeConstraint(constraint) 45 | 46 | edit: (variable, strength, weight, continuation) -> 47 | unless @editing?[variable.name] 48 | constraint = new c.EditConstraint(variable, @strength(strength, 'strong'), @weight(weight)) 49 | constraint.variable = variable 50 | @Constraint::inject @, constraint 51 | (@editing ||= {})[variable.name] = constraint 52 | 53 | return constraint 54 | 55 | nullify: (variable, full) -> 56 | @instance._externalParametricVars.delete(variable) 57 | variable.value = 0 58 | #if full 59 | #@instance.rows.delete(variable) 60 | 61 | suggest: (path, value, strength, weight, continuation) -> 62 | if typeof path == 'string' 63 | unless variable = @variables[path] 64 | variable = @Variable::declare(@, path) 65 | else 66 | variable = path 67 | 68 | @edit(variable, strength, weight, continuation) 69 | @instance.suggestValue(variable, value) 70 | variable.value = value 71 | @suggested = true 72 | return variable 73 | 74 | variable: (name) -> 75 | return new c.Variable name: name 76 | 77 | strength: (strength, byDefault = 'medium') -> 78 | return strength && c.Strength[strength] || c.Strength[byDefault] 79 | 80 | weight: (weight, operation) -> 81 | #if index = operation?.parent[0].index 82 | # return index / 1000 83 | return weight 84 | 85 | # Capture values coming from other domains 86 | Linear.Mixin = 87 | yield: (result, engine, operation, continuation, scope, ascender) -> 88 | if typeof result == 'number' 89 | return operation.parent.domain.suggest('%' + operation.command.toExpression(operation), result, 'require') 90 | 91 | 92 | Linear::Constraint = Constraint.extend { 93 | 94 | before: (args, engine, operation, continuation, scope, ascender, ascending) -> 95 | return @get(engine, operation, ascending) 96 | 97 | after: (args, result, engine, operation, continuation, scope, ascender, ascending) -> 98 | if result.hashCode 99 | return ((engine.operations ||= {})[operation.hash ||= @toExpression(operation)] ||= {})[@toHash(ascending)] ||= result 100 | return result 101 | 102 | # Get cached operation by expression and set of input variables 103 | get: (engine, operation, scope) -> 104 | return engine.operations?[operation.hash ||= @toExpression(operation)]?[@toHash(scope)] 105 | 106 | yield: Linear.Mixin.yield 107 | 108 | inject: (engine, constraint) -> 109 | engine.instance.addConstraint(constraint) 110 | 111 | eject: (engine, constraint) -> 112 | engine.instance.removeConstraint(constraint) 113 | 114 | }, 115 | '==': (left, right, strength, weight, engine, operation) -> 116 | return new c.Equation(left, right, engine.strength(strength), engine.weight(weight, operation)) 117 | 118 | '<=': (left, right, strength, weight, engine, operation) -> 119 | return new c.Inequality(left, c.LEQ, right, engine.strength(strength), engine.weight(weight, operation)) 120 | 121 | '>=': (left, right, strength, weight, engine, operation) -> 122 | return new c.Inequality(left, c.GEQ, right, engine.strength(strength), engine.weight(weight, operation)) 123 | 124 | '<': (left, right, strength, weight, engine, operation) -> 125 | return new c.Inequality(left, c.LEQ, engine['+'](right, 1), engine.strength(strength), engine.weight(weight, operation)) 126 | 127 | '>': (left, right, strength, weight, engine, operation) -> 128 | return new c.Inequality(left, c.GEQ, engine['+'](right, 1), engine.strength(strength), engine.weight(weight, operation)) 129 | 130 | 131 | Linear::Variable = Variable.extend Linear.Mixin, 132 | get: (path, engine, operation) -> 133 | variable = @declare(engine, path) 134 | engine.unedit(variable) 135 | return variable 136 | 137 | Linear::Variable.Expression = Variable.Expression.extend Linear.Mixin, 138 | 139 | '+': (left, right) -> 140 | return c.plus(left, right) 141 | 142 | '-': (left, right) -> 143 | return c.minus(left, right) 144 | 145 | '*': (left, right) -> 146 | return c.times(left, right) 147 | 148 | '/': (left, right) -> 149 | return c.divide(left, right) 150 | 151 | # Handle constraints wrapped into meta constructs provided by Input 152 | Linear::Meta = Command.Meta.extend {}, 153 | 'object': 154 | execute: (constraint, engine, operation) -> 155 | if constraint?.hashCode? 156 | operation[1].command.add(constraint, engine, operation[1], operation[0].key) 157 | 158 | descend: (engine, operation) -> 159 | 160 | if meta = operation[0] 161 | continuation = meta.key 162 | scope = meta.scope && engine.identity[meta.scope] || engine.scope 163 | 164 | operation[1].parent = operation 165 | [ 166 | operation[1].command.solve(engine, operation[1], continuation, scope, undefined, operation[0]) 167 | engine, 168 | operation 169 | ] 170 | 171 | Linear::Stay = Command.extend { 172 | signature: [ 173 | value: ['Variable'] 174 | ] 175 | }, 176 | stay: (value, engine, operation) -> 177 | engine.suggested = true 178 | engine.instance.addStay(value) 179 | return 180 | 181 | Linear::Remove = Command.extend { 182 | extras: 1 183 | 184 | signature: false 185 | }, 186 | 187 | remove: (args ..., engine) -> 188 | engine.remove.apply(engine, args) 189 | 190 | 191 | 192 | 193 | # Phantom js doesnt enforce order of numerical keys in plain objects. 194 | # The hack enforces arrays as base structure. 195 | do -> 196 | unless c.isUnordered? 197 | obj = {'10': 1, '9': 1} 198 | for property of obj 199 | break 200 | if c.isUnordered = (property > 9) 201 | set = c.HashTable.prototype.set 202 | c.HashTable.prototype.set = -> 203 | if !@_store.push 204 | store = @_store 205 | @_store = [] 206 | for property of store 207 | @_store[property] = store[property] 208 | 209 | return set.apply(@, arguments) 210 | module.exports = Linear 211 | -------------------------------------------------------------------------------- /src/engine/domains/Output.coffee: -------------------------------------------------------------------------------- 1 | Data = require('./Data') 2 | Constraint = require('../commands/Constraint') 3 | 4 | class Output extends Data 5 | displayName: 'Output' 6 | immutable: true 7 | priority: -200 8 | finalized: true 9 | 10 | Output::Constraint = Constraint.extend { 11 | signature: [ 12 | left: ['Variable', 'Number', 'Constraint'], 13 | right: ['Variable', 'Number', 'Constraint'] 14 | ] 15 | }, 16 | "&&": (a, b) -> 17 | return a && b 18 | 19 | "||": (a, b) -> 20 | return a || b 21 | 22 | "!=": (a, b) -> 23 | return a != b 24 | 25 | "==": (a, b) -> 26 | return a == b 27 | 28 | "<=": (a, b) -> 29 | return a <= b 30 | 31 | ">=": (a, b) -> 32 | return a >= b 33 | 34 | "<": (a, b) -> 35 | return a < b 36 | 37 | ">": (a, b) -> 38 | return a > b 39 | 40 | module.exports = Output 41 | -------------------------------------------------------------------------------- /src/engine/utilities/Console.coffee: -------------------------------------------------------------------------------- 1 | # Console shim & wrapper. Used to output collapsible table view. 2 | 3 | class Console 4 | constructor: (@level) -> 5 | @level ?= self.GSS_LOG ? parseFloat(self?.location?.search.match(/log=([\d.]+)/)?[1] || 0) 6 | if !Console.bind 7 | @level = 0 8 | @stack = [] 9 | @buffer = [] 10 | self.addEventListener 'error', @onError, true 11 | 12 | methods: ['log', 'warn', 'info', 'error', 'group', 'groupEnd', 'groupCollapsed', 'time', 'timeEnd', 'profile', 'profileEnd'] 13 | groups: 0 14 | 15 | onError: (e) => 16 | true while @pop(e) 17 | 18 | compile: (engine) -> 19 | @DESCEND = engine.Command.prototype.DESCEND 20 | 21 | push: (a, b, c, type) -> 22 | if @level > 0.5 || type 23 | unless @buffer.length 24 | if @level > 1 25 | console?.profile() 26 | index = @buffer.push(a, b, c, undefined, type || @row) 27 | @stack.push(index - 5) 28 | 29 | pop: (d, type = @row, update) -> 30 | if (@level > 0.5 || type != @row) && @stack.length 31 | index = @stack.pop() 32 | @buffer[index + 3] = d 33 | if type != @row 34 | @buffer[index + 2] = @getTime(@buffer[index + 2]) 35 | unless @stack.length 36 | @flush() 37 | return true 38 | return false 39 | 40 | flush: -> 41 | if @level > 1 42 | console?.profileEnd() 43 | for item, index in @buffer by 5 44 | @buffer[index + 4].call(@, @buffer[index], @buffer[index + 1], @buffer[index + 2], @buffer[index + 3]) 45 | @buffer = [] 46 | 47 | pad: (object, length = 17) -> 48 | if object.length > length 49 | object.substring(0, length - 1) + '…' 50 | else 51 | object + Array(length - object.length).join(' ') 52 | 53 | openGroup: (name, reason = '', time, result = '') -> 54 | 55 | fmt = '%c%s' 56 | 57 | switch typeof reason 58 | when 'string' 59 | fmt += '%s' 60 | reason = @pad(reason, 16) 61 | when 'object' 62 | fmt += '%O\t' 63 | unless reason.length? 64 | fmt += '\t' 65 | 66 | switch typeof result 67 | when 'string' 68 | fmt += '%s' 69 | result = @pad(result, 17) 70 | when 'object' 71 | fmt += '%O\t' 72 | unless result.length > 9 73 | fmt += '\t' 74 | 75 | fmt += ' %c%sms' 76 | 77 | name = @pad(name, 13) 78 | 79 | if @level <= 1.5 80 | method = 'groupCollapsed' 81 | @[method || 'group'](fmt, 'font-weight: normal', name, reason, result, 'color: #999; font-weight: normal; font-style: italic;', time) 82 | 83 | closeGroup: -> 84 | @groupEnd() 85 | 86 | stringify: (obj) -> 87 | return '' unless obj 88 | if obj.push 89 | obj.map @stringify, @ 90 | else if obj.nodeType 91 | obj._gss_id 92 | else if obj.toString != Object.prototype.toString 93 | obj.toString() 94 | else if obj.displayName 95 | return obj.displayName 96 | else 97 | JSON.stringify(obj) 98 | 99 | debug: (exp) -> 100 | document.location = document.location.toString().replace(/[&?]breakpoint=[^&]+|$/, 101 | ((document.location.search.indexOf('?') > -1) && '&' || '?') + 102 | 'breakpoint=' + exp.trim().replace(/\r?\n+|\r|\s+/g, ' ')) 103 | 104 | breakpoint: decodeURIComponent (document?.location.search.match(/breakpoint=([^&]+)/, '') || ['',''])[1] 105 | 106 | row: (a, b = '', c = '', d = '') -> 107 | return if @level < 1 108 | a = a.name || a 109 | return if typeof a != 'string' 110 | p1 = Array(4 - Math.floor((a.length + 1) / 4)).join('\t') 111 | 112 | if (index = c.indexOf(@DESCEND)) > -1 113 | if c.indexOf('style[type*="gss"]') > -1 114 | c = c.substring(index + 1) 115 | 116 | c = c.replace /\r?\n|\r|\s+/g, ' ' 117 | 118 | fmt = '%c%s' 119 | 120 | switch typeof b 121 | when 'string' 122 | fmt += '%s' 123 | b = @pad(b, 14) 124 | when 'object' 125 | fmt += '%O\t' 126 | unless b.push 127 | b = [b] 128 | #fmt += '' 129 | 130 | switch typeof d 131 | when 'string', 'boolean', 'number' 132 | fmt += ' %s ' 133 | d = @pad(String(d), 17) 134 | when 'object' 135 | fmt += ' %O\t ' 136 | if d.item 137 | d = Array.prototype.slice.call(d) 138 | else unless d.length? 139 | d = [d] 140 | 141 | if document? 142 | @log(fmt + '%c%s', 143 | 'color: #666', @pad(a, 11), 144 | b, 145 | d, 146 | 'color: #999', c) 147 | else 148 | @log a, b, c 149 | 150 | start: (reason, name) -> 151 | @push(reason, name, @getTime(), @openGroup) 152 | 153 | end: (result) -> 154 | @buffer.push(undefined, undefined, undefined, undefined, @closeGroup) 155 | @pop(result, @openGroup, true) 156 | 157 | getTime: (other, time) -> 158 | time ||= performance?.now?() || Date.now?() || + (new Date) 159 | return time if time && !other 160 | return Math.floor((time - other) * 100) / 100 161 | 162 | 163 | for method in Console::methods 164 | Console::[method] = do (method) -> 165 | return -> 166 | if method == 'group' || method == 'groupCollapsed' 167 | Console::groups++ 168 | else if method == 'groupEnd' 169 | return unless Console::groups 170 | Console::groups-- 171 | 172 | if @level || method == 'error' 173 | console?[method]?(arguments...) 174 | 175 | module.exports = Console 176 | -------------------------------------------------------------------------------- /src/engine/utilities/Exporter.coffee: -------------------------------------------------------------------------------- 1 | class Exporter 2 | constructor: (@engine) -> 3 | return unless @command = location.search.match(/export=([a-z0-9]+)/)?[1] 4 | 5 | @preexport() 6 | 7 | preexport: => 8 | # Let every element get an ID 9 | if (scope = @engine.scope).nodeType == 9 10 | scope = @engine.scope.body 11 | @engine.identify(scope) 12 | for element in scope.getElementsByTagName('*') 13 | if element.tagName != 'SCRIPT' && 14 | (element.tagName != 'STYLE' || element.getAttribute('type')?.indexOf('gss') > -1) 15 | @engine.identify(element) 16 | if window.Sizes 17 | @sizes = [] 18 | for pairs in window.Sizes 19 | for width in pairs[0] 20 | for height in pairs[1] 21 | @sizes.push(width + 'x' + height) 22 | 23 | if @command.indexOf('x') > -1 24 | [width, height] = @command.split('x') 25 | baseline = 72 26 | width = parseInt(width) * baseline 27 | height = parseInt(height) * baseline 28 | window.addEventListener 'load', => 29 | localStorage[@command] = JSON.stringify(@export()) 30 | @postexport() 31 | 32 | document.body.style.width = width + 'px' 33 | @engine.data.properties['::window[height]'] = -> 34 | return height 35 | @engine.data.properties['::window[width]'] = -> 36 | return width 37 | 38 | else 39 | if @command == 'true' 40 | localStorage.clear() 41 | @postexport() 42 | 43 | postexport: => 44 | for size in @sizes 45 | unless localStorage[size] 46 | location.search = location.search.replace(/[&?]export=([a-z0-9])+/, '') + '?export=' + size 47 | return 48 | result = {} 49 | for property, value of localStorage 50 | if property.match(/^\d+x\d+$/) 51 | result[property] = JSON.parse(value) 52 | document.write(JSON.stringify(result)) 53 | 54 | export: -> 55 | values = {} 56 | for path, value of @engine.values 57 | if (index = path.indexOf('[')) > -1 && path.indexOf('"') == -1 58 | property = @engine.data.camelize(path.substring(index + 1, path.length - 1)) 59 | id = path.substring(0, index) 60 | if property == 'x' || property == 'y' || document.body.style[property] != undefined 61 | unless @engine.values[id + '[intrinsic-' + property + ']']? 62 | values[path] = Math.ceil(value) 63 | values.stylesheets = @engine.document.Stylesheet.export() 64 | return values 65 | 66 | module.exports = Exporter -------------------------------------------------------------------------------- /vendor/weakmap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | if (typeof window != 'undefined') 8 | if (typeof WeakMap === 'undefined') { 9 | (function() { 10 | var defineProperty = Object.defineProperty; 11 | var counter = Date.now() % 1e9; 12 | 13 | var WeakMap = function() { 14 | this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); 15 | }; 16 | 17 | WeakMap.prototype = { 18 | set: function(key, value) { 19 | var entry = key[this.name]; 20 | if (entry && entry[0] === key) 21 | entry[1] = value; 22 | else 23 | defineProperty(key, this.name, {value: [key, value], writable: true}); 24 | }, 25 | get: function(key) { 26 | var entry; 27 | return (entry = key[this.name]) && entry[0] === key ? 28 | entry[1] : undefined; 29 | }, 30 | 'delete': function(key) { 31 | this.set(key, undefined); 32 | } 33 | }; 34 | 35 | window.WeakMap = WeakMap; 36 | })(); 37 | } 38 | --------------------------------------------------------------------------------