ranger-compiler 3.5.1 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/CHANGELOG.md +1064 -20
  2. package/LICENSE +3 -1
  3. package/README.md +123 -28
  4. package/dist/Lang.rgr +1188 -290
  5. package/dist/api.d.ts +2343 -879
  6. package/dist/api.js +79923 -56921
  7. package/dist/lib/JSON.rgr +102 -91
  8. package/dist/lib/Shell.rgr +4 -4
  9. package/dist/lib/apple/AppleToolchain.rgr +4 -4
  10. package/dist/lib/apple/README.md +5 -4
  11. package/dist/lib/apple/apple_test.rgr +1 -1
  12. package/dist/lib/core/README.md +1 -1
  13. package/dist/lib/evg/EVG.rgr +12 -0
  14. package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
  15. package/dist/lib/evg/EVGA11yTree.rgr +894 -0
  16. package/dist/lib/evg/EVGBox.rgr +267 -0
  17. package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
  18. package/dist/lib/evg/EVGCodepoint.rgr +316 -0
  19. package/dist/lib/evg/EVGColor.rgr +700 -0
  20. package/dist/lib/evg/EVGCommands.rgr +177 -0
  21. package/dist/lib/evg/EVGComponent.rgr +331 -0
  22. package/dist/lib/evg/EVGComponentTest.rgr +387 -0
  23. package/dist/lib/evg/EVGConnector.rgr +541 -0
  24. package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
  25. package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
  26. package/dist/lib/evg/EVGEasing.rgr +370 -0
  27. package/dist/lib/evg/EVGEffectTest.rgr +253 -0
  28. package/dist/lib/evg/EVGElement.rgr +4914 -0
  29. package/dist/lib/evg/EVGFixedTest.rgr +289 -0
  30. package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
  31. package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
  32. package/dist/lib/evg/EVGFling.rgr +244 -0
  33. package/dist/lib/evg/EVGFocus.rgr +400 -0
  34. package/dist/lib/evg/EVGFocusTest.rgr +399 -0
  35. package/dist/lib/evg/EVGGradient.rgr +322 -0
  36. package/dist/lib/evg/EVGGrapheme.rgr +190 -0
  37. package/dist/lib/evg/EVGGrid.rgr +975 -0
  38. package/dist/lib/evg/EVGHitTest.rgr +208 -0
  39. package/dist/lib/evg/EVGHoles.rgr +294 -0
  40. package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
  41. package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
  42. package/dist/lib/evg/EVGHostTree.rgr +834 -0
  43. package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
  44. package/dist/lib/evg/EVGImageDecode.rgr +117 -0
  45. package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
  46. package/dist/lib/evg/EVGInspect.rgr +883 -0
  47. package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
  48. package/dist/lib/evg/EVGJsonTest.rgr +535 -0
  49. package/dist/lib/evg/EVGLayout.rgr +4220 -0
  50. package/dist/lib/evg/EVGMeasure.rgr +1267 -0
  51. package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
  52. package/dist/lib/evg/EVGPatch.rgr +1721 -0
  53. package/dist/lib/evg/EVGPatchTest.rgr +692 -0
  54. package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
  55. package/dist/lib/evg/EVGReconcile.rgr +226 -0
  56. package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
  57. package/dist/lib/evg/EVGReject.rgr +126 -0
  58. package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
  59. package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
  60. package/dist/lib/evg/EVGRuler.rgr +232 -0
  61. package/dist/lib/evg/EVGRulerTest.rgr +172 -0
  62. package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
  63. package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
  64. package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
  65. package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
  66. package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
  67. package/dist/lib/evg/EVGText.rgr +105 -0
  68. package/dist/lib/evg/EVGTextEngine.rgr +536 -0
  69. package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
  70. package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
  71. package/dist/lib/evg/EVGToolbar.rgr +1369 -0
  72. package/dist/lib/evg/EVGTransition.rgr +728 -0
  73. package/dist/lib/evg/EVGTreeJson.rgr +721 -0
  74. package/dist/lib/evg/EVGUnit.rgr +554 -0
  75. package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
  76. package/dist/lib/evg/EvgApp.rgr +189 -0
  77. package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
  78. package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
  79. package/dist/lib/evg/EvgHost.rgr +451 -0
  80. package/dist/lib/evg/EvgTest.rgr +91 -0
  81. package/dist/lib/evg/EvgTraceColor.rgr +236 -0
  82. package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
  83. package/dist/lib/evg/EvgTraceFit.rgr +767 -0
  84. package/dist/lib/evg/EvgTracePath.rgr +435 -0
  85. package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
  86. package/dist/lib/evg/EvgViewport.rgr +313 -0
  87. package/dist/lib/evg/FxDemoDoc.rgr +185 -0
  88. package/dist/lib/evg/HOSTS.md +227 -0
  89. package/dist/lib/evg/ISSUES.md +835 -0
  90. package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
  91. package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
  92. package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
  93. package/dist/lib/evg/PLAN_EVG.md +518 -0
  94. package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
  95. package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
  96. package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
  97. package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
  98. package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
  99. package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
  100. package/dist/lib/evg/PathBuilder.rgr +333 -0
  101. package/dist/lib/evg/README.md +1236 -0
  102. package/dist/lib/evg/SPEC.md +1252 -0
  103. package/dist/lib/evg/SVGPathParser.rgr +1199 -0
  104. package/dist/lib/evg/SvgParser.rgr +1736 -0
  105. package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
  106. package/dist/lib/evg/VectorShapes.rgr +331 -0
  107. package/dist/lib/evg/VectorStroke.rgr +107 -0
  108. package/dist/lib/evg/VectorViewBox.rgr +380 -0
  109. package/dist/lib/evg/agent/README.md +290 -0
  110. package/dist/lib/evg/agent/evg_agent.rgr +899 -0
  111. package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
  112. package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
  113. package/dist/lib/evg/agent/fixtures/connector.css +63 -0
  114. package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
  115. package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
  116. package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
  117. package/dist/lib/evg/agent/fixtures/popover.css +99 -0
  118. package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
  119. package/dist/lib/evg/agent/roundtrip.sh +74 -0
  120. package/dist/lib/evg/agent/smoke.sh +230 -0
  121. package/dist/lib/evg/android/README.md +97 -0
  122. package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
  123. package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
  124. package/dist/lib/evg/android/androidstubs/App.kt +30 -0
  125. package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
  126. package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
  127. package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
  128. package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
  129. package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
  130. package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
  131. package/dist/lib/evg/android/androidstubs/View.kt +113 -0
  132. package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
  133. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
  134. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
  135. package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
  136. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
  137. package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
  138. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
  139. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
  140. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
  141. package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
  142. package/dist/lib/evg/apple/README.md +128 -0
  143. package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
  144. package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
  145. package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
  146. package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
  147. package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
  148. package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
  149. package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
  150. package/dist/lib/evg/bench/README.md +64 -0
  151. package/dist/lib/evg/bench/layout-bench.mjs +316 -0
  152. package/dist/lib/evg/bench/layout-cases.mjs +510 -0
  153. package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
  154. package/dist/lib/evg/bin/.gitignore +17 -0
  155. package/dist/lib/evg/evg_test.rgr +313 -0
  156. package/dist/lib/evg/gl/README.md +198 -0
  157. package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
  158. package/dist/lib/evg/gl/blur-check.mjs +399 -0
  159. package/dist/lib/evg/gl/boxmodel.json +1 -0
  160. package/dist/lib/evg/gl/demo.html +46 -0
  161. package/dist/lib/evg/gl/effect-presets.css +285 -0
  162. package/dist/lib/evg/gl/effect-presets.js +63 -0
  163. package/dist/lib/evg/gl/effect-shots.mjs +135 -0
  164. package/dist/lib/evg/gl/evg-a11y.js +566 -0
  165. package/dist/lib/evg/gl/evg-binary.js +160 -0
  166. package/dist/lib/evg/gl/evg-engine.js +296 -0
  167. package/dist/lib/evg/gl/evg-fx.js +237 -0
  168. package/dist/lib/evg/gl/evg-gestures.js +209 -0
  169. package/dist/lib/evg/gl/evg-list.js +167 -0
  170. package/dist/lib/evg/gl/evg-measure.js +186 -0
  171. package/dist/lib/evg/gl/evg-textinput.js +303 -0
  172. package/dist/lib/evg/gl/evg-view.js +144 -0
  173. package/dist/lib/evg/gl/evg-webgl.js +3942 -0
  174. package/dist/lib/evg/gl/fx-check.mjs +998 -0
  175. package/dist/lib/evg/gl/fx-demo.html +166 -0
  176. package/dist/lib/evg/gl/fx-demo.js +2 -0
  177. package/dist/lib/evg/gl/fx-serve.mjs +47 -0
  178. package/dist/lib/evg/gl/gestures-check.mjs +189 -0
  179. package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
  180. package/dist/lib/evg/gl/measure-check.mjs +135 -0
  181. package/dist/lib/evg/gl/rotation-check.mjs +207 -0
  182. package/dist/lib/evg/gl/shift-check.mjs +113 -0
  183. package/dist/lib/evg/gl/stroke-check.mjs +168 -0
  184. package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
  185. package/dist/lib/evg/gl/view-check.mjs +343 -0
  186. package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
  187. package/dist/lib/evg/html/evg-dom.js +318 -0
  188. package/dist/lib/evg/html/evg-html.js +600 -0
  189. package/dist/lib/evg/inspect/README.md +415 -0
  190. package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
  191. package/dist/lib/evg/inspect/evg-inspect.js +947 -0
  192. package/dist/lib/evg/inspect/shots/css.png +0 -0
  193. package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
  194. package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
  195. package/dist/lib/evg/inspect/shots/state.png +0 -0
  196. package/dist/lib/evg/inspect/shots.mjs +226 -0
  197. package/dist/lib/evg/oracle/css-blur.json +576 -0
  198. package/dist/lib/evg/oracle/css-box.json +157 -0
  199. package/dist/lib/evg/oracle/css-timing.json +709 -0
  200. package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
  201. package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
  202. package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
  203. package/dist/lib/evg/original/EVGColor.clj +310 -0
  204. package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
  205. package/dist/lib/evg/original/SVGPath.rgr +627 -0
  206. package/dist/lib/evg/original/Vec2.crgr +103 -0
  207. package/dist/lib/evg/ranger.json +9 -0
  208. package/dist/lib/evg/showcase/README.md +246 -0
  209. package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
  210. package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
  211. package/dist/lib/evg/showcase/build.mjs +651 -0
  212. package/dist/lib/evg/showcase/pages/album.tsx +30 -0
  213. package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
  214. package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
  215. package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
  216. package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
  217. package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
  218. package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
  219. package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
  220. package/dist/lib/evg/showcase/pages/more.tsx +332 -0
  221. package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
  222. package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
  223. package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
  224. package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
  225. package/dist/lib/evg/showcase/pages/units.tsx +35 -0
  226. package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
  227. package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
  228. package/dist/lib/evg/showcase/pages/views.tsx +223 -0
  229. package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
  230. package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
  231. package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
  232. package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
  233. package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
  234. package/dist/lib/evg/showcase/themes/more-default.css +128 -0
  235. package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
  236. package/dist/lib/evg/showcase/themes/showcase.css +508 -0
  237. package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
  238. package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
  239. package/dist/lib/evg/showcase/themes/views-default.css +15 -0
  240. package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
  241. package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
  242. package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
  243. package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
  244. package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
  245. package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
  246. package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
  247. package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
  248. package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
  249. package/dist/lib/evg/web/responsive/README.md +97 -0
  250. package/dist/lib/evg/web/responsive/build.mjs +90 -0
  251. package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
  252. package/dist/lib/evg/web/responsive/index.html +177 -0
  253. package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
  254. package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
  255. package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
  256. package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
  257. package/dist/lib/evg/web/tools/minify.mjs +60 -0
  258. package/dist/lib/evg/web/tracer/build.mjs +87 -0
  259. package/dist/lib/evg/web/tracer/index.html +2805 -0
  260. package/dist/lib/evg/web/tracer/sample.png +0 -0
  261. package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
  262. package/dist/lib/evgr/Cargo.lock +21 -0
  263. package/dist/lib/evgr/Cargo.toml +19 -0
  264. package/dist/lib/evgr/README.md +140 -0
  265. package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
  266. package/dist/lib/evgr/bench/compare.mjs +252 -0
  267. package/dist/lib/evgr/bench/speed.mjs +277 -0
  268. package/dist/lib/evgr/bin/.gitignore +3 -0
  269. package/dist/lib/evgr/src/bin/bench.rs +106 -0
  270. package/dist/lib/evgr/src/bin/smoke.rs +13 -0
  271. package/dist/lib/evgr/src/grid.rs +730 -0
  272. package/dist/lib/evgr/src/lib.rs +1003 -0
  273. package/dist/lib/evgr/src/style.rs +468 -0
  274. package/dist/lib/evgr/src/text.rs +83 -0
  275. package/dist/lib/image/BitReader.rgr +171 -0
  276. package/dist/lib/image/Buffer.rgr +173 -0
  277. package/dist/lib/image/DCT.rgr +283 -0
  278. package/dist/lib/image/Deflate.rgr +339 -0
  279. package/dist/lib/image/HuffmanDecoder.rgr +181 -0
  280. package/dist/lib/image/ImageBuffer.rgr +706 -0
  281. package/dist/lib/image/JPEGDecoder.rgr +808 -0
  282. package/dist/lib/image/PNGDecoder.rgr +544 -0
  283. package/dist/lib/image/PNGEncoder.rgr +388 -0
  284. package/dist/lib/image/PPMImage.rgr +224 -0
  285. package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
  286. package/dist/lib/image/README.md +33 -0
  287. package/dist/lib/image/RasterBuffer.rgr +294 -0
  288. package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
  289. package/dist/lib/image/WebPDecoder.rgr +433 -0
  290. package/dist/lib/image/WebPLossless.rgr +1035 -0
  291. package/dist/lib/image/WebPLossy.rgr +1929 -0
  292. package/dist/lib/image/ranger.json +9 -0
  293. package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
  294. package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
  295. package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
  296. package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
  297. package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
  298. package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
  299. package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
  300. package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
  301. package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
  302. package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
  303. package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
  304. package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
  305. package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
  306. package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
  307. package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
  308. package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
  309. package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
  310. package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
  311. package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
  312. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
  313. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
  314. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
  315. package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
  316. package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
  317. package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
  318. package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
  319. package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
  320. package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
  321. package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
  322. package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
  323. package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
  324. package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
  325. package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
  326. package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
  327. package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
  328. package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
  329. package/dist/lib/image/tests/run_webp_tests.sh +39 -0
  330. package/dist/lib/rust/RsJson.rgr +468 -0
  331. package/dist/lib/rust/RsPrelude.rgr +1663 -0
  332. package/dist/lib/shell_test.rgr +7 -7
  333. package/dist/lib/stdlib.rgr +21 -5
  334. package/dist/lib/zip/ranger.json +6 -0
  335. package/dist/rgrc.js +87318 -67610
  336. package/package.json +1010 -1040
  337. package/dist/README.md +0 -117
  338. package/dist/package.json +0 -47
@@ -0,0 +1,1417 @@
1
+ ; SPDX-License-Identifier: MIT
2
+
3
+ ; ==============================================================================
4
+ ; EVGTimingTest — the timing functions, against the browser's own numbers
5
+ ; ==============================================================================
6
+ ;
7
+ ; Every expected value in this file was MEASURED, not derived. `npm run
8
+ ; evg:timing:oracle` drives a real `Animation` in Chromium, pauses it, sets
9
+ ; `currentTime` by hand and reads the interpolated value back at thirteen input
10
+ ; progresses; `oracle/css-timing.json` is that capture and the tables below are
11
+ ; transcribed from it.
12
+ ;
13
+ ; Which is the point. The control points behind `ease` are quotable from a
14
+ ; specification, but whether an implementation of the Bézier solver actually
15
+ ; lands on the same curve is a question only numbers can answer — and a solver
16
+ ; that is right at t=0.5 and wrong at t=0.05 looks perfect in a demo. The
17
+ ; tolerance is 1e-4, comfortably inside the browser's own rounding and far
18
+ ; tighter than a wrong solver could sneak through.
19
+ ;
20
+ ; `left` was the animated property in the capture, not `opacity`, because a
21
+ ; cubic-bezier with a control point outside [0,1] OVERSHOOTS and opacity
22
+ ; clamps. The overshoot row below has values above 1 and below 0 in it, and
23
+ ; those are the rows a naively clamped implementation fails.
24
+ ; ==============================================================================
25
+
26
+ Import "EVGEasing.rgr"
27
+ Import "EVGElement.rgr"
28
+ Import "EVGTransition.rgr"
29
+ Import "EVGDisplayList.rgr"
30
+ Import "EVGLayout.rgr"
31
+
32
+ class TimingCheck {
33
+ def passed:int 0
34
+ def failed:int 0
35
+
36
+ fn ok:void (name:string cond:boolean) {
37
+ if cond {
38
+ passed = (passed + 1)
39
+ } {
40
+ failed = (failed + 1)
41
+ print (" FAIL " + name)
42
+ }
43
+ }
44
+
45
+ ; 1e-4: looser than the capture's five decimals, tighter than any
46
+ ; disagreement that would matter to an eye or hide a wrong curve.
47
+ fn eqStr:void (name:string got:boolean want:string) {
48
+ def good:boolean (((to_string got)) == want)
49
+ this.ok(name good)
50
+ }
51
+
52
+ fn near:void (name:string got:double want:double) {
53
+ def good:boolean ((fabs (got - want)) < 0.0001)
54
+ if (good == false) {
55
+ print (" got " + (to_string got) + " want " + (to_string want))
56
+ }
57
+ this.ok(name good)
58
+ }
59
+ }
60
+
61
+ class EVGTimingTest {
62
+ sfn check_linear:void (c:TimingCheck) {
63
+ def e:EVGEasing (EVGEasing.parse("linear"))
64
+ c.ok("linear parses" (e.ok))
65
+ c.near("linear at 0" (e.ease(0.0000)) 0.000000)
66
+ c.near("linear at 0.05" (e.ease(0.0500)) 0.050000)
67
+ c.near("linear at 0.1" (e.ease(0.1000)) 0.100000)
68
+ c.near("linear at 0.2" (e.ease(0.2000)) 0.200000)
69
+ c.near("linear at 0.25" (e.ease(0.2500)) 0.250000)
70
+ c.near("linear at 0.3" (e.ease(0.3000)) 0.300000)
71
+ c.near("linear at 0.4" (e.ease(0.4000)) 0.400000)
72
+ c.near("linear at 0.499" (e.ease(0.4990)) 0.499000)
73
+ c.near("linear at 0.5" (e.ease(0.5000)) 0.500000)
74
+ c.near("linear at 0.501" (e.ease(0.5010)) 0.501000)
75
+ c.near("linear at 0.6" (e.ease(0.6000)) 0.600000)
76
+ c.near("linear at 0.7" (e.ease(0.7000)) 0.700000)
77
+ c.near("linear at 0.75" (e.ease(0.7500)) 0.750000)
78
+ c.near("linear at 0.8" (e.ease(0.8000)) 0.800000)
79
+ c.near("linear at 0.9" (e.ease(0.9000)) 0.900000)
80
+ c.near("linear at 0.95" (e.ease(0.9500)) 0.950000)
81
+ c.near("linear at 1" (e.ease(1.0000)) 1.000000)
82
+ }
83
+
84
+ sfn check_ease:void (c:TimingCheck) {
85
+ def e:EVGEasing (EVGEasing.parse("ease"))
86
+ c.ok("ease parses" (e.ok))
87
+ c.near("ease at 0" (e.ease(0.0000)) 0.000000)
88
+ c.near("ease at 0.05" (e.ease(0.0500)) 0.032950)
89
+ c.near("ease at 0.1" (e.ease(0.1000)) 0.094800)
90
+ c.near("ease at 0.2" (e.ease(0.2000)) 0.295240)
91
+ c.near("ease at 0.25" (e.ease(0.2500)) 0.408510)
92
+ c.near("ease at 0.3" (e.ease(0.3000)) 0.513320)
93
+ c.near("ease at 0.4" (e.ease(0.4000)) 0.682540)
94
+ c.near("ease at 0.499" (e.ease(0.4990)) 0.801410)
95
+ c.near("ease at 0.5" (e.ease(0.5000)) 0.802400)
96
+ c.near("ease at 0.501" (e.ease(0.5010)) 0.803400)
97
+ c.near("ease at 0.6" (e.ease(0.6000)) 0.885230)
98
+ c.near("ease at 0.7" (e.ease(0.7000)) 0.940760)
99
+ c.near("ease at 0.75" (e.ease(0.7500)) 0.960460)
100
+ c.near("ease at 0.8" (e.ease(0.8000)) 0.975620)
101
+ c.near("ease at 0.9" (e.ease(0.9000)) 0.994320)
102
+ c.near("ease at 0.95" (e.ease(0.9500)) 0.998620)
103
+ c.near("ease at 1" (e.ease(1.0000)) 1.000000)
104
+ }
105
+
106
+ sfn check_ease_in:void (c:TimingCheck) {
107
+ def e:EVGEasing (EVGEasing.parse("ease-in"))
108
+ c.ok("ease-in parses" (e.ok))
109
+ c.near("ease-in at 0" (e.ease(0.0000)) 0.000000)
110
+ c.near("ease-in at 0.05" (e.ease(0.0500)) 0.004470)
111
+ c.near("ease-in at 0.1" (e.ease(0.1000)) 0.017030)
112
+ c.near("ease-in at 0.2" (e.ease(0.2000)) 0.062280)
113
+ c.near("ease-in at 0.25" (e.ease(0.2500)) 0.093460)
114
+ c.near("ease-in at 0.3" (e.ease(0.3000)) 0.129580)
115
+ c.near("ease-in at 0.4" (e.ease(0.4000)) 0.214860)
116
+ c.near("ease-in at 0.499" (e.ease(0.4990)) 0.314280)
117
+ c.near("ease-in at 0.5" (e.ease(0.5000)) 0.315360)
118
+ c.near("ease-in at 0.501" (e.ease(0.5010)) 0.316430)
119
+ c.near("ease-in at 0.6" (e.ease(0.6000)) 0.429120)
120
+ c.near("ease-in at 0.7" (e.ease(0.7000)) 0.554810)
121
+ c.near("ease-in at 0.75" (e.ease(0.7500)) 0.621860)
122
+ c.near("ease-in at 0.8" (e.ease(0.8000)) 0.691630)
123
+ c.near("ease-in at 0.9" (e.ease(0.9000)) 0.839430)
124
+ c.near("ease-in at 0.95" (e.ease(0.9500)) 0.917760)
125
+ c.near("ease-in at 1" (e.ease(1.0000)) 1.000000)
126
+ }
127
+
128
+ sfn check_ease_out:void (c:TimingCheck) {
129
+ def e:EVGEasing (EVGEasing.parse("ease-out"))
130
+ c.ok("ease-out parses" (e.ok))
131
+ c.near("ease-out at 0" (e.ease(0.0000)) 0.000000)
132
+ c.near("ease-out at 0.05" (e.ease(0.0500)) 0.082240)
133
+ c.near("ease-out at 0.1" (e.ease(0.1000)) 0.160570)
134
+ c.near("ease-out at 0.2" (e.ease(0.2000)) 0.308370)
135
+ c.near("ease-out at 0.25" (e.ease(0.2500)) 0.378140)
136
+ c.near("ease-out at 0.3" (e.ease(0.3000)) 0.445190)
137
+ c.near("ease-out at 0.4" (e.ease(0.4000)) 0.570880)
138
+ c.near("ease-out at 0.499" (e.ease(0.4990)) 0.683570)
139
+ c.near("ease-out at 0.5" (e.ease(0.5000)) 0.684640)
140
+ c.near("ease-out at 0.501" (e.ease(0.5010)) 0.685720)
141
+ c.near("ease-out at 0.6" (e.ease(0.6000)) 0.785140)
142
+ c.near("ease-out at 0.7" (e.ease(0.7000)) 0.870420)
143
+ c.near("ease-out at 0.75" (e.ease(0.7500)) 0.906530)
144
+ c.near("ease-out at 0.8" (e.ease(0.8000)) 0.937720)
145
+ c.near("ease-out at 0.9" (e.ease(0.9000)) 0.982970)
146
+ c.near("ease-out at 0.95" (e.ease(0.9500)) 0.995520)
147
+ c.near("ease-out at 1" (e.ease(1.0000)) 1.000000)
148
+ }
149
+
150
+ sfn check_ease_in_out:void (c:TimingCheck) {
151
+ def e:EVGEasing (EVGEasing.parse("ease-in-out"))
152
+ c.ok("ease-in-out parses" (e.ok))
153
+ c.near("ease-in-out at 0" (e.ease(0.0000)) 0.000000)
154
+ c.near("ease-in-out at 0.05" (e.ease(0.0500)) 0.004830)
155
+ c.near("ease-in-out at 0.1" (e.ease(0.1000)) 0.019720)
156
+ c.near("ease-in-out at 0.2" (e.ease(0.2000)) 0.081660)
157
+ c.near("ease-in-out at 0.25" (e.ease(0.2500)) 0.129160)
158
+ c.near("ease-in-out at 0.3" (e.ease(0.3000)) 0.187400)
159
+ c.near("ease-in-out at 0.4" (e.ease(0.4000)) 0.331880)
160
+ c.near("ease-in-out at 0.499" (e.ease(0.4990)) 0.498280)
161
+ c.near("ease-in-out at 0.5" (e.ease(0.5000)) 0.500000)
162
+ c.near("ease-in-out at 0.501" (e.ease(0.5010)) 0.501720)
163
+ c.near("ease-in-out at 0.6" (e.ease(0.6000)) 0.668120)
164
+ c.near("ease-in-out at 0.7" (e.ease(0.7000)) 0.812600)
165
+ c.near("ease-in-out at 0.75" (e.ease(0.7500)) 0.870840)
166
+ c.near("ease-in-out at 0.8" (e.ease(0.8000)) 0.918340)
167
+ c.near("ease-in-out at 0.9" (e.ease(0.9000)) 0.980280)
168
+ c.near("ease-in-out at 0.95" (e.ease(0.9500)) 0.995170)
169
+ c.near("ease-in-out at 1" (e.ease(1.0000)) 1.000000)
170
+ }
171
+
172
+ sfn check_cubic_bezier_0_68___0_55__0_27__1_55_:void (c:TimingCheck) {
173
+ def e:EVGEasing (EVGEasing.parse("cubic-bezier(0.68, -0.55, 0.27, 1.55)"))
174
+ c.ok("cubic-bezier(0.68, -0.55, 0.27, 1.55) parses" (e.ok))
175
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0" (e.ease(0.0000)) 0.000000)
176
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.05" (e.ease(0.0500)) -0.037040)
177
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.1" (e.ease(0.1000)) -0.066270)
178
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.2" (e.ease(0.2000)) -0.092700)
179
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.25" (e.ease(0.2500)) -0.082940)
180
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.3" (e.ease(0.3000)) -0.049800)
181
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.4" (e.ease(0.4000)) 0.139360)
182
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.499" (e.ease(0.4990)) 0.591590)
183
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.5" (e.ease(0.5000)) 0.596600)
184
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.501" (e.ease(0.5010)) 0.601580)
185
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.6" (e.ease(0.6000)) 0.943070)
186
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.7" (e.ease(0.7000)) 1.068130)
187
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.75" (e.ease(0.7500)) 1.088820)
188
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.8" (e.ease(0.8000)) 1.092190)
189
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.9" (e.ease(0.9000)) 1.062700)
190
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 0.95" (e.ease(0.9500)) 1.034700)
191
+ c.near("cubic-bezier(0.68, -0.55, 0.27, 1.55) at 1" (e.ease(1.0000)) 1.000000)
192
+ }
193
+
194
+ sfn check_steps_4__end_:void (c:TimingCheck) {
195
+ def e:EVGEasing (EVGEasing.parse("steps(4, end)"))
196
+ c.ok("steps(4, end) parses" (e.ok))
197
+ c.near("steps(4, end) at 0" (e.ease(0.0000)) 0.000000)
198
+ c.near("steps(4, end) at 0.05" (e.ease(0.0500)) 0.000000)
199
+ c.near("steps(4, end) at 0.1" (e.ease(0.1000)) 0.000000)
200
+ c.near("steps(4, end) at 0.2" (e.ease(0.2000)) 0.000000)
201
+ c.near("steps(4, end) at 0.25" (e.ease(0.2500)) 0.250000)
202
+ c.near("steps(4, end) at 0.3" (e.ease(0.3000)) 0.250000)
203
+ c.near("steps(4, end) at 0.4" (e.ease(0.4000)) 0.250000)
204
+ c.near("steps(4, end) at 0.499" (e.ease(0.4990)) 0.250000)
205
+ c.near("steps(4, end) at 0.5" (e.ease(0.5000)) 0.500000)
206
+ c.near("steps(4, end) at 0.501" (e.ease(0.5010)) 0.500000)
207
+ c.near("steps(4, end) at 0.6" (e.ease(0.6000)) 0.500000)
208
+ c.near("steps(4, end) at 0.7" (e.ease(0.7000)) 0.500000)
209
+ c.near("steps(4, end) at 0.75" (e.ease(0.7500)) 0.750000)
210
+ c.near("steps(4, end) at 0.8" (e.ease(0.8000)) 0.750000)
211
+ c.near("steps(4, end) at 0.9" (e.ease(0.9000)) 0.750000)
212
+ c.near("steps(4, end) at 0.95" (e.ease(0.9500)) 0.750000)
213
+ c.near("steps(4, end) at 1" (e.ease(1.0000)) 1.000000)
214
+ }
215
+
216
+ sfn check_steps_4__start_:void (c:TimingCheck) {
217
+ def e:EVGEasing (EVGEasing.parse("steps(4, start)"))
218
+ c.ok("steps(4, start) parses" (e.ok))
219
+ c.near("steps(4, start) at 0" (e.ease(0.0000)) 0.250000)
220
+ c.near("steps(4, start) at 0.05" (e.ease(0.0500)) 0.250000)
221
+ c.near("steps(4, start) at 0.1" (e.ease(0.1000)) 0.250000)
222
+ c.near("steps(4, start) at 0.2" (e.ease(0.2000)) 0.250000)
223
+ c.near("steps(4, start) at 0.25" (e.ease(0.2500)) 0.500000)
224
+ c.near("steps(4, start) at 0.3" (e.ease(0.3000)) 0.500000)
225
+ c.near("steps(4, start) at 0.4" (e.ease(0.4000)) 0.500000)
226
+ c.near("steps(4, start) at 0.499" (e.ease(0.4990)) 0.500000)
227
+ c.near("steps(4, start) at 0.5" (e.ease(0.5000)) 0.750000)
228
+ c.near("steps(4, start) at 0.501" (e.ease(0.5010)) 0.750000)
229
+ c.near("steps(4, start) at 0.6" (e.ease(0.6000)) 0.750000)
230
+ c.near("steps(4, start) at 0.7" (e.ease(0.7000)) 0.750000)
231
+ c.near("steps(4, start) at 0.75" (e.ease(0.7500)) 1.000000)
232
+ c.near("steps(4, start) at 0.8" (e.ease(0.8000)) 1.000000)
233
+ c.near("steps(4, start) at 0.9" (e.ease(0.9000)) 1.000000)
234
+ c.near("steps(4, start) at 0.95" (e.ease(0.9500)) 1.000000)
235
+ c.near("steps(4, start) at 1" (e.ease(1.0000)) 1.000000)
236
+ }
237
+
238
+ sfn check_cubic_bezier_1__0__0__1_:void (c:TimingCheck) {
239
+ def e:EVGEasing (EVGEasing.parse("cubic-bezier(1, 0, 0, 1)"))
240
+ c.ok("cubic-bezier(1, 0, 0, 1) parses" (e.ok))
241
+ c.near("cubic-bezier(1, 0, 0, 1) at 0" (e.ease(0.0000)) 0.000000)
242
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.05" (e.ease(0.0500)) 0.000880)
243
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.1" (e.ease(0.1000)) 0.003760)
244
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.2" (e.ease(0.2000)) 0.017430)
245
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.25" (e.ease(0.2500)) 0.029720)
246
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.3" (e.ease(0.3000)) 0.047400)
247
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.4" (e.ease(0.4000)) 0.111400)
248
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.499" (e.ease(0.4990)) 0.406010)
249
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.5" (e.ease(0.5000)) 0.500000)
250
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.501" (e.ease(0.5010)) 0.594000)
251
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.6" (e.ease(0.6000)) 0.888600)
252
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.7" (e.ease(0.7000)) 0.952610)
253
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.75" (e.ease(0.7500)) 0.970270)
254
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.8" (e.ease(0.8000)) 0.982580)
255
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.9" (e.ease(0.9000)) 0.996240)
256
+ c.near("cubic-bezier(1, 0, 0, 1) at 0.95" (e.ease(0.9500)) 0.999120)
257
+ c.near("cubic-bezier(1, 0, 0, 1) at 1" (e.ease(1.0000)) 1.000000)
258
+ }
259
+
260
+ sfn check_cubic_bezier_0_999__0__0_001__1_:void (c:TimingCheck) {
261
+ def e:EVGEasing (EVGEasing.parse("cubic-bezier(0.999, 0, 0.001, 1)"))
262
+ c.ok("cubic-bezier(0.999, 0, 0.001, 1) parses" (e.ok))
263
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0" (e.ease(0.0000)) 0.000000)
264
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.05" (e.ease(0.0500)) 0.000880)
265
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.1" (e.ease(0.1000)) 0.003770)
266
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.2" (e.ease(0.2000)) 0.017460)
267
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.25" (e.ease(0.2500)) 0.029790)
268
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.3" (e.ease(0.3000)) 0.047500)
269
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.4" (e.ease(0.4000)) 0.111670)
270
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.499" (e.ease(0.4990)) 0.408890)
271
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.5" (e.ease(0.5000)) 0.500000)
272
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.501" (e.ease(0.5010)) 0.591110)
273
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.6" (e.ease(0.6000)) 0.888330)
274
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.7" (e.ease(0.7000)) 0.952500)
275
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.75" (e.ease(0.7500)) 0.970210)
276
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.8" (e.ease(0.8000)) 0.982540)
277
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.9" (e.ease(0.9000)) 0.996230)
278
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 0.95" (e.ease(0.9500)) 0.999110)
279
+ c.near("cubic-bezier(0.999, 0, 0.001, 1) at 1" (e.ease(1.0000)) 1.000000)
280
+ }
281
+ ; --- the shorthand's own parsing ------------------------------------------
282
+ ;
283
+ ; A timing function can contain commas and spaces, so the list parser has to
284
+ ; count parentheses. Splitting `transition: opacity 1s cubic-bezier(0, 0,
285
+ ; 1, 1)` on every comma yields four fragments that each still parse as
286
+ ; something, which is the worst kind of wrong.
287
+ sfn checkTopLevelSplitting:void (c:TimingCheck) {
288
+ def parts:[string] (EVGEasing.splitTop("opacity 1s cubic-bezier(0, 0, 1, 1), color 2s" 44))
289
+ c.ok("a comma inside a function does not split the list" ((array_length parts) == 2))
290
+ def words:[string] (EVGEasing.splitWordsTop("opacity 200ms steps(4, end) 50ms"))
291
+ c.ok("a space inside a function does not split the words" ((array_length words) == 4))
292
+ c.ok("and the function is one word" ((itemAt words 2) == "steps(4, end)"))
293
+ c.ok("a function is recognised as one" (EVGEasing.looksLikeFunction("cubic-bezier(0,0,1,1)")))
294
+ c.ok("a duration is not" ((EVGEasing.looksLikeFunction("200ms")) == false))
295
+ }
296
+
297
+ ; An unknown timing function is REPORTED, for the same reason an unknown
298
+ ; pseudo-class is: silently becoming linear is motion that looks subtly
299
+ ; wrong with nothing anywhere saying why.
300
+ sfn checkUnknown:void (c:TimingCheck) {
301
+ def e:EVGEasing (EVGEasing.parse("spring(1, 2)"))
302
+ c.ok("an unknown timing function is not ok" ((e.ok) == false))
303
+ c.near("and falls back to linear" (e.ease(0.5)) 0.5)
304
+ def keyword:EVGEasing (EVGEasing.parse("ease-in-out"))
305
+ c.ok("a real one is ok" (keyword.ok))
306
+ }
307
+
308
+ ; step-start / step-end are steps(1, …), which is worth a check because a
309
+ ; one-step function is where an off-by-one in the jump shows up plainly.
310
+ sfn checkStepKeywords:void (c:TimingCheck) {
311
+ def se:EVGEasing (EVGEasing.parse("step-end"))
312
+ c.near("step-end holds at 0 until the end" (se.ease(0.99)) 0.0)
313
+ c.near("step-end arrives at 1" (se.ease(1.0)) 1.0)
314
+ def ss:EVGEasing (EVGEasing.parse("step-start"))
315
+ c.near("step-start is already at 1" (ss.ease(0.01)) 1.0)
316
+ }
317
+
318
+ ; The curve is monotonic in x even when it overshoots in y, so the solver
319
+ ; must never be handed a parameter outside [0,1] and must be stable at the
320
+ ; ends. These are the inputs that made a Newton-only solver wander.
321
+ sfn checkSolverEdges:void (c:TimingCheck) {
322
+ def e:EVGEasing (EVGEasing.parse("cubic-bezier(0.68, -0.55, 0.27, 1.55)"))
323
+ c.near("pinned at the start" (e.ease(0.0)) 0.0)
324
+ c.near("pinned at the end" (e.ease(1.0)) 1.0)
325
+ c.near("clamped below" (e.ease(0.0 - 1.0)) 0.0)
326
+ c.near("clamped above" (e.ease(2.0)) 1.0)
327
+ ; 0.5 EXACTLY is the one input a Newton-only solver gets right on a
328
+ ; stiff curve — it converges on the first iteration — so checking that
329
+ ; and stopping is how the missing fallback stayed invisible. The table
330
+ ; above samples 0.499 and 0.501 for this reason.
331
+ def flat:EVGEasing (EVGEasing.parse("cubic-bezier(1, 0, 0, 1)"))
332
+ c.near("a flat-derivative curve solves at the middle" (flat.ease(0.5)) 0.5)
333
+ c.near("and a hair below it" (flat.ease(0.499)) 0.40601)
334
+ c.near("and a hair above it" (flat.ease(0.501)) 0.594)
335
+ }
336
+
337
+
338
+ ; ==========================================================================
339
+ ; The shorthand, and the clock it drives
340
+ ; ==========================================================================
341
+
342
+ ; An element whose background is transitioned by `spec`, already settled at
343
+ ; `from`. Settled matters: the first apply on a fresh element establishes a
344
+ ; value rather than animating to it out of nowhere.
345
+ sfn probe:EVGElement (spec:string from:string) {
346
+ def el (new EVGElement())
347
+ el.transitionSpec = spec
348
+ el.backgroundColor = (EVGColor.parse(from))
349
+ return el
350
+ }
351
+
352
+ sfn red:int (el:EVGElement) {
353
+ if el.backgroundColor {
354
+ def c:EVGColor (unwrap el.backgroundColor)
355
+ return (to_int (c.r + 0.5))
356
+ }
357
+ return (0 - 1)
358
+ }
359
+
360
+ sfn flightMs:double (tr:EVGTransition el:EVGElement) {
361
+ def f@(optional):EVGFlight (tr.flightFor(el "background-color"))
362
+ if f {
363
+ def one:EVGFlight (unwrap f)
364
+ return one.durationMs
365
+ }
366
+ return (0.0 - 1.0)
367
+ }
368
+
369
+ sfn flightDelay:double (tr:EVGTransition el:EVGElement) {
370
+ def f@(optional):EVGFlight (tr.flightFor(el "background-color"))
371
+ if f {
372
+ def one:EVGFlight (unwrap f)
373
+ return one.delayMs
374
+ }
375
+ return (0.0 - 1.0)
376
+ }
377
+
378
+ sfn checkShorthand:void (c:TimingCheck) {
379
+ def a:EVGTimingSpec (EVGTransition.specFor("background-color 200ms linear 100ms" "background-color"))
380
+ c.ok("the property is found" (a.found))
381
+ c.near("the FIRST time is the duration" a.durationMs 200.0)
382
+ c.near("the SECOND is the delay" a.delayMs 100.0)
383
+
384
+ ; Order-tolerant except for the times. This is CSS's rule, not a
385
+ ; convenience: the parser cannot tell a duration from a delay any other
386
+ ; way.
387
+ def b:EVGTimingSpec (EVGTransition.specFor("ease-out 0.2s opacity" "opacity"))
388
+ c.ok("the name can come last" (b.found))
389
+ c.near("seconds become milliseconds" b.durationMs 200.0)
390
+ c.near("and one time is a duration, not a delay" b.delayMs 0.0)
391
+
392
+ def cc:EVGTimingSpec (EVGTransition.specFor("opacity 1s cubic-bezier(0.42, 0, 1, 1)" "opacity"))
393
+ c.near("a function's own commas do not split the list" cc.durationMs 1000.0)
394
+ c.near("and it is the function it said" (cc.easing.ease(0.5)) 0.31536)
395
+
396
+ ; `all` matches anything, and a later entry overrides an earlier one —
397
+ ; which together are what let a sheet say "everything except this".
398
+ def all:EVGTimingSpec (EVGTransition.specFor("all 200ms, opacity 0s" "color"))
399
+ c.near("all covers a property it does not name" all.durationMs 200.0)
400
+ def none:EVGTimingSpec (EVGTransition.specFor("all 200ms, opacity 0s" "opacity"))
401
+ c.ok("a later entry overrides an earlier one" (none.found))
402
+ c.near("so opacity is opted out" none.durationMs 0.0)
403
+
404
+ def missing:EVGTimingSpec (EVGTransition.specFor("color 200ms" "transform"))
405
+ c.ok("an unmentioned property is not found at all" ((missing.found) == false))
406
+ c.near("which is a different answer from zero" (EVGTransition.durationFor("color 200ms" "transform")) (0.0 - 1.0))
407
+
408
+ def bad:EVGTimingSpec (EVGTransition.specFor("color 200ms spring(1,2)" "color"))
409
+ c.ok("an unparsable timing function is reported" ((bad.ok) == false))
410
+ }
411
+
412
+ ; Measured: `background-color 200ms linear 100ms` driven from black to
413
+ ; rgb(200,200,200) does not move at all for its first 100ms, then runs.
414
+ ; A delay implemented as "start slowly" passes a midpoint check and fails
415
+ ; every one of these.
416
+ sfn checkDelay:void (c:TimingCheck) {
417
+ def tr (new EVGTransition())
418
+ def el:EVGElement (EVGTimingTest.probe("background-color 200ms linear 100ms" "rgb(0,0,0)"))
419
+ tr.reconcile(el)
420
+ el.backgroundColor = (EVGColor.parse("rgb(200,200,200)"))
421
+ tr.reconcile(el)
422
+ c.ok("at 0ms the value is 0" ((EVGTimingTest.red(el)) == 0))
423
+ tr.advance(el 50.0)
424
+ c.ok("at 50ms the value is 0" ((EVGTimingTest.red(el)) == 0))
425
+ tr.advance(el 50.0)
426
+ c.ok("at 100ms the value is 0" ((EVGTimingTest.red(el)) == 0))
427
+ tr.advance(el 50.0)
428
+ c.ok("at 150ms the value is 50" ((EVGTimingTest.red(el)) == 50))
429
+ tr.advance(el 50.0)
430
+ c.ok("at 200ms the value is 100" ((EVGTimingTest.red(el)) == 100))
431
+ tr.advance(el 50.0)
432
+ c.ok("at 250ms the value is 150" ((EVGTimingTest.red(el)) == 150))
433
+ tr.advance(el 50.0)
434
+ c.ok("at 300ms the value is 200" ((EVGTimingTest.red(el)) == 200))
435
+ c.ok("and only then is it done" ((tr.busy(el)) == false))
436
+ }
437
+
438
+ ; The reversal durations, straight off the capture. The `linear` row is the
439
+ ; one an implementation using CLOCK progress also passes, which is why it
440
+ ; is not the only row.
441
+ sfn reversedAfter:double (easing:string dur:double at:double) {
442
+ def tr (new EVGTransition())
443
+ def spec:string ("background-color " + (to_string (to_int dur)) + "ms " + easing)
444
+ def el:EVGElement (EVGTimingTest.probe(spec "rgb(0,0,0)"))
445
+ tr.reconcile(el)
446
+ el.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
447
+ tr.reconcile(el)
448
+ tr.advance(el at)
449
+ el.backgroundColor = (EVGColor.parse("rgb(0,0,0)"))
450
+ tr.reconcile(el)
451
+ return (EVGTimingTest.flightMs(tr el))
452
+ }
453
+
454
+ sfn checkReversal:void (c:TimingCheck) {
455
+ c.near("linear reversed at 100ms of 200ms" (EVGTimingTest.reversedAfter("linear" 200.0 100.0)) 100.0000)
456
+ c.near("ease-in reversed at 100ms of 200ms" (EVGTimingTest.reversedAfter("ease-in" 200.0 100.0)) 63.0713)
457
+ c.near("ease-out reversed at 100ms of 200ms" (EVGTimingTest.reversedAfter("ease-out" 200.0 100.0)) 136.9287)
458
+ c.near("ease reversed at 50ms of 200ms" (EVGTimingTest.reversedAfter("ease" 200.0 50.0)) 81.7021)
459
+ }
460
+
461
+ ; Reversing a reversal. The factor compounds, so the third leg is LONGER
462
+ ; than the second — 200 → 100 → 150, not 200 → 100 → 50. Halving again is
463
+ ; the obvious implementation and it makes a jittery pointer converge to an
464
+ ; instant snap.
465
+ sfn checkDoubleReversal:void (c:TimingCheck) {
466
+ def tr50 (new EVGTransition())
467
+ def el50:EVGElement (EVGTimingTest.probe("background-color 200ms linear" "rgb(0,0,0)"))
468
+ tr50.reconcile(el50)
469
+ el50.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
470
+ tr50.reconcile(el50)
471
+ tr50.advance(el50 100.0)
472
+ el50.backgroundColor = (EVGColor.parse("rgb(0,0,0)"))
473
+ tr50.reconcile(el50)
474
+ c.near("leg 2 of the 0.5 case" (EVGTimingTest.flightMs(tr50 el50)) 100.0000)
475
+ tr50.advance(el50 50.0000)
476
+ c.ok("and its colour there is 64" ((EVGTimingTest.red(el50)) == 64))
477
+ el50.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
478
+ tr50.reconcile(el50)
479
+ c.near("leg 3 of the 0.5 case is LONGER than leg 2" (EVGTimingTest.flightMs(tr50 el50)) 150.0000)
480
+
481
+ def tr25 (new EVGTransition())
482
+ def el25:EVGElement (EVGTimingTest.probe("background-color 200ms linear" "rgb(0,0,0)"))
483
+ tr25.reconcile(el25)
484
+ el25.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
485
+ tr25.reconcile(el25)
486
+ tr25.advance(el25 100.0)
487
+ el25.backgroundColor = (EVGColor.parse("rgb(0,0,0)"))
488
+ tr25.reconcile(el25)
489
+ c.near("leg 2 of the 0.25 case" (EVGTimingTest.flightMs(tr25 el25)) 100.0000)
490
+ tr25.advance(el25 25.0000)
491
+ c.ok("and its colour there is 96" ((EVGTimingTest.red(el25)) == 96))
492
+ el25.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
493
+ tr25.reconcile(el25)
494
+ c.near("leg 3 of the 0.25 case is LONGER than leg 2" (EVGTimingTest.flightMs(tr25 el25)) 125.0000)
495
+ }
496
+
497
+ ; A delay survives a reversal at full length while the duration is
498
+ ; shortened. Both halves of that were read off the screen, not off a
499
+ ; timing object: the reversed leg holds its colour for 100ms and then runs
500
+ ; for 100ms.
501
+ sfn checkDelayedReversal:void (c:TimingCheck) {
502
+ def tr (new EVGTransition())
503
+ def el:EVGElement (EVGTimingTest.probe("background-color 200ms linear 100ms" "rgb(0,0,0)"))
504
+ tr.reconcile(el)
505
+ el.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
506
+ tr.reconcile(el)
507
+ tr.advance(el 200.0)
508
+ c.ok("half way, after the delay" ((EVGTimingTest.red(el)) == 128))
509
+ el.backgroundColor = (EVGColor.parse("rgb(0,0,0)"))
510
+ tr.reconcile(el)
511
+ c.near("the duration is shortened" (EVGTimingTest.flightMs(tr el)) 100.0000)
512
+ c.near("the delay is NOT" (EVGTimingTest.flightDelay(tr el)) 100.0000)
513
+ c.ok("reversed: 0ms in, the value is 128" ((EVGTimingTest.red(el)) == 128))
514
+ tr.advance(el 50.0)
515
+ c.ok("reversed: 50ms in, the value is 128" ((EVGTimingTest.red(el)) == 128))
516
+ tr.advance(el 50.0)
517
+ c.ok("reversed: 100ms in, the value is 128" ((EVGTimingTest.red(el)) == 128))
518
+ tr.advance(el 25.0)
519
+ c.ok("reversed: 125ms in, the value is 96" ((EVGTimingTest.red(el)) == 96))
520
+ tr.advance(el 25.0)
521
+ c.ok("reversed: 150ms in, the value is 64" ((EVGTimingTest.red(el)) == 64))
522
+ tr.advance(el 25.0)
523
+ c.ok("reversed: 175ms in, the value is 32" ((EVGTimingTest.red(el)) == 32))
524
+ tr.advance(el 25.0)
525
+ c.ok("reversed: 200ms in, the value is 0" ((EVGTimingTest.red(el)) == 0))
526
+ }
527
+
528
+ ; Not a reversal: a third value. It gets the full duration, because the
529
+ ; value has to travel a full journey to somewhere it has never been.
530
+ sfn checkThirdValue:void (c:TimingCheck) {
531
+ def tr (new EVGTransition())
532
+ def el:EVGElement (EVGTimingTest.probe("background-color 200ms linear" "rgb(0,0,0)"))
533
+ tr.reconcile(el)
534
+ el.backgroundColor = (EVGColor.parse("rgb(255,255,255)"))
535
+ tr.reconcile(el)
536
+ tr.advance(el 100.0)
537
+ el.backgroundColor = (EVGColor.parse("rgb(60,60,60)"))
538
+ tr.reconcile(el)
539
+ c.near("a new destination is not a reversal" (EVGTimingTest.flightMs(tr el)) 200.0)
540
+ c.ok("and it leaves from where it had got to" ((EVGTimingTest.red(el)) == 128))
541
+ }
542
+
543
+
544
+ ; ==========================================================================
545
+ ; transform
546
+ ; ==========================================================================
547
+
548
+ sfn checkTransformParsing:void (c:TimingCheck) {
549
+ def el (new EVGElement())
550
+ el.setAttribute("transform" "rotate(90deg)")
551
+ c.near("degrees" el.rotate 90.0)
552
+ el.setAttribute("transform" "rotate(0.25turn)")
553
+ c.near("a quarter turn is 90 degrees" el.rotate 90.0)
554
+ el.setAttribute("transform" "rotate(1.5707963268rad)")
555
+ c.near("and so is pi over two radians" el.rotate 90.0)
556
+ el.setAttribute("transform" "rotate(100grad)")
557
+ c.near("and a hundred gradians" el.rotate 90.0)
558
+
559
+ el.setAttribute("transform" "scale(1.25)")
560
+ c.near("a uniform scale" el.scale 1.25)
561
+ c.near("and it leaves the angle alone" el.rotate 0.0)
562
+
563
+ el.setAttribute("transform" "translate(4px, -2px)")
564
+ c.near("translate x" el.translateX 4.0)
565
+ c.near("translate y" el.translateY (0.0 - 2.0))
566
+ el.setAttribute("transform" "translateY(7)")
567
+ c.near("translateY on its own" el.translateY 7.0)
568
+ c.near("and it is not read as a translate" el.translateX 0.0)
569
+
570
+ el.setAttribute("transform" "none")
571
+ c.near("none resets the angle" el.rotate 0.0)
572
+ c.near("and the scale" el.scale 1.0)
573
+
574
+ ; Order matters, and this is the pair that proves the composition is
575
+ ; real rather than four fields set independently. Turning a quarter
576
+ ; turn and THEN moving 10 along x moves down the page; moving first and
577
+ ; then turning moves along x. Both orders give the same angle, so an
578
+ ; implementation that ignores order looks perfect until something is
579
+ ; both turned and offset.
580
+ el.setAttribute("transform" "rotate(90deg) translate(10px, 0)")
581
+ c.near("rotate then translate goes down the page" el.translateY 10.0)
582
+ c.near("and not across it" el.translateX 0.0)
583
+ el.setAttribute("transform" "translate(10px, 0) rotate(90deg)")
584
+ c.near("translate then rotate goes across" el.translateX 10.0)
585
+ c.near("and not down" el.translateY 0.0)
586
+
587
+ c.eqStr("a skew is refused, not approximated"
588
+ (EVGElement.transformProblem("skewX(10deg)") != "") "true")
589
+ c.eqStr("so is a non-uniform scale"
590
+ (EVGElement.transformProblem("scale(2, 1)") != "") "true")
591
+ c.eqStr("a uniform one written twice is fine"
592
+ (EVGElement.transformProblem("scale(2, 2)") == "") "true")
593
+ c.eqStr("and the supported set is accepted"
594
+ (EVGElement.transformProblem("rotate(9deg) scale(1.1) translate(1px, 2px)") == "") "true")
595
+
596
+ ; A MIRROR is still a similarity: same magnitude on both axes, one
597
+ ; sign apart. Figma writes a mirrored layer exactly this way — its
598
+ ; matrix has a negative determinant — and read as a plain rotation
599
+ ; the layer comes out backwards, an arrow with its head where its
600
+ ; tail belongs.
601
+ c.eqStr("a sign apart is not a non-uniform scale"
602
+ (EVGElement.transformProblem("scale(1, -1)") == "") "true")
603
+ el.setAttribute("transform" "scale(1, -1)")
604
+ c.eqStr("and it is read as a flip" el.flipY "true")
605
+ c.near("with the factor it was written with" el.scale 1.0)
606
+ el.setAttribute("transform" "scale(-1, 1)")
607
+ c.eqStr("the other axis is the same flip the other way up" el.flipY "true")
608
+ c.near("carried in the sign of the factor" el.scale (0.0 - 1.0))
609
+ el.setAttribute("transform" "scale(-1, -1)")
610
+ c.eqStr("and both signs together are a half turn, not a flip" el.flipY "false")
611
+ el.setAttribute("transform" "none")
612
+ c.eqStr("none clears the flip" el.flipY "false")
613
+ ; A flip turns what follows it around: F·R(b) = R(-b)·F. Both orders
614
+ ; are mirrored, and an implementation that sets the fields
615
+ ; independently gets the angle of one of them backwards.
616
+ el.setAttribute("transform" "rotate(30deg) scale(1, -1)")
617
+ c.near("a turn before a flip keeps its angle" el.rotate 30.0)
618
+ el.setAttribute("transform" "scale(1, -1) rotate(30deg)")
619
+ c.near("a turn after one is the other way" el.rotate (0.0 - 30.0))
620
+ }
621
+
622
+ ; The pivot, measured: Chromium reports `transform-origin: 50px 20px` for a
623
+ ; 100x40 box and a quarter turn leaves it at (30, -30, 40, 100) — centred
624
+ ; where it began. So every command of the subtree must name the ELEMENT's
625
+ ; centre, not its own.
626
+ sfn checkTransformOrigin:void (c:TimingCheck) {
627
+ def page (new EVGElement())
628
+ page.setAttribute("width" "200")
629
+ page.setAttribute("height" "200")
630
+ def kid (new EVGElement())
631
+ kid.setAttribute("width" "100")
632
+ kid.setAttribute("height" "40")
633
+ kid.setAttribute("background-color" "rgb(10,20,30)")
634
+ kid.textContent = "turned"
635
+ kid.setAttribute("transform" "rotate(90deg)")
636
+ page.addChild(kid)
637
+
638
+ def layout (new EVGLayout())
639
+ layout.layout(page)
640
+ def dl (new EVGDisplayList())
641
+ dl.build(page)
642
+
643
+ def found:int 0
644
+ def i:int 0
645
+ while (i < (array_length dl.cmds)) {
646
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
647
+ if (cmd.rotate != 0.0) {
648
+ found = (found + 1)
649
+ c.ok("every turned command names an origin" cmd.hasRotOrigin)
650
+ ; The box was laid out at (0,0) 100x40, so its centre is
651
+ ; (50, 20) — the same two numbers the browser reported.
652
+ c.near("and it is the element centre in x" cmd.rotOriginX 50.0)
653
+ c.near("and in y" cmd.rotOriginY 20.0)
654
+ c.near("with the angle the sheet asked for" cmd.rotate 90.0)
655
+ }
656
+ i = (i + 1)
657
+ }
658
+ ; The background AND the text, not just one of them: the text quad's
659
+ ; own centre is nowhere near the box's, and that is the case a
660
+ ; per-command pivot gets visibly wrong.
661
+ c.ok("the box and its text are both turned" (found >= 2))
662
+ }
663
+
664
+ ; A mirrored box is drawn on the other side of its origin, and comes out
665
+ ; the right way up: a rectangle has no negative side, so the flip moves
666
+ ; the box rather than leaving a negative height for a painter to puzzle
667
+ ; over.
668
+ sfn checkMirroredBox:void (c:TimingCheck) {
669
+ def page (new EVGElement())
670
+ page.setAttribute("width" "200")
671
+ page.setAttribute("height" "200")
672
+ def kid (new EVGElement())
673
+ kid.setAttribute("width" "100")
674
+ kid.setAttribute("height" "40")
675
+ kid.setAttribute("background-color" "rgb(10,20,30)")
676
+ kid.setAttribute("transform-origin" "0 0")
677
+ kid.setAttribute("transform" "scale(1, -1)")
678
+ page.addChild(kid)
679
+ def layout (new EVGLayout())
680
+ layout.layout(page)
681
+ def dl (new EVGDisplayList())
682
+ dl.build(page)
683
+ def seen:boolean false
684
+ def i:int 0
685
+ while (i < (array_length dl.cmds)) {
686
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
687
+ if ((cmd.kind == 0) && ((fabs (cmd.w - 100.0)) < 0.001)) {
688
+ seen = true
689
+ c.near("the flipped box is above the origin" cmd.y (0.0 - 40.0))
690
+ c.near("and is still forty high, not minus forty" cmd.h 40.0)
691
+ c.near("and did not move sideways" cmd.x 0.0)
692
+ }
693
+ i = (i + 1)
694
+ }
695
+ c.ok("the flipped box was drawn" seen)
696
+ }
697
+
698
+ ; A PICTURE IS NOT ALWAYS SHOWN WHOLE. Figma keeps a crop window on the
699
+ ; paint, and a list that drops it draws the whole frame where a detail
700
+ ; belongs: on a board where two boxes show two halves of one screenshot,
701
+ ; the boxes are the same size and the same bytes and differ ONLY in that
702
+ ; window, so dropping it drew the same picture twice. The window is in
703
+ ; the SOURCE's own 0..1 space, so no page transform touches it, and it
704
+ ; may reach past the bitmap — a crop zoomed out past the picture leaves
705
+ ; a margin the picture does not cover.
706
+ sfn hasSub:boolean (s:string needle:string) {
707
+ def m:int (strlen needle)
708
+ def n:int (strlen s)
709
+ def i:int 0
710
+ while (i <= (n - m)) {
711
+ if ((substring s i (i + m)) == needle) {
712
+ return true
713
+ }
714
+ i = (i + 1)
715
+ }
716
+ return false
717
+ }
718
+
719
+ ; A SHADOW NOTHING EVER WROTE. `hasShadow` and binary slots 31-35 have
720
+ ; been on the command since the office exporters needed them, and the
721
+ ; tree walk never set one from an element: the fields were there, the
722
+ ; bridge carried them, and a page built from a document had none. A board
723
+ ; of FigJam stickies is 154 shadows, and every note sat flat on it.
724
+ ; A GRADIENT ON A SHAPE THAT IS NOT ITS BOX. `hasGrad` was written for
725
+ ; rectangles only, so a vector filled with a ramp had to choose between
726
+ ; the shading and its own outline: painted as a box it drew a grey square
727
+ ; where a rounded pill should be, and flattened to the average of its two
728
+ ; ends it kept the pill and lost the shading. The command carries the
729
+ ; rings AND the stops, so neither is given up.
730
+ ; A RING AROUND A TURNED LAYER HAS TO LEAN THE WAY IT DOES. Drawn on the
731
+ ; box that CONTAINS a turned shape it is an upright rectangle half again
732
+ ; too big, which says the layer is straight — and on a board where a
733
+ ; layer can be turned, which way it is turned is what the ring is for.
734
+ sfn checkTurnedFrame:void (c:TimingCheck) {
735
+ def dl (new EVGDisplayList())
736
+ dl.addTurnedFrame(10.0 20.0 100.0 40.0 2.0 (EVGColor.rgb(59 130 246)) 35.0 10.0 20.0)
737
+ ; …and one with no angle, which must stay exactly the command it was
738
+ ; before this existed: `addFrame` goes through the same code.
739
+ dl.addFrame(0.0 0.0 50.0 50.0 1.0 (EVGColor.rgb(0 0 0)))
740
+ def turned:int 0
741
+ def plain:int 0
742
+ def i:int 0
743
+ while (i < (array_length dl.cmds)) {
744
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
745
+ if (cmd.kind == 1) {
746
+ if cmd.hasRotOrigin {
747
+ turned = (turned + 1)
748
+ c.near("the ring leans by the layer's own angle" cmd.rotate 35.0)
749
+ ; About the layer's CORNER, which is where Figma turns
750
+ ; one — about the centre it would swing off the shape.
751
+ c.near("about the corner it was given" cmd.rotOriginX 10.0)
752
+ c.near("in both axes" cmd.rotOriginY 20.0)
753
+ } {
754
+ plain = (plain + 1)
755
+ c.near("and an upright ring carries no angle" cmd.rotate 0.0)
756
+ }
757
+ }
758
+ i = (i + 1)
759
+ }
760
+ c.ok("the turned ring is one command" (turned == 1))
761
+ c.ok("and the upright one is unchanged" (plain == 1))
762
+ }
763
+
764
+ sfn checkPathGradient:void (c:TimingCheck) {
765
+ def page (new EVGElement())
766
+ page.setAttribute("width" "200")
767
+ page.setAttribute("height" "200")
768
+ def pill (EVGElement.createPath())
769
+ pill.setAttribute("width" "100")
770
+ pill.setAttribute("height" "40")
771
+ pill.setAttribute("viewBox" "0 0 100 40")
772
+ pill.setAttribute("d" "M0 0L100 0L100 40L0 40Z")
773
+ pill.setAttribute("fill" "#ff0000")
774
+ pill.setAttribute("gradient-from" "#ff0000")
775
+ pill.setAttribute("gradient-to" "#0000ff")
776
+ pill.setAttribute("gradient-dir" "1")
777
+ page.addChild(pill)
778
+ def layout (new EVGLayout())
779
+ layout.layout(page)
780
+ def dl (new EVGDisplayList())
781
+ dl.build(page)
782
+ def shaded:int 0
783
+ def i:int 0
784
+ while (i < (array_length dl.cmds)) {
785
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
786
+ if ((cmd.kind == 6) && cmd.hasGrad) {
787
+ shaded = (shaded + 1)
788
+ c.ok("the near stop is the first colour" ((cmd.r == 255) && (cmd.b == 0)))
789
+ c.ok("and the far stop the second" ((cmd.r2 == 0) && (cmd.b2 == 255)))
790
+ ; And the shape is still there: a gradient that cost the
791
+ ; rings would be the old bug the other way round.
792
+ c.ok("with the rings it was drawn from" ((array_length cmd.pts) > 0))
793
+ }
794
+ i = (i + 1)
795
+ }
796
+ c.ok("the shaded vector is one PATH command, shaded" (shaded == 1))
797
+ ; ONE command, not two. A path is not its box, so the paint goes on
798
+ ; the path and a background rectangle beside it draws the box the
799
+ ; shape is not: the shaded pill came out sitting on a shaded
800
+ ; rectangle, grey leaking past its own rounded corner.
801
+ def boxes:int 0
802
+ def k:int 0
803
+ while (k < (array_length dl.cmds)) {
804
+ def bc:EVGDrawCmd (itemAt dl.cmds k)
805
+ if (bc.kind == 0) {
806
+ boxes = (boxes + 1)
807
+ }
808
+ k = (k + 1)
809
+ }
810
+ c.ok("and no rectangle is drawn under it" (boxes == 0))
811
+ def json:string (dl.toJson())
812
+ c.ok("and both stops reach the painter" (EVGTimingTest.hasSub(json "\"c2\":[0,0,255,1.00]")))
813
+ }
814
+
815
+ sfn checkShadowFromElement:void (c:TimingCheck) {
816
+ def page (new EVGElement())
817
+ page.setAttribute("width" "200")
818
+ page.setAttribute("height" "200")
819
+ def card (new EVGElement())
820
+ card.setAttribute("width" "100")
821
+ card.setAttribute("height" "40")
822
+ card.setAttribute("background-color" "rgb(255,255,255)")
823
+ card.setAttribute("shadow-offset-x" "0px")
824
+ card.setAttribute("shadow-offset-y" "2px")
825
+ card.setAttribute("shadow-radius" "8px")
826
+ card.setAttribute("shadow-color" "#0000001a")
827
+ page.addChild(card)
828
+ ; A box with no shadow named must not grow one.
829
+ def plain (new EVGElement())
830
+ plain.setAttribute("width" "100")
831
+ plain.setAttribute("height" "40")
832
+ plain.setAttribute("background-color" "rgb(0,0,255)")
833
+ page.addChild(plain)
834
+ def layout (new EVGLayout())
835
+ layout.layout(page)
836
+ def dl (new EVGDisplayList())
837
+ dl.build(page)
838
+ def lit:int 0
839
+ def dark:int 0
840
+ def i:int 0
841
+ while (i < (array_length dl.cmds)) {
842
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
843
+ if (cmd.kind == 0) {
844
+ if cmd.hasShadow {
845
+ lit = (lit + 1)
846
+ c.near("it falls two pixels down" cmd.shadowY 2.0)
847
+ c.near("and none sideways" cmd.shadowX 0.0)
848
+ c.near("softened by eight" cmd.shadowBlur 8.0)
849
+ ; The alpha is the whole point. A sticky's shadow is
850
+ ; black at a tenth; at one it is a slab under the note.
851
+ c.near("and translucent, as the colour said" cmd.shadowA 0.1019)
852
+ } {
853
+ dark = (dark + 1)
854
+ }
855
+ }
856
+ i = (i + 1)
857
+ }
858
+ c.ok("the card's shadow reaches the list" (lit == 1))
859
+ c.ok("and the plain box grew none" (dark >= 1))
860
+ def json:string (dl.toJson())
861
+ c.ok("and the JSON carries it, as the binary always did" (EVGTimingTest.hasSub(json "\"sh\":{\"x\":0.00,\"y\":2.00,\"blur\":8.00")))
862
+ }
863
+
864
+ sfn checkImageCrop:void (c:TimingCheck) {
865
+ def page (new EVGElement())
866
+ page.setAttribute("width" "200")
867
+ page.setAttribute("height" "200")
868
+ def img (new EVGElement())
869
+ img.setAttribute("width" "100")
870
+ img.setAttribute("height" "50")
871
+ img.src = "photo.png"
872
+ img.setAttribute("image-view-box" "0.25 0.5 0.5 0.25")
873
+ page.addChild(img)
874
+ def plain (new EVGElement())
875
+ plain.setAttribute("width" "100")
876
+ plain.setAttribute("height" "50")
877
+ plain.src = "photo.png"
878
+ page.addChild(plain)
879
+ def layout (new EVGLayout())
880
+ layout.layout(page)
881
+ def dl (new EVGDisplayList())
882
+ dl.build(page)
883
+ def cropped:int 0
884
+ def whole:int 0
885
+ def i:int 0
886
+ while (i < (array_length dl.cmds)) {
887
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
888
+ if (cmd.kind == 2) {
889
+ if cmd.hasCrop {
890
+ cropped = (cropped + 1)
891
+ c.near("the window starts a quarter across" cmd.cropX 0.25)
892
+ c.near("and halfway down" cmd.cropY 0.5)
893
+ c.near("and is half the width" cmd.cropW 0.5)
894
+ c.near("and a quarter of the height" cmd.cropH 0.25)
895
+ } {
896
+ whole = (whole + 1)
897
+ }
898
+ }
899
+ i = (i + 1)
900
+ }
901
+ c.ok("the cropped picture carries its window" (cropped == 1))
902
+ ; An uncropped picture has to stay uncropped: 0,0,1,1 is a legal
903
+ ; window, so the flag is what separates them and not the numbers.
904
+ c.ok("and the uncropped one carries none" (whole == 1))
905
+ def json:string (dl.toJson())
906
+ c.ok("the window reaches the painter as a UV rectangle" (EVGTimingTest.hasSub(json "\"cu\":[0.2500,0.5000,0.7500,0.7500]")))
907
+ }
908
+
909
+ ; A CLIP INSIDE A TURN IS NOT A CLIP. It crosses the list as x/y/w/h
910
+ ; and every backend makes it a scissor, which is axis-aligned; the
911
+ ; rotation that moves the pixels cannot move the rectangle with them,
912
+ ; so the two end up describing different places. Applied anyway, a
913
+ ; turned frame clips away the very thing it contains — a quarter-turned
914
+ ; arrow on a FigJam board drew nothing at all, its content sent a
915
+ ; hundred pixels above a scissor that stayed where the layout put it.
916
+ ; The clip is dropped instead, which is the same choice culling already
917
+ ; makes for a rotation.
918
+ sfn checkClipInsideTurn:void (c:TimingCheck) {
919
+ def page (new EVGElement())
920
+ page.setAttribute("width" "200")
921
+ page.setAttribute("height" "200")
922
+ def turn (new EVGElement())
923
+ turn.setAttribute("width" "100")
924
+ turn.setAttribute("height" "100")
925
+ turn.setAttribute("transform" "rotate(90deg)")
926
+ page.addChild(turn)
927
+ def box (new EVGElement())
928
+ box.setAttribute("width" "80")
929
+ box.setAttribute("height" "80")
930
+ box.setAttribute("overflow" "hidden")
931
+ turn.addChild(box)
932
+ def ink (new EVGElement())
933
+ ink.setAttribute("width" "40")
934
+ ink.setAttribute("height" "40")
935
+ ink.setAttribute("background-color" "rgb(10,20,30)")
936
+ box.addChild(ink)
937
+ def layout (new EVGLayout())
938
+ layout.layout(page)
939
+ def dl (new EVGDisplayList())
940
+ dl.build(page)
941
+ def clips:int 0
942
+ def inked:int 0
943
+ def i:int 0
944
+ while (i < (array_length dl.cmds)) {
945
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
946
+ if (cmd.kind == 4) {
947
+ clips = (clips + 1)
948
+ }
949
+ if ((cmd.kind == 0) && (cmd.w > 30.0)) {
950
+ inked = (inked + 1)
951
+ }
952
+ i = (i + 1)
953
+ }
954
+ c.ok("no clip is written inside a turn" (clips == 0))
955
+ c.ok("and what it held is still drawn" (inked > 0))
956
+ }
957
+
958
+ ; A clip outside one is still a clip.
959
+ sfn checkClipWithoutTurn:void (c:TimingCheck) {
960
+ def page (new EVGElement())
961
+ page.setAttribute("width" "200")
962
+ page.setAttribute("height" "200")
963
+ def box (new EVGElement())
964
+ box.setAttribute("width" "80")
965
+ box.setAttribute("height" "80")
966
+ box.setAttribute("overflow" "hidden")
967
+ page.addChild(box)
968
+ def ink (new EVGElement())
969
+ ink.setAttribute("width" "40")
970
+ ink.setAttribute("height" "40")
971
+ ink.setAttribute("background-color" "rgb(10,20,30)")
972
+ box.addChild(ink)
973
+ def layout (new EVGLayout())
974
+ layout.layout(page)
975
+ def dl (new EVGDisplayList())
976
+ dl.build(page)
977
+ def clips:int 0
978
+ def i:int 0
979
+ while (i < (array_length dl.cmds)) {
980
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
981
+ if (cmd.kind == 4) {
982
+ clips = (clips + 1)
983
+ }
984
+ i = (i + 1)
985
+ }
986
+ c.ok("a clip with no turn over it is written" (clips > 0))
987
+ }
988
+
989
+ ; A scaled subtree keeps its proportions, type included.
990
+ sfn checkTransformScale:void (c:TimingCheck) {
991
+ def page (new EVGElement())
992
+ page.setAttribute("width" "200")
993
+ page.setAttribute("height" "200")
994
+ def kid (new EVGElement())
995
+ kid.setAttribute("width" "100")
996
+ kid.setAttribute("height" "40")
997
+ kid.setAttribute("background-color" "rgb(10,20,30)")
998
+ kid.setAttribute("transform" "scale(2)")
999
+ page.addChild(kid)
1000
+ def layout (new EVGLayout())
1001
+ layout.layout(page)
1002
+ def dl (new EVGDisplayList())
1003
+ dl.build(page)
1004
+ def i:int 0
1005
+ def checked:boolean false
1006
+ while (i < (array_length dl.cmds)) {
1007
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
1008
+ if ((cmd.kind == 0) && (cmd.w > 50.0)) {
1009
+ c.near("twice as wide" cmd.w 200.0)
1010
+ c.near("twice as tall" cmd.h 80.0)
1011
+ ; Grown about the centre, so it spreads BOTH ways: a 100x40 box
1012
+ ; at the origin doubled about (50, 20) starts at (-50, -20).
1013
+ c.near("and grown about its centre in x" cmd.x (0.0 - 50.0))
1014
+ c.near("and in y" cmd.y (0.0 - 20.0))
1015
+ checked = true
1016
+ }
1017
+ i = (i + 1)
1018
+ }
1019
+ c.ok("a scaled rectangle was found at all" checked)
1020
+ }
1021
+
1022
+ ; `transition: transform 200ms` drives all four numbers off one entry.
1023
+ sfn checkTransformTransition:void (c:TimingCheck) {
1024
+ def tr (new EVGTransition())
1025
+ def el (new EVGElement())
1026
+ el.transitionSpec = "transform 200ms linear"
1027
+ el.setAttribute("transform" "rotate(0deg) scale(1)")
1028
+ tr.reconcile(el)
1029
+ el.setAttribute("transform" "rotate(20deg) scale(1.5)")
1030
+ tr.reconcile(el)
1031
+ tr.advance(el 100.0)
1032
+ c.near("the angle is half way" el.rotate 10.0)
1033
+ c.near("and so is the scale" el.scale 1.25)
1034
+ tr.advance(el 100.0)
1035
+ c.near("it arrives at the angle" el.rotate 20.0)
1036
+ c.near("and the scale" el.scale 1.5)
1037
+ c.ok("and then stops asking for frames" ((tr.busy(el)) == false))
1038
+ }
1039
+
1040
+ ; ==========================================================================
1041
+ ; transform-origin
1042
+ ; ==========================================================================
1043
+
1044
+ ; Seventeen spellings, and the pixel origin a browser resolved each one to
1045
+ ; for a 100x40 box. Transcribed from `oracle/css-timing.json`.
1046
+ ;
1047
+ ; The single-value rows are the ones worth staring at. `10px` is
1048
+ ; (10, 20) — a length sets x and leaves y at the middle — while `top` is
1049
+ ; (50, 0), because `top` names the OTHER axis. An implementation that reads
1050
+ ; "the first value is x" gets every length right and every keyword wrong,
1051
+ ; and passes any test written only with two-value forms.
1052
+ sfn checkOriginResolution:void (c:TimingCheck) {
1053
+ def el (new EVGElement())
1054
+ el.setAttribute("transform-origin" "50% 50%")
1055
+ c.near("origin x of '50% 50%'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1056
+ c.near("origin y of '50% 50%'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1057
+ el.setAttribute("transform-origin" "0 0")
1058
+ c.near("origin x of '0 0'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1059
+ c.near("origin y of '0 0'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1060
+ el.setAttribute("transform-origin" "100% 100%")
1061
+ c.near("origin x of '100% 100%'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1062
+ c.near("origin y of '100% 100%'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1063
+ el.setAttribute("transform-origin" "0% 100%")
1064
+ c.near("origin x of '0% 100%'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1065
+ c.near("origin y of '0% 100%'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1066
+ el.setAttribute("transform-origin" "left top")
1067
+ c.near("origin x of 'left top'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1068
+ c.near("origin y of 'left top'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1069
+ el.setAttribute("transform-origin" "right bottom")
1070
+ c.near("origin x of 'right bottom'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1071
+ c.near("origin y of 'right bottom'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1072
+ el.setAttribute("transform-origin" "center bottom")
1073
+ c.near("origin x of 'center bottom'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1074
+ c.near("origin y of 'center bottom'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1075
+ el.setAttribute("transform-origin" "top left")
1076
+ c.near("origin x of 'top left'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1077
+ c.near("origin y of 'top left'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1078
+ el.setAttribute("transform-origin" "bottom left")
1079
+ c.near("origin x of 'bottom left'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1080
+ c.near("origin y of 'bottom left'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1081
+ el.setAttribute("transform-origin" "right top")
1082
+ c.near("origin x of 'right top'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1083
+ c.near("origin y of 'right top'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1084
+ el.setAttribute("transform-origin" "top center")
1085
+ c.near("origin x of 'top center'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1086
+ c.near("origin y of 'top center'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1087
+ el.setAttribute("transform-origin" "center right")
1088
+ c.near("origin x of 'center right'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1089
+ c.near("origin y of 'center right'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1090
+ el.setAttribute("transform-origin" "10px 4px")
1091
+ c.near("origin x of '10px 4px'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 10.0)
1092
+ c.near("origin y of '10px 4px'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 4.0)
1093
+ el.setAttribute("transform-origin" "25% 10px")
1094
+ c.near("origin x of '25% 10px'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 25.0)
1095
+ c.near("origin y of '25% 10px'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 10.0)
1096
+ el.setAttribute("transform-origin" "left")
1097
+ c.near("origin x of 'left'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1098
+ c.near("origin y of 'left'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1099
+ el.setAttribute("transform-origin" "right")
1100
+ c.near("origin x of 'right'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1101
+ c.near("origin y of 'right'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1102
+ el.setAttribute("transform-origin" "top")
1103
+ c.near("origin x of 'top'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1104
+ c.near("origin y of 'top'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1105
+ el.setAttribute("transform-origin" "bottom")
1106
+ c.near("origin x of 'bottom'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1107
+ c.near("origin y of 'bottom'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1108
+ el.setAttribute("transform-origin" "center")
1109
+ c.near("origin x of 'center'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1110
+ c.near("origin y of 'center'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1111
+ el.setAttribute("transform-origin" "10px")
1112
+ c.near("origin x of '10px'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 10.0)
1113
+ c.near("origin y of '10px'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1114
+ el.setAttribute("transform-origin" "25%")
1115
+ c.near("origin x of '25%'" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 25.0)
1116
+ c.near("origin y of '25%'" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1117
+
1118
+ ; Never set at all is the default, and the default is the centre.
1119
+ def fresh (new EVGElement())
1120
+ c.near("an untouched element turns about its centre in x" (EVGElement.resolveOrigin(fresh.transformOriginX 100.0)) 50.0)
1121
+ c.near("and in y" (EVGElement.resolveOrigin(fresh.transformOriginY 40.0)) 20.0)
1122
+ }
1123
+
1124
+ ; And the box actually ENDS UP where the browser put it. Resolving the
1125
+ ; origin correctly and then turning about something else is a live
1126
+ ; possibility, so the pivot is checked twice: once as a number, once as the
1127
+ ; place a quarter turn leaves the box.
1128
+ ;
1129
+ ; EVG paints an axis-aligned rectangle plus an angle rather than a turned
1130
+ ; quad, so what is compared is the rectangle's CENTRE against the centre of
1131
+ ; the browser's bounding box — the one point the two representations agree
1132
+ ; on exactly.
1133
+ sfn checkOriginTurnsTheBox:void (c:TimingCheck) {
1134
+ EVGTimingTest.turnedCentre(c "0 0" -20.0000 50.0000)
1135
+ EVGTimingTest.turnedCentre(c "100% 100%" 120.0000 -10.0000)
1136
+ EVGTimingTest.turnedCentre(c "10px 4px" -6.0000 44.0000)
1137
+ EVGTimingTest.turnedCentre(c "left" 0.0000 70.0000)
1138
+ EVGTimingTest.turnedCentre(c "top" 30.0000 0.0000)
1139
+ }
1140
+
1141
+ ; Lay out a 100x40 box at the page origin, turn it a quarter turn about
1142
+ ; `spec`, and report where the background rectangle's centre ended up.
1143
+ sfn turnedCentre:void (c:TimingCheck spec:string wantX:double wantY:double) {
1144
+ def page (new EVGElement())
1145
+ page.setAttribute("width" "300")
1146
+ page.setAttribute("height" "300")
1147
+ def kid (new EVGElement())
1148
+ kid.setAttribute("width" "100")
1149
+ kid.setAttribute("height" "40")
1150
+ kid.setAttribute("background-color" "rgb(10,20,30)")
1151
+ kid.setAttribute("transform-origin" spec)
1152
+ kid.setAttribute("transform" "rotate(90deg)")
1153
+ page.addChild(kid)
1154
+ def layout (new EVGLayout())
1155
+ layout.layout(page)
1156
+ def dl (new EVGDisplayList())
1157
+ dl.build(page)
1158
+ def i:int 0
1159
+ def found:boolean false
1160
+ while (i < (array_length dl.cmds)) {
1161
+ def cmd:EVGDrawCmd (itemAt dl.cmds i)
1162
+ if ((cmd.kind == 0) && (cmd.w > 90.0)) {
1163
+ ; A quarter turn about the pivot: the rectangle keeps its own
1164
+ ; width and height in the list, and the ANGLE plus the ORIGIN
1165
+ ; say where it lands. Work out the turned centre the way a
1166
+ ; painter would.
1167
+ def bx:double (cmd.x + (cmd.w / 2.0))
1168
+ def by:double (cmd.y + (cmd.h / 2.0))
1169
+ def rad:double ((cmd.rotate * 3.14159265358979) / 180.0)
1170
+ def dx:double (bx - cmd.rotOriginX)
1171
+ def dy:double (by - cmd.rotOriginY)
1172
+ def tx:double (cmd.rotOriginX + ((dx * (cos rad)) - (dy * (sin rad))))
1173
+ def ty:double (cmd.rotOriginY + ((dx * (sin rad)) + (dy * (cos rad))))
1174
+ c.near(("turned about '" + spec + "' lands at x") tx wantX)
1175
+ c.near(("turned about '" + spec + "' lands at y") ty wantY)
1176
+ found = true
1177
+ }
1178
+ i = (i + 1)
1179
+ }
1180
+ c.ok(("a box was painted for '" + spec + "'") found)
1181
+ }
1182
+
1183
+ ; `transition: transform-origin 200ms linear` from 0% 0% to 100% 100%, on
1184
+ ; the same 100x40 box the browser was asked about. The resolved pixel
1185
+ ; positions at five points are its answer.
1186
+ ;
1187
+ ; The percentages are what move; the pixels below are what those
1188
+ ; percentages resolve to. Checking the resolved value rather than the raw
1189
+ ; number is deliberate — it is the only form the two engines can be
1190
+ ; compared in, and it is what the pivot actually uses.
1191
+ sfn checkOriginTransition:void (c:TimingCheck) {
1192
+ def tr (new EVGTransition())
1193
+ def el (new EVGElement())
1194
+ el.transitionSpec = "transform-origin 200ms linear"
1195
+ el.setAttribute("transform-origin" "0% 0%")
1196
+ tr.reconcile(el)
1197
+ el.setAttribute("transform-origin" "100% 100%")
1198
+ tr.reconcile(el)
1199
+ c.near("at 0ms the origin x is 0px" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 0.0)
1200
+ c.near("at 0ms the origin y is 0px" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 0.0)
1201
+ tr.advance(el 50.0)
1202
+ c.near("at 50ms the origin x is 25px" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 25.0)
1203
+ c.near("at 50ms the origin y is 10px" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 10.0)
1204
+ tr.advance(el 50.0)
1205
+ c.near("at 100ms the origin x is 50px" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 50.0)
1206
+ c.near("at 100ms the origin y is 20px" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1207
+ tr.advance(el 50.0)
1208
+ c.near("at 150ms the origin x is 75px" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 75.0)
1209
+ c.near("at 150ms the origin y is 30px" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 30.0)
1210
+ tr.advance(el 50.0)
1211
+ c.near("at 200ms the origin x is 100px" (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1212
+ c.near("at 200ms the origin y is 40px" (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 40.0)
1213
+ }
1214
+
1215
+ ; A unit change cannot be interpolated, so it lands at once instead of
1216
+ ; sliding through numbers that mean nothing. Written down because it is a
1217
+ ; deliberate difference from CSS, which blends the resolved lengths.
1218
+ sfn checkOriginUnitChangeJumps:void (c:TimingCheck) {
1219
+ def tr (new EVGTransition())
1220
+ def el (new EVGElement())
1221
+ el.transitionSpec = "transform-origin 200ms linear"
1222
+ el.setAttribute("transform-origin" "0% 0%")
1223
+ tr.reconcile(el)
1224
+ el.setAttribute("transform-origin" "80px 0%")
1225
+ tr.reconcile(el)
1226
+ c.near("a percentage becoming a length arrives at once"
1227
+ (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 80.0)
1228
+ ; The OTHER axis kept its unit, so it is still a transition.
1229
+ el.setAttribute("transform-origin" "80px 100%")
1230
+ tr.reconcile(el)
1231
+ tr.advance(el 100.0)
1232
+ c.near("while the axis that kept its unit still travels"
1233
+ (EVGElement.resolveOrigin(el.transformOriginY 40.0)) 20.0)
1234
+ }
1235
+
1236
+ ; And it is a separate property from `transform`: a sheet that transitions
1237
+ ; one must not get the other for free.
1238
+ sfn checkOriginIsItsOwnProperty:void (c:TimingCheck) {
1239
+ def tr (new EVGTransition())
1240
+ def el (new EVGElement())
1241
+ el.transitionSpec = "transform 200ms linear"
1242
+ el.setAttribute("transform-origin" "0% 0%")
1243
+ tr.reconcile(el)
1244
+ el.setAttribute("transform-origin" "100% 100%")
1245
+ tr.reconcile(el)
1246
+ c.near("transitioning transform does not transition the origin"
1247
+ (EVGElement.resolveOrigin(el.transformOriginX 100.0)) 100.0)
1248
+ }
1249
+
1250
+ ; Fading in from `transparent`, against the browser's own numbers.
1251
+ ;
1252
+ ; `transparent` is rgba(0, 0, 0, 0), so channel-by-channel interpolation
1253
+ ; drags this through a dark grey. CSS interpolates in premultiplied alpha
1254
+ ; instead and the RED STAYS AT 244 the whole way while only the opacity
1255
+ ; moves — which is why the red is checked at every step and not just the
1256
+ ; alpha.
1257
+ sfn checkTransparentFade:void (c:TimingCheck) {
1258
+ def tr (new EVGTransition())
1259
+ def el (new EVGElement())
1260
+ el.transitionSpec = "background-color 200ms linear"
1261
+ el.backgroundColor = (EVGColor.parse("transparent"))
1262
+ tr.reconcile(el)
1263
+ el.backgroundColor = (EVGColor.parse("rgb(244,241,254)"))
1264
+ tr.reconcile(el)
1265
+ c.near("at 0ms the red is 0" (EVGTimingTest.redOf(el)) 0.0)
1266
+ c.near("at 0ms the alpha is 0" (EVGTimingTest.alphaOf(el)) 0.0)
1267
+ tr.advance(el 50.0)
1268
+ c.near("at 50ms the red is 244" (EVGTimingTest.redOf(el)) 244.0)
1269
+ c.near("at 50ms the alpha is 0.25" (EVGTimingTest.alphaOf(el)) 0.25)
1270
+ tr.advance(el 50.0)
1271
+ c.near("at 100ms the red is 244" (EVGTimingTest.redOf(el)) 244.0)
1272
+ c.near("at 100ms the alpha is 0.5" (EVGTimingTest.alphaOf(el)) 0.5)
1273
+ tr.advance(el 50.0)
1274
+ c.near("at 150ms the red is 244" (EVGTimingTest.redOf(el)) 244.0)
1275
+ c.near("at 150ms the alpha is 0.75" (EVGTimingTest.alphaOf(el)) 0.75)
1276
+ tr.advance(el 50.0)
1277
+ c.near("at 200ms the red is 244" (EVGTimingTest.redOf(el)) 244.0)
1278
+ c.near("at 200ms the alpha is 1" (EVGTimingTest.alphaOf(el)) 1.0)
1279
+ }
1280
+
1281
+ sfn redOf:double (el:EVGElement) {
1282
+ if el.backgroundColor {
1283
+ def col:EVGColor (unwrap el.backgroundColor)
1284
+ return col.r
1285
+ }
1286
+ return (0.0 - 1.0)
1287
+ }
1288
+
1289
+ sfn alphaOf:double (el:EVGElement) {
1290
+ if el.backgroundColor {
1291
+ def col:EVGColor (unwrap el.backgroundColor)
1292
+ return col.a
1293
+ }
1294
+ return (0.0 - 1.0)
1295
+ }
1296
+
1297
+ ; Reconciling twice in one frame must not stop the animation.
1298
+ ;
1299
+ ; `reconcile` reads the target off the element and `writeBack` writes the
1300
+ ; showing value to the same field, so a second reconcile before the next
1301
+ ; tick reads its own output back — and without a memory of what it wrote it
1302
+ ; concludes the target has moved to wherever the animation has got to, and
1303
+ ; retargets there. The flight then converges to a standstill on the frame it
1304
+ ; started.
1305
+ ;
1306
+ ; This is not a contrived call order. A host asks its tree three questions a
1307
+ ; frame — what to draw, what is under the pointer, what it means — and each
1308
+ ; one lays out and reconciles. It stayed hidden because the stylesheet
1309
+ ; rewrites its own values at the top of every one of them; a property set
1310
+ ; INLINE, which nothing rewrites, simply refused to move.
1311
+ sfn checkReconcileIsIdempotent:void (c:TimingCheck) {
1312
+ def tr (new EVGTransition())
1313
+ def el (new EVGElement())
1314
+ el.transitionSpec = "transform 200ms linear"
1315
+ el.setAttribute("transform" "translate(0, 0)")
1316
+ tr.reconcile(el)
1317
+ el.setAttribute("transform" "translate(0, 100px)")
1318
+ tr.reconcile(el)
1319
+ ; Two more, exactly as a host laying out three times would produce, and
1320
+ ; with nothing re-declaring the target in between.
1321
+ tr.reconcile(el)
1322
+ tr.reconcile(el)
1323
+ tr.advance(el 100.0)
1324
+ c.near("still half way after three reconciles" el.translateY 50.0)
1325
+ tr.reconcile(el)
1326
+ tr.advance(el 100.0)
1327
+ c.near("and it still arrives" el.translateY 100.0)
1328
+ }
1329
+
1330
+ ; The same for a colour, and for the same reason.
1331
+ sfn checkColourReconcileIsIdempotent:void (c:TimingCheck) {
1332
+ def tr (new EVGTransition())
1333
+ def el (new EVGElement())
1334
+ el.transitionSpec = "background-color 200ms linear"
1335
+ el.backgroundColor = (EVGColor.parse("rgb(0,0,0)"))
1336
+ tr.reconcile(el)
1337
+ el.backgroundColor = (EVGColor.parse("rgb(200,200,200)"))
1338
+ tr.reconcile(el)
1339
+ tr.reconcile(el)
1340
+ tr.advance(el 100.0)
1341
+ c.ok("half way after two reconciles" ((EVGTimingTest.red(el)) == 100))
1342
+ tr.reconcile(el)
1343
+ tr.advance(el 100.0)
1344
+ c.ok("and it arrives" ((EVGTimingTest.red(el)) == 200))
1345
+ }
1346
+
1347
+ ; A value the sheet DOES rewrite still reacts to a real change, so the
1348
+ ; memory above must not make the engine deaf. This is the check that would
1349
+ ; fail if "ignore what I wrote" were applied too broadly.
1350
+ sfn checkStillReactsToARealChange:void (c:TimingCheck) {
1351
+ def tr (new EVGTransition())
1352
+ def el (new EVGElement())
1353
+ el.transitionSpec = "transform 200ms linear"
1354
+ el.setAttribute("transform" "translate(0, 0)")
1355
+ tr.reconcile(el)
1356
+ el.setAttribute("transform" "translate(0, 100px)")
1357
+ tr.reconcile(el)
1358
+ tr.advance(el 100.0)
1359
+ ; A third destination, declared while the first is still in flight.
1360
+ el.setAttribute("transform" "translate(0, 20px)")
1361
+ tr.reconcile(el)
1362
+ tr.advance(el 200.0)
1363
+ c.near("a new declaration mid-flight is still heard" el.translateY 20.0)
1364
+ }
1365
+
1366
+ sfn main:void () {
1367
+ def c (new TimingCheck())
1368
+ print "=== EVG timing functions vs the captured browser oracle ==="
1369
+ EVGTimingTest.check_linear(c)
1370
+ EVGTimingTest.check_ease(c)
1371
+ EVGTimingTest.check_ease_in(c)
1372
+ EVGTimingTest.check_ease_out(c)
1373
+ EVGTimingTest.check_ease_in_out(c)
1374
+ EVGTimingTest.check_cubic_bezier_0_68___0_55__0_27__1_55_(c)
1375
+ EVGTimingTest.check_steps_4__end_(c)
1376
+ EVGTimingTest.check_steps_4__start_(c)
1377
+ EVGTimingTest.check_cubic_bezier_1__0__0__1_(c)
1378
+ EVGTimingTest.check_cubic_bezier_0_999__0__0_001__1_(c)
1379
+ EVGTimingTest.checkTopLevelSplitting(c)
1380
+ EVGTimingTest.checkUnknown(c)
1381
+ EVGTimingTest.checkStepKeywords(c)
1382
+ EVGTimingTest.checkSolverEdges(c)
1383
+ EVGTimingTest.checkShorthand(c)
1384
+ EVGTimingTest.checkDelay(c)
1385
+ EVGTimingTest.checkReversal(c)
1386
+ EVGTimingTest.checkDoubleReversal(c)
1387
+ EVGTimingTest.checkDelayedReversal(c)
1388
+ EVGTimingTest.checkThirdValue(c)
1389
+ EVGTimingTest.checkTransformParsing(c)
1390
+ EVGTimingTest.checkTransformOrigin(c)
1391
+ EVGTimingTest.checkTransformScale(c)
1392
+ EVGTimingTest.checkMirroredBox(c)
1393
+ EVGTimingTest.checkTurnedFrame(c)
1394
+ EVGTimingTest.checkPathGradient(c)
1395
+ EVGTimingTest.checkShadowFromElement(c)
1396
+ EVGTimingTest.checkImageCrop(c)
1397
+ EVGTimingTest.checkClipInsideTurn(c)
1398
+ EVGTimingTest.checkClipWithoutTurn(c)
1399
+ EVGTimingTest.checkTransformTransition(c)
1400
+ EVGTimingTest.checkOriginResolution(c)
1401
+ EVGTimingTest.checkOriginTurnsTheBox(c)
1402
+ EVGTimingTest.checkOriginTransition(c)
1403
+ EVGTimingTest.checkOriginUnitChangeJumps(c)
1404
+ EVGTimingTest.checkOriginIsItsOwnProperty(c)
1405
+ EVGTimingTest.checkTransparentFade(c)
1406
+ EVGTimingTest.checkReconcileIsIdempotent(c)
1407
+ EVGTimingTest.checkColourReconcileIsIdempotent(c)
1408
+ EVGTimingTest.checkStillReactsToARealChange(c)
1409
+ print ""
1410
+ print ("passed=" + (to_string c.passed) + " failed=" + (to_string c.failed))
1411
+ if (c.failed > 0) {
1412
+ print "FAILURES"
1413
+ } {
1414
+ print "ALL PASS"
1415
+ }
1416
+ }
1417
+ }