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.
- package/CHANGELOG.md +1064 -20
- package/LICENSE +3 -1
- package/README.md +123 -28
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79923 -56921
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,1199 @@
|
|
|
1
|
+
; SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
; SVGPathParser.rgr - Parse SVG path data and calculate bounding boxes
|
|
4
|
+
;
|
|
5
|
+
; This module parses SVG path commands (M, L, C, Q, A, Z) and provides:
|
|
6
|
+
; 1. Bounding box calculation for scaling
|
|
7
|
+
; 2. PDF path command generation
|
|
8
|
+
|
|
9
|
+
; Path command types
|
|
10
|
+
class PathCommand {
|
|
11
|
+
def type:string "" ; "M", "L", "C", "Q", "A", "Z"
|
|
12
|
+
def x:double 0.0
|
|
13
|
+
def y:double 0.0
|
|
14
|
+
def x1:double 0.0
|
|
15
|
+
def y1:double 0.0
|
|
16
|
+
def x2:double 0.0
|
|
17
|
+
def y2:double 0.0
|
|
18
|
+
def rx:double 0.0
|
|
19
|
+
def ry:double 0.0
|
|
20
|
+
def rotation:double 0.0
|
|
21
|
+
def largeArc:boolean false
|
|
22
|
+
def sweep:boolean false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
; One closed contour of a flattened path, as a flat [x0 y0 x1 y1 ...] list.
|
|
26
|
+
;
|
|
27
|
+
; Rings are the unit a fill is defined over, which is why flattening has to
|
|
28
|
+
; preserve them: `flatten` returns every point of every subpath in one list with
|
|
29
|
+
; no boundary between them, so a consumer can only treat the result as a single
|
|
30
|
+
; polygon. That is why a donut, the letter "O", or any icon with a counter came
|
|
31
|
+
; out solid — the hole and the outline were welded into one ring.
|
|
32
|
+
class PathRing {
|
|
33
|
+
def pts:[double]
|
|
34
|
+
; Z ended this ring. A fill closes every ring by itself; a stroke draws
|
|
35
|
+
; the closing edge only when told the ring has one.
|
|
36
|
+
def closed:boolean false
|
|
37
|
+
|
|
38
|
+
Constructor () {
|
|
39
|
+
def p:[double]
|
|
40
|
+
pts = p
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn pointCount:int () {
|
|
44
|
+
return (idiv (array_length pts) 2)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class PathBounds {
|
|
49
|
+
def minX:double 0.0
|
|
50
|
+
def minY:double 0.0
|
|
51
|
+
def maxX:double 0.0
|
|
52
|
+
def maxY:double 0.0
|
|
53
|
+
def width:double 0.0
|
|
54
|
+
def height:double 0.0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class SVGPathParser {
|
|
58
|
+
def pathData:string ""
|
|
59
|
+
def i:int 0
|
|
60
|
+
def len:int 0
|
|
61
|
+
def currentX:double 0.0
|
|
62
|
+
def currentY:double 0.0
|
|
63
|
+
def startX:double 0.0
|
|
64
|
+
def startY:double 0.0
|
|
65
|
+
def commands:[PathCommand]
|
|
66
|
+
def bounds:PathBounds
|
|
67
|
+
|
|
68
|
+
; The previous segment's second control point, reflected by S and T.
|
|
69
|
+
; `lastCtrlKind` is "C" after a cubic, "Q" after a quadratic and "" after
|
|
70
|
+
; anything else — S following a non-cubic (and T following a non-quadratic)
|
|
71
|
+
; must use the current point instead of a stale reflection.
|
|
72
|
+
def lastCtrlX:double 0.0
|
|
73
|
+
def lastCtrlY:double 0.0
|
|
74
|
+
def lastCtrlKind:string ""
|
|
75
|
+
|
|
76
|
+
; Diagnostics. SVG 1.1 §8.3 says a path with a syntax error renders up to,
|
|
77
|
+
; but not including, the command containing the error — so bad data is
|
|
78
|
+
; partially drawn rather than dropped or fatal. That is implemented, but on
|
|
79
|
+
; its own it is indistinguishable from art that just ends there, which is
|
|
80
|
+
; how unsupported commands went unnoticed for so long. So the parser also
|
|
81
|
+
; records WHAT it refused and where, and callers can surface it.
|
|
82
|
+
def errors:[string]
|
|
83
|
+
def truncated:boolean false
|
|
84
|
+
|
|
85
|
+
; Set when a number was expected and not found. Commands check it after
|
|
86
|
+
; reading their arguments and decline to emit a partial segment.
|
|
87
|
+
def numFail:boolean false
|
|
88
|
+
|
|
89
|
+
Constructor () {
|
|
90
|
+
def emptyCommands:[PathCommand]
|
|
91
|
+
commands = emptyCommands
|
|
92
|
+
def emptyErrors:[string]
|
|
93
|
+
errors = emptyErrors
|
|
94
|
+
bounds = (new PathBounds())
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn getErrors:[string] () {
|
|
98
|
+
return errors
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn hasErrors:boolean () {
|
|
102
|
+
return ((array_length errors) > 0)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
; One line naming every problem, for a renderer or CLI to print. Empty when
|
|
106
|
+
; the path parsed cleanly.
|
|
107
|
+
fn errorSummary:string () {
|
|
108
|
+
def n:int (array_length errors)
|
|
109
|
+
if (n == 0) {
|
|
110
|
+
return ""
|
|
111
|
+
}
|
|
112
|
+
def out:string (itemAt errors 0)
|
|
113
|
+
def k:int 1
|
|
114
|
+
while (k < n) {
|
|
115
|
+
out = (out + "; " + (itemAt errors k))
|
|
116
|
+
k = (k + 1)
|
|
117
|
+
}
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn addError:void (msg:string) {
|
|
122
|
+
push errors (msg + " at offset " + (to_string i))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
; Parse SVG path data string
|
|
126
|
+
fn parse:void (data:string) {
|
|
127
|
+
pathData = data
|
|
128
|
+
i = 0
|
|
129
|
+
len = (strlen data)
|
|
130
|
+
currentX = 0.0
|
|
131
|
+
currentY = 0.0
|
|
132
|
+
startX = 0.0
|
|
133
|
+
startY = 0.0
|
|
134
|
+
|
|
135
|
+
; Reset commands array
|
|
136
|
+
def emptyCommands:[PathCommand]
|
|
137
|
+
commands = emptyCommands
|
|
138
|
+
def emptyErrors:[string]
|
|
139
|
+
errors = emptyErrors
|
|
140
|
+
truncated = false
|
|
141
|
+
lastCtrlKind = ""
|
|
142
|
+
|
|
143
|
+
; The command letter currently in force. SVG lets a command's argument
|
|
144
|
+
; set repeat without repeating the letter — "M 10 10 20 20 30 30" is a
|
|
145
|
+
; moveto followed by two linetos — and real files lean on it heavily,
|
|
146
|
+
; both hand-written and from optimizers. The old loop read one letter,
|
|
147
|
+
; consumed exactly one argument set, and then skipped the leftover
|
|
148
|
+
; numbers one character at a time, silently losing every repeat.
|
|
149
|
+
def pending:int 0
|
|
150
|
+
|
|
151
|
+
while (i < len) {
|
|
152
|
+
this.skipWhitespace()
|
|
153
|
+
if (i >= len) {
|
|
154
|
+
break
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
def ch:char (charAt pathData i)
|
|
158
|
+
def chInt:int ch
|
|
159
|
+
|
|
160
|
+
def isLetter:boolean false
|
|
161
|
+
if ((chInt >= 65) && (chInt <= 90)) { isLetter = true }
|
|
162
|
+
if ((chInt >= 97) && (chInt <= 122)) { isLetter = true }
|
|
163
|
+
|
|
164
|
+
if isLetter {
|
|
165
|
+
pending = chInt
|
|
166
|
+
i = (i + 1)
|
|
167
|
+
} {
|
|
168
|
+
if (pending == 0) {
|
|
169
|
+
this.addError("path data must begin with a command letter")
|
|
170
|
+
truncated = true
|
|
171
|
+
break
|
|
172
|
+
}
|
|
173
|
+
; A repeated moveto is a lineto — the pen only moves once.
|
|
174
|
+
if (pending == 77) { pending = 76 }
|
|
175
|
+
if (pending == 109) { pending = 108 }
|
|
176
|
+
; closepath takes no arguments, so a number here cannot be a
|
|
177
|
+
; repeat of anything. Without this the loop would spin forever
|
|
178
|
+
; on a command that consumes nothing.
|
|
179
|
+
if (pending == 90) {
|
|
180
|
+
this.addError("unexpected number after closepath")
|
|
181
|
+
truncated = true
|
|
182
|
+
break
|
|
183
|
+
}
|
|
184
|
+
if (pending == 122) {
|
|
185
|
+
this.addError("unexpected number after closepath")
|
|
186
|
+
truncated = true
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
def ok:boolean (this.parseCommand(pending))
|
|
192
|
+
if (ok == false) {
|
|
193
|
+
truncated = true
|
|
194
|
+
break
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
; Calculate bounding box
|
|
199
|
+
this.calculateBounds()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
; True when the next non-separator character could start a number, which is
|
|
203
|
+
; how an implicit repeat is told apart from the end of the path.
|
|
204
|
+
fn hasNumberAhead:boolean () {
|
|
205
|
+
this.skipWhitespace()
|
|
206
|
+
if (i >= len) {
|
|
207
|
+
return false
|
|
208
|
+
}
|
|
209
|
+
def ch:char (charAt pathData i)
|
|
210
|
+
def chInt:int ch
|
|
211
|
+
if ((chInt >= 48) && (chInt <= 57)) { return true }
|
|
212
|
+
if (chInt == 46) { return true }
|
|
213
|
+
if (chInt == 45) { return true }
|
|
214
|
+
if (chInt == 43) { return true }
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
; An arc flag is a single character, 0 or 1, and needs no separator after
|
|
219
|
+
; it: "a1 1 0 011 1" is legal and optimizers emit exactly that. Reading a
|
|
220
|
+
; flag as a general number would swallow the digits that follow it and throw
|
|
221
|
+
; the rest of the arc out of alignment.
|
|
222
|
+
fn parseFlag:boolean () {
|
|
223
|
+
this.skipWhitespace()
|
|
224
|
+
if (i >= len) {
|
|
225
|
+
numFail = true
|
|
226
|
+
this.addError("arc flag expected")
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
def ch:char (charAt pathData i)
|
|
230
|
+
def chInt:int ch
|
|
231
|
+
if (chInt == 48) {
|
|
232
|
+
i = (i + 1)
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
if (chInt == 49) {
|
|
236
|
+
i = (i + 1)
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
numFail = true
|
|
240
|
+
this.addError("arc flag must be 0 or 1")
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fn skipWhitespace:void () {
|
|
245
|
+
while (i < len) {
|
|
246
|
+
def ch:char (charAt pathData i)
|
|
247
|
+
def chInt:int ch
|
|
248
|
+
; Space, tab, newline, carriage return, comma
|
|
249
|
+
if ((chInt == 32) || (chInt == 9) || (chInt == 10) || (chInt == 13) || (chInt == 44)) {
|
|
250
|
+
i = i + 1
|
|
251
|
+
} {
|
|
252
|
+
break
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn parseNumber:double () {
|
|
258
|
+
this.skipWhitespace()
|
|
259
|
+
def start:int i
|
|
260
|
+
def ch:char (charAt pathData i)
|
|
261
|
+
def chInt:int ch
|
|
262
|
+
|
|
263
|
+
; Handle optional sign
|
|
264
|
+
if ((chInt == 45) || (chInt == 43)) {
|
|
265
|
+
i = i + 1
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
; Parse integer part
|
|
269
|
+
while (i < len) {
|
|
270
|
+
def ch2:char (charAt pathData i)
|
|
271
|
+
def chInt2:int ch2
|
|
272
|
+
if ((chInt2 >= 48) && (chInt2 <= 57)) {
|
|
273
|
+
i = i + 1
|
|
274
|
+
} {
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
; Parse decimal part
|
|
280
|
+
if (i < len) {
|
|
281
|
+
def ch3:char (charAt pathData i)
|
|
282
|
+
def chInt3:int ch3
|
|
283
|
+
if (chInt3 == 46) {
|
|
284
|
+
i = i + 1
|
|
285
|
+
while (i < len) {
|
|
286
|
+
def ch4:char (charAt pathData i)
|
|
287
|
+
def chInt4:int ch4
|
|
288
|
+
if ((chInt4 >= 48) && (chInt4 <= 57)) {
|
|
289
|
+
i = i + 1
|
|
290
|
+
} {
|
|
291
|
+
break
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
; Parse exponent
|
|
298
|
+
if (i < len) {
|
|
299
|
+
def ch5:char (charAt pathData i)
|
|
300
|
+
def chInt5:int ch5
|
|
301
|
+
if ((chInt5 == 101) || (chInt5 == 69)) {
|
|
302
|
+
i = i + 1
|
|
303
|
+
if (i < len) {
|
|
304
|
+
def ch6:char (charAt pathData i)
|
|
305
|
+
def chInt6:int ch6
|
|
306
|
+
if ((chInt6 == 45) || (chInt6 == 43)) {
|
|
307
|
+
i = i + 1
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
while (i < len) {
|
|
311
|
+
def ch7:char (charAt pathData i)
|
|
312
|
+
def chInt7:int ch7
|
|
313
|
+
if ((chInt7 >= 48) && (chInt7 <= 57)) {
|
|
314
|
+
i = i + 1
|
|
315
|
+
} {
|
|
316
|
+
break
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
def numStr:string (substring pathData start i)
|
|
323
|
+
; A sign with no digits after it, or nothing at all, is a syntax error
|
|
324
|
+
; rather than a zero. Reading it as zero is how a malformed path used to
|
|
325
|
+
; turn into geometry that looked deliberate.
|
|
326
|
+
def parsed@(optional):double (str2double numStr)
|
|
327
|
+
if parsed {
|
|
328
|
+
return (unwrap parsed)
|
|
329
|
+
}
|
|
330
|
+
numFail = true
|
|
331
|
+
this.addError("expected a number")
|
|
332
|
+
return 0.0
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
; Parse ONE argument set for `cmdInt`. Returns false when the arguments
|
|
336
|
+
; were malformed or the command is not one this profile implements, and the
|
|
337
|
+
; caller then stops — matching SVG 1.1 §8.3, which renders a path up to but
|
|
338
|
+
; not including the command containing the first error.
|
|
339
|
+
;
|
|
340
|
+
; The command letter has already been consumed by the caller, because an
|
|
341
|
+
; implicit repeat has no letter to consume.
|
|
342
|
+
fn parseCommand:boolean (cmdInt:int) {
|
|
343
|
+
numFail = false
|
|
344
|
+
|
|
345
|
+
; ----------------------------------------------------------------- M m
|
|
346
|
+
if ((cmdInt == 77) || (cmdInt == 109)) {
|
|
347
|
+
def x:double (this.parseNumber())
|
|
348
|
+
def y:double (this.parseNumber())
|
|
349
|
+
if numFail {
|
|
350
|
+
return false
|
|
351
|
+
}
|
|
352
|
+
if (cmdInt == 109) {
|
|
353
|
+
x = currentX + x
|
|
354
|
+
y = currentY + y
|
|
355
|
+
}
|
|
356
|
+
def pathCmd (new PathCommand())
|
|
357
|
+
pathCmd.type = "M"
|
|
358
|
+
pathCmd.x = x
|
|
359
|
+
pathCmd.y = y
|
|
360
|
+
push commands pathCmd
|
|
361
|
+
currentX = x
|
|
362
|
+
currentY = y
|
|
363
|
+
startX = x
|
|
364
|
+
startY = y
|
|
365
|
+
lastCtrlKind = ""
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
; ----------------------------------------------------------------- L l
|
|
370
|
+
if ((cmdInt == 76) || (cmdInt == 108)) {
|
|
371
|
+
def x:double (this.parseNumber())
|
|
372
|
+
def y:double (this.parseNumber())
|
|
373
|
+
if numFail {
|
|
374
|
+
return false
|
|
375
|
+
}
|
|
376
|
+
if (cmdInt == 108) {
|
|
377
|
+
x = currentX + x
|
|
378
|
+
y = currentY + y
|
|
379
|
+
}
|
|
380
|
+
this.emitLine(x y)
|
|
381
|
+
return true
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
; ----------------------------------------------------------------- H h
|
|
385
|
+
if ((cmdInt == 72) || (cmdInt == 104)) {
|
|
386
|
+
def x:double (this.parseNumber())
|
|
387
|
+
if numFail {
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
if (cmdInt == 104) {
|
|
391
|
+
x = currentX + x
|
|
392
|
+
}
|
|
393
|
+
this.emitLine(x currentY)
|
|
394
|
+
return true
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
; ----------------------------------------------------------------- V v
|
|
398
|
+
if ((cmdInt == 86) || (cmdInt == 118)) {
|
|
399
|
+
def y:double (this.parseNumber())
|
|
400
|
+
if numFail {
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
if (cmdInt == 118) {
|
|
404
|
+
y = currentY + y
|
|
405
|
+
}
|
|
406
|
+
this.emitLine(currentX y)
|
|
407
|
+
return true
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
; ----------------------------------------------------------------- C c
|
|
411
|
+
if ((cmdInt == 67) || (cmdInt == 99)) {
|
|
412
|
+
def x1:double (this.parseNumber())
|
|
413
|
+
def y1:double (this.parseNumber())
|
|
414
|
+
def x2:double (this.parseNumber())
|
|
415
|
+
def y2:double (this.parseNumber())
|
|
416
|
+
def x:double (this.parseNumber())
|
|
417
|
+
def y:double (this.parseNumber())
|
|
418
|
+
if numFail {
|
|
419
|
+
return false
|
|
420
|
+
}
|
|
421
|
+
if (cmdInt == 99) {
|
|
422
|
+
x1 = currentX + x1
|
|
423
|
+
y1 = currentY + y1
|
|
424
|
+
x2 = currentX + x2
|
|
425
|
+
y2 = currentY + y2
|
|
426
|
+
x = currentX + x
|
|
427
|
+
y = currentY + y
|
|
428
|
+
}
|
|
429
|
+
this.emitCubic(x1 y1 x2 y2 x y)
|
|
430
|
+
return true
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
; ----------------------------------------------------------------- S s
|
|
434
|
+
; Smooth cubic: the first control point is the reflection of the
|
|
435
|
+
; previous cubic's second control point about the current point. After
|
|
436
|
+
; anything that was not a cubic, the spec says to use the current point
|
|
437
|
+
; itself, which is what makes a lone S behave like a plain C.
|
|
438
|
+
if ((cmdInt == 83) || (cmdInt == 115)) {
|
|
439
|
+
def x2:double (this.parseNumber())
|
|
440
|
+
def y2:double (this.parseNumber())
|
|
441
|
+
def x:double (this.parseNumber())
|
|
442
|
+
def y:double (this.parseNumber())
|
|
443
|
+
if numFail {
|
|
444
|
+
return false
|
|
445
|
+
}
|
|
446
|
+
if (cmdInt == 115) {
|
|
447
|
+
x2 = currentX + x2
|
|
448
|
+
y2 = currentY + y2
|
|
449
|
+
x = currentX + x
|
|
450
|
+
y = currentY + y
|
|
451
|
+
}
|
|
452
|
+
def x1:double currentX
|
|
453
|
+
def y1:double currentY
|
|
454
|
+
if (lastCtrlKind == "C") {
|
|
455
|
+
x1 = ((2.0 * currentX) - lastCtrlX)
|
|
456
|
+
y1 = ((2.0 * currentY) - lastCtrlY)
|
|
457
|
+
}
|
|
458
|
+
this.emitCubic(x1 y1 x2 y2 x y)
|
|
459
|
+
return true
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
; ----------------------------------------------------------------- Q q
|
|
463
|
+
if ((cmdInt == 81) || (cmdInt == 113)) {
|
|
464
|
+
def x1:double (this.parseNumber())
|
|
465
|
+
def y1:double (this.parseNumber())
|
|
466
|
+
def x:double (this.parseNumber())
|
|
467
|
+
def y:double (this.parseNumber())
|
|
468
|
+
if numFail {
|
|
469
|
+
return false
|
|
470
|
+
}
|
|
471
|
+
if (cmdInt == 113) {
|
|
472
|
+
x1 = currentX + x1
|
|
473
|
+
y1 = currentY + y1
|
|
474
|
+
x = currentX + x
|
|
475
|
+
y = currentY + y
|
|
476
|
+
}
|
|
477
|
+
this.emitQuad(x1 y1 x y)
|
|
478
|
+
return true
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
; ----------------------------------------------------------------- T t
|
|
482
|
+
; Smooth quadratic: same reflection rule, against the previous
|
|
483
|
+
; quadratic's single control point.
|
|
484
|
+
if ((cmdInt == 84) || (cmdInt == 116)) {
|
|
485
|
+
def x:double (this.parseNumber())
|
|
486
|
+
def y:double (this.parseNumber())
|
|
487
|
+
if numFail {
|
|
488
|
+
return false
|
|
489
|
+
}
|
|
490
|
+
if (cmdInt == 116) {
|
|
491
|
+
x = currentX + x
|
|
492
|
+
y = currentY + y
|
|
493
|
+
}
|
|
494
|
+
def x1:double currentX
|
|
495
|
+
def y1:double currentY
|
|
496
|
+
if (lastCtrlKind == "Q") {
|
|
497
|
+
x1 = ((2.0 * currentX) - lastCtrlX)
|
|
498
|
+
y1 = ((2.0 * currentY) - lastCtrlY)
|
|
499
|
+
}
|
|
500
|
+
this.emitQuad(x1 y1 x y)
|
|
501
|
+
return true
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
; ----------------------------------------------------------------- A a
|
|
505
|
+
if ((cmdInt == 65) || (cmdInt == 97)) {
|
|
506
|
+
def rx:double (this.parseNumber())
|
|
507
|
+
def ry:double (this.parseNumber())
|
|
508
|
+
def rot:double (this.parseNumber())
|
|
509
|
+
def largeArc:boolean (this.parseFlag())
|
|
510
|
+
def sweep:boolean (this.parseFlag())
|
|
511
|
+
def x:double (this.parseNumber())
|
|
512
|
+
def y:double (this.parseNumber())
|
|
513
|
+
if numFail {
|
|
514
|
+
return false
|
|
515
|
+
}
|
|
516
|
+
if (cmdInt == 97) {
|
|
517
|
+
x = currentX + x
|
|
518
|
+
y = currentY + y
|
|
519
|
+
}
|
|
520
|
+
this.emitArc(rx ry rot largeArc sweep x y)
|
|
521
|
+
return true
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
; ----------------------------------------------------------------- Z z
|
|
525
|
+
if ((cmdInt == 90) || (cmdInt == 122)) {
|
|
526
|
+
def pathCmd (new PathCommand())
|
|
527
|
+
pathCmd.type = "Z"
|
|
528
|
+
push commands pathCmd
|
|
529
|
+
currentX = startX
|
|
530
|
+
currentY = startY
|
|
531
|
+
lastCtrlKind = ""
|
|
532
|
+
return true
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
; Anything else is outside this profile. Saying so is the point: an
|
|
536
|
+
; unknown letter used to be skipped one character at a time, taking its
|
|
537
|
+
; arguments with it, so the path silently closed across the gap and
|
|
538
|
+
; looked like art that had always been that shape.
|
|
539
|
+
this.addError("unsupported path command '" + (strfromcode cmdInt) + "'")
|
|
540
|
+
return false
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
; --------------------------------------------------------------------------
|
|
544
|
+
; Segment emitters — one place each, so the pen and the reflection state
|
|
545
|
+
; cannot get out of step with what was pushed.
|
|
546
|
+
; --------------------------------------------------------------------------
|
|
547
|
+
|
|
548
|
+
fn emitLine:void (x:double y:double) {
|
|
549
|
+
def pathCmd (new PathCommand())
|
|
550
|
+
pathCmd.type = "L"
|
|
551
|
+
pathCmd.x = x
|
|
552
|
+
pathCmd.y = y
|
|
553
|
+
push commands pathCmd
|
|
554
|
+
currentX = x
|
|
555
|
+
currentY = y
|
|
556
|
+
lastCtrlKind = ""
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
fn emitCubic:void (x1:double y1:double x2:double y2:double x:double y:double) {
|
|
560
|
+
def pathCmd (new PathCommand())
|
|
561
|
+
pathCmd.type = "C"
|
|
562
|
+
pathCmd.x1 = x1
|
|
563
|
+
pathCmd.y1 = y1
|
|
564
|
+
pathCmd.x2 = x2
|
|
565
|
+
pathCmd.y2 = y2
|
|
566
|
+
pathCmd.x = x
|
|
567
|
+
pathCmd.y = y
|
|
568
|
+
push commands pathCmd
|
|
569
|
+
currentX = x
|
|
570
|
+
currentY = y
|
|
571
|
+
lastCtrlX = x2
|
|
572
|
+
lastCtrlY = y2
|
|
573
|
+
lastCtrlKind = "C"
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
fn emitQuad:void (x1:double y1:double x:double y:double) {
|
|
577
|
+
def pathCmd (new PathCommand())
|
|
578
|
+
pathCmd.type = "Q"
|
|
579
|
+
pathCmd.x1 = x1
|
|
580
|
+
pathCmd.y1 = y1
|
|
581
|
+
pathCmd.x = x
|
|
582
|
+
pathCmd.y = y
|
|
583
|
+
push commands pathCmd
|
|
584
|
+
currentX = x
|
|
585
|
+
currentY = y
|
|
586
|
+
lastCtrlX = x1
|
|
587
|
+
lastCtrlY = y1
|
|
588
|
+
lastCtrlKind = "Q"
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
; --------------------------------------------------------------------------
|
|
592
|
+
; Elliptical arc -> cubics
|
|
593
|
+
; --------------------------------------------------------------------------
|
|
594
|
+
; SVG 1.1 Appendix F.6.5 (endpoint -> centre parameterisation) and F.6.6
|
|
595
|
+
; (correction of out-of-range radii). The arc is converted here, once, so
|
|
596
|
+
; every consumer gets arcs for free: PDF has no arc operator, the raster
|
|
597
|
+
; side flattens curves anyway, and the previous state of affairs was that
|
|
598
|
+
; PathCommand carried rx/ry/rotation/largeArc/sweep fields that nothing ever
|
|
599
|
+
; produced.
|
|
600
|
+
;
|
|
601
|
+
; The conversion follows the same construction as the older SVGPath.rgr in
|
|
602
|
+
; evg/original/: split the sweep into pieces of at most 90 degrees and give
|
|
603
|
+
; each piece a cubic whose control points sit along the arc's tangents.
|
|
604
|
+
fn emitArc:void (rxIn:double ryIn:double rotDeg:double largeArc:boolean sweep:boolean x:double y:double) {
|
|
605
|
+
def x1:double currentX
|
|
606
|
+
def y1:double currentY
|
|
607
|
+
def x2:double x
|
|
608
|
+
def y2:double y
|
|
609
|
+
|
|
610
|
+
def rx:double (fabs rxIn)
|
|
611
|
+
def ry:double (fabs ryIn)
|
|
612
|
+
|
|
613
|
+
def dx:double (x1 - x2)
|
|
614
|
+
def dy:double (y1 - y2)
|
|
615
|
+
def d:double (sqrt ((dx * dx) + (dy * dy)))
|
|
616
|
+
|
|
617
|
+
; Degenerate: coincident endpoints, or a radius of zero. The spec says
|
|
618
|
+
; to draw a straight line — not to drop the segment.
|
|
619
|
+
if (d < 0.00001) {
|
|
620
|
+
this.emitLine(x2 y2)
|
|
621
|
+
return
|
|
622
|
+
}
|
|
623
|
+
if (rx < 0.00001) {
|
|
624
|
+
this.emitLine(x2 y2)
|
|
625
|
+
return
|
|
626
|
+
}
|
|
627
|
+
if (ry < 0.00001) {
|
|
628
|
+
this.emitLine(x2 y2)
|
|
629
|
+
return
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
def PI:double (M_PI)
|
|
633
|
+
def rot:double ((rotDeg / 180.0) * PI)
|
|
634
|
+
def sinrot:double (sin rot)
|
|
635
|
+
def cosrot:double (cos rot)
|
|
636
|
+
|
|
637
|
+
; Endpoint -> centre, step 1: the endpoint midpoint in the ellipse's
|
|
638
|
+
; own rotated frame.
|
|
639
|
+
def x1p:double (((cosrot * dx) / 2.0) + ((sinrot * dy) / 2.0))
|
|
640
|
+
def y1p:double ((((0.0 - sinrot) * dx) / 2.0) + ((cosrot * dy) / 2.0))
|
|
641
|
+
|
|
642
|
+
; F.6.6: radii too small to span the endpoints are scaled up until they
|
|
643
|
+
; exactly reach, rather than the arc being abandoned.
|
|
644
|
+
def lambda:double (((x1p * x1p) / (rx * rx)) + ((y1p * y1p) / (ry * ry)))
|
|
645
|
+
if (lambda > 1.0) {
|
|
646
|
+
def k:double (sqrt lambda)
|
|
647
|
+
rx = (rx * k)
|
|
648
|
+
ry = (ry * k)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
def sa:double ((((rx * rx) * (ry * ry)) - ((rx * rx) * (y1p * y1p))) - ((ry * ry) * (x1p * x1p)))
|
|
652
|
+
def sb:double (((rx * rx) * (y1p * y1p)) + ((ry * ry) * (x1p * x1p)))
|
|
653
|
+
if (sa < 0.0) {
|
|
654
|
+
sa = 0.0
|
|
655
|
+
}
|
|
656
|
+
def s:double 0.0
|
|
657
|
+
if (sb > 0.0) {
|
|
658
|
+
s = (sqrt (sa / sb))
|
|
659
|
+
}
|
|
660
|
+
; The two flags pick which of the two possible ellipses is drawn.
|
|
661
|
+
if (largeArc == sweep) {
|
|
662
|
+
s = (0.0 - s)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
def cxp:double (((s * rx) * y1p) / ry)
|
|
666
|
+
def cyp:double ((((0.0 - s) * ry) * x1p) / rx)
|
|
667
|
+
def cx:double (((x1 + x2) / 2.0) + ((cosrot * cxp) - (sinrot * cyp)))
|
|
668
|
+
def cy:double (((y1 + y2) / 2.0) + ((sinrot * cxp) + (cosrot * cyp)))
|
|
669
|
+
|
|
670
|
+
; Start angle and swept angle, on the unit circle the ellipse maps from.
|
|
671
|
+
def ux:double ((x1p - cxp) / rx)
|
|
672
|
+
def uy:double ((y1p - cyp) / ry)
|
|
673
|
+
def vx:double (((0.0 - x1p) - cxp) / rx)
|
|
674
|
+
def vy:double (((0.0 - y1p) - cyp) / ry)
|
|
675
|
+
|
|
676
|
+
def a1:double (this.vecAngle(1.0 0.0 ux uy))
|
|
677
|
+
def da:double (this.vecAngle(ux uy vx vy))
|
|
678
|
+
|
|
679
|
+
if (sweep == false) {
|
|
680
|
+
if (da > 0.0) {
|
|
681
|
+
da = (da - (2.0 * PI))
|
|
682
|
+
}
|
|
683
|
+
} {
|
|
684
|
+
if (da < 0.0) {
|
|
685
|
+
da = ((2.0 * PI) + da)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
; One cubic per <=90 degrees keeps the approximation error far below
|
|
690
|
+
; anything a renderer can show.
|
|
691
|
+
def ndivs:int (to_int (((fabs da) / (PI * 0.5)) + 1.0))
|
|
692
|
+
def hda:double ((da / (to_double ndivs)) / 2.0)
|
|
693
|
+
def kappa:double (fabs (((4.0 / 3.0) * (1.0 - (cos hda))) / (sin hda)))
|
|
694
|
+
if (da < 0.0) {
|
|
695
|
+
kappa = (0.0 - kappa)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
def px:double 0.0
|
|
699
|
+
def py:double 0.0
|
|
700
|
+
def ptanx:double 0.0
|
|
701
|
+
def ptany:double 0.0
|
|
702
|
+
|
|
703
|
+
def k:int 0
|
|
704
|
+
while (k <= ndivs) {
|
|
705
|
+
def a:double (a1 + ((da * (to_double k)) / (to_double ndivs)))
|
|
706
|
+
def cosa:double (cos a)
|
|
707
|
+
def sina:double (sin a)
|
|
708
|
+
|
|
709
|
+
; Point on the ellipse, and the tangent scaled by kappa — both
|
|
710
|
+
; carried through the same rotation and translation.
|
|
711
|
+
def ex:double (((cosrot * (cosa * rx)) - (sinrot * (sina * ry))) + cx)
|
|
712
|
+
def ey:double (((sinrot * (cosa * rx)) + (cosrot * (sina * ry))) + cy)
|
|
713
|
+
def tvx:double ((0.0 - sina) * (rx * kappa))
|
|
714
|
+
def tvy:double (cosa * (ry * kappa))
|
|
715
|
+
def tanx:double ((cosrot * tvx) - (sinrot * tvy))
|
|
716
|
+
def tany:double ((sinrot * tvx) + (cosrot * tvy))
|
|
717
|
+
|
|
718
|
+
if (k > 0) {
|
|
719
|
+
this.emitCubic((px + ptanx) (py + ptany) (ex - tanx) (ey - tany) ex ey)
|
|
720
|
+
}
|
|
721
|
+
px = ex
|
|
722
|
+
py = ey
|
|
723
|
+
ptanx = tanx
|
|
724
|
+
ptany = tany
|
|
725
|
+
k = (k + 1)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
; An arc leaves no control point for S/T to reflect.
|
|
729
|
+
currentX = x2
|
|
730
|
+
currentY = y2
|
|
731
|
+
lastCtrlKind = ""
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
; Signed angle from (ux, uy) to (vx, vy), in radians.
|
|
735
|
+
fn vecAngle:double (ux:double uy:double vx:double vy:double) {
|
|
736
|
+
def magU:double (sqrt ((ux * ux) + (uy * uy)))
|
|
737
|
+
def magV:double (sqrt ((vx * vx) + (vy * vy)))
|
|
738
|
+
def denom:double (magU * magV)
|
|
739
|
+
if (denom < 0.0000000001) {
|
|
740
|
+
return 0.0
|
|
741
|
+
}
|
|
742
|
+
def r:double (((ux * vx) + (uy * vy)) / denom)
|
|
743
|
+
; Rounding can push the cosine a hair outside [-1, 1], where acos is
|
|
744
|
+
; undefined and would poison every later coordinate.
|
|
745
|
+
if (r < (0.0 - 1.0)) {
|
|
746
|
+
r = (0.0 - 1.0)
|
|
747
|
+
}
|
|
748
|
+
if (r > 1.0) {
|
|
749
|
+
r = 1.0
|
|
750
|
+
}
|
|
751
|
+
def sign:double 1.0
|
|
752
|
+
if ((ux * vy) < (uy * vx)) {
|
|
753
|
+
sign = (0.0 - 1.0)
|
|
754
|
+
}
|
|
755
|
+
return (sign * (acos r))
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
fn calculateBounds:void () {
|
|
760
|
+
if ((array_length commands) == 0) {
|
|
761
|
+
return
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
def minX:double 999999.0
|
|
765
|
+
def minY:double 999999.0
|
|
766
|
+
def maxX:double -999999.0
|
|
767
|
+
def maxY:double -999999.0
|
|
768
|
+
|
|
769
|
+
def i:int 0
|
|
770
|
+
while (i < (array_length commands)) {
|
|
771
|
+
def cmd:PathCommand (itemAt commands i)
|
|
772
|
+
|
|
773
|
+
if ((cmd.type == "M") || (cmd.type == "L")) {
|
|
774
|
+
if (cmd.x < minX) {
|
|
775
|
+
minX = cmd.x
|
|
776
|
+
}
|
|
777
|
+
if (cmd.x > maxX) {
|
|
778
|
+
maxX = cmd.x
|
|
779
|
+
}
|
|
780
|
+
if (cmd.y < minY) {
|
|
781
|
+
minY = cmd.y
|
|
782
|
+
}
|
|
783
|
+
if (cmd.y > maxY) {
|
|
784
|
+
maxY = cmd.y
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (cmd.type == "C") {
|
|
789
|
+
; Include control points for better bounds
|
|
790
|
+
if (cmd.x1 < minX) {
|
|
791
|
+
minX = cmd.x1
|
|
792
|
+
}
|
|
793
|
+
if (cmd.x1 > maxX) {
|
|
794
|
+
maxX = cmd.x1
|
|
795
|
+
}
|
|
796
|
+
if (cmd.y1 < minY) {
|
|
797
|
+
minY = cmd.y1
|
|
798
|
+
}
|
|
799
|
+
if (cmd.y1 > maxY) {
|
|
800
|
+
maxY = cmd.y1
|
|
801
|
+
}
|
|
802
|
+
if (cmd.x2 < minX) {
|
|
803
|
+
minX = cmd.x2
|
|
804
|
+
}
|
|
805
|
+
if (cmd.x2 > maxX) {
|
|
806
|
+
maxX = cmd.x2
|
|
807
|
+
}
|
|
808
|
+
if (cmd.y2 < minY) {
|
|
809
|
+
minY = cmd.y2
|
|
810
|
+
}
|
|
811
|
+
if (cmd.y2 > maxY) {
|
|
812
|
+
maxY = cmd.y2
|
|
813
|
+
}
|
|
814
|
+
if (cmd.x < minX) {
|
|
815
|
+
minX = cmd.x
|
|
816
|
+
}
|
|
817
|
+
if (cmd.x > maxX) {
|
|
818
|
+
maxX = cmd.x
|
|
819
|
+
}
|
|
820
|
+
if (cmd.y < minY) {
|
|
821
|
+
minY = cmd.y
|
|
822
|
+
}
|
|
823
|
+
if (cmd.y > maxY) {
|
|
824
|
+
maxY = cmd.y
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (cmd.type == "Q") {
|
|
829
|
+
; Include control point
|
|
830
|
+
if (cmd.x1 < minX) {
|
|
831
|
+
minX = cmd.x1
|
|
832
|
+
}
|
|
833
|
+
if (cmd.x1 > maxX) {
|
|
834
|
+
maxX = cmd.x1
|
|
835
|
+
}
|
|
836
|
+
if (cmd.y1 < minY) {
|
|
837
|
+
minY = cmd.y1
|
|
838
|
+
}
|
|
839
|
+
if (cmd.y1 > maxY) {
|
|
840
|
+
maxY = cmd.y1
|
|
841
|
+
}
|
|
842
|
+
if (cmd.x < minX) {
|
|
843
|
+
minX = cmd.x
|
|
844
|
+
}
|
|
845
|
+
if (cmd.x > maxX) {
|
|
846
|
+
maxX = cmd.x
|
|
847
|
+
}
|
|
848
|
+
if (cmd.y < minY) {
|
|
849
|
+
minY = cmd.y
|
|
850
|
+
}
|
|
851
|
+
if (cmd.y > maxY) {
|
|
852
|
+
maxY = cmd.y
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
i = i + 1
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
bounds.minX = minX
|
|
860
|
+
bounds.minY = minY
|
|
861
|
+
bounds.maxX = maxX
|
|
862
|
+
bounds.maxY = maxY
|
|
863
|
+
bounds.width = maxX - minX
|
|
864
|
+
bounds.height = maxY - minY
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
fn getBounds:PathBounds () {
|
|
868
|
+
def result:PathBounds (unwrap bounds)
|
|
869
|
+
return result
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
fn getCommands:[PathCommand] () {
|
|
873
|
+
return commands
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
; Scale path commands to fit target dimensions
|
|
877
|
+
fn getScaledCommands:[PathCommand] (targetWidth:double targetHeight:double) {
|
|
878
|
+
def scaleX:double 1.0
|
|
879
|
+
def scaleY:double 1.0
|
|
880
|
+
|
|
881
|
+
if (bounds.width > 0.0) {
|
|
882
|
+
scaleX = targetWidth / bounds.width
|
|
883
|
+
}
|
|
884
|
+
if (bounds.height > 0.0) {
|
|
885
|
+
scaleY = targetHeight / bounds.height
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
def scaled:[PathCommand]
|
|
889
|
+
def i:int 0
|
|
890
|
+
while (i < (array_length commands)) {
|
|
891
|
+
def cmd:PathCommand (itemAt commands i)
|
|
892
|
+
def newCmd (new PathCommand())
|
|
893
|
+
newCmd.type = cmd.type
|
|
894
|
+
|
|
895
|
+
if ((cmd.type == "M") || (cmd.type == "L")) {
|
|
896
|
+
newCmd.x = (cmd.x - bounds.minX) * scaleX
|
|
897
|
+
newCmd.y = (cmd.y - bounds.minY) * scaleY
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (cmd.type == "C") {
|
|
901
|
+
newCmd.x1 = (cmd.x1 - bounds.minX) * scaleX
|
|
902
|
+
newCmd.y1 = (cmd.y1 - bounds.minY) * scaleY
|
|
903
|
+
newCmd.x2 = (cmd.x2 - bounds.minX) * scaleX
|
|
904
|
+
newCmd.y2 = (cmd.y2 - bounds.minY) * scaleY
|
|
905
|
+
newCmd.x = (cmd.x - bounds.minX) * scaleX
|
|
906
|
+
newCmd.y = (cmd.y - bounds.minY) * scaleY
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (cmd.type == "Q") {
|
|
910
|
+
newCmd.x1 = (cmd.x1 - bounds.minX) * scaleX
|
|
911
|
+
newCmd.y1 = (cmd.y1 - bounds.minY) * scaleY
|
|
912
|
+
newCmd.x = (cmd.x - bounds.minX) * scaleX
|
|
913
|
+
newCmd.y = (cmd.y - bounds.minY) * scaleY
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
push scaled newCmd
|
|
917
|
+
i = i + 1
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return scaled
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
; Flatten the path to a polygon outline: a flat [x0,y0,x1,y1,...] list of
|
|
924
|
+
; points, sampling C/Q curves into `steps` segments each and approximating A
|
|
925
|
+
; arcs as a straight line to the endpoint. M starts a new point, Z closes the
|
|
926
|
+
; ring (the fill closes implicitly, so Z adds no point). Used by a filled-path
|
|
927
|
+
; renderer (UIContext.fillPolygon) to rasterize <Path> elements.
|
|
928
|
+
; Flatten to closed contours, transformed by an affine matrix on the way.
|
|
929
|
+
;
|
|
930
|
+
; The six matrix arguments are passed as plain numbers rather than a
|
|
931
|
+
; Matrix2D so that this file stays free of dependencies — it is imported by
|
|
932
|
+
; the PDF renderer, the WASM UI and the game-engine tests alike. They are in
|
|
933
|
+
; SVG matrix(a b c d e f) order:
|
|
934
|
+
;
|
|
935
|
+
; x' = a*x + c*y + e
|
|
936
|
+
; y' = b*x + d*y + f
|
|
937
|
+
;
|
|
938
|
+
; `steps` is the number of line segments each curve is split into. Doing the
|
|
939
|
+
; transform HERE rather than afterwards matters: the subdivision then
|
|
940
|
+
; happens at the size the shape is actually drawn, so a path scaled up does
|
|
941
|
+
; not show the facets of a curve flattened at its original scale.
|
|
942
|
+
; A parser wrapped around commands that were produced somewhere else — by
|
|
943
|
+
; VectorShapes, by PathBuilder, or by the SVG importer, which resolves a
|
|
944
|
+
; whole document into per-shape command lists. Everything below the parse
|
|
945
|
+
; step (bounds, flattening, ring extraction) then works on them unchanged,
|
|
946
|
+
; so a renderer has one way to turn commands into geometry rather than one
|
|
947
|
+
; per producer.
|
|
948
|
+
sfn fromCommands:SVGPathParser (cmds:[PathCommand]) {
|
|
949
|
+
def p (new SVGPathParser())
|
|
950
|
+
p.commands = cmds
|
|
951
|
+
p.calculateBounds()
|
|
952
|
+
return p
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
; --- how finely to cut ONE curve ------------------------------------------
|
|
956
|
+
;
|
|
957
|
+
; `steps` is the caller's ceiling, chosen from the size of the thing being
|
|
958
|
+
; drawn. Inside a path that size says nothing about the curves: a heading
|
|
959
|
+
; 2,855 pixels wide cut every curve of every glyph in it 47 ways in order
|
|
960
|
+
; to draw those glyphs two pixels tall, and one frame of a Figma board
|
|
961
|
+
; carried 2.5 million points because of it.
|
|
962
|
+
;
|
|
963
|
+
; So a curve is cut by ITS OWN length — the control polygon, measured
|
|
964
|
+
; through the transform, at about a point every two device pixels. Never
|
|
965
|
+
; finer than the caller asked for, so nothing gets slower or heavier than
|
|
966
|
+
; it was; never fewer than two, so a curve is never a straight line.
|
|
967
|
+
; ...and when there is NO transform, there is no device length to cut by.
|
|
968
|
+
; `flattenRingsPlain` is how a shape catalogue flattens: the path is
|
|
969
|
+
; authored in a unit box, 0 to 1, and scaled to the slide afterwards. A
|
|
970
|
+
; curve of it is half a unit long, so the rule above cuts it in two and
|
|
971
|
+
; a smiley face's head came back as an octagon — nine preset shapes, all
|
|
972
|
+
; of which still returned points, so nothing counted them as broken.
|
|
973
|
+
; There the caller's number is the count it has always been.
|
|
974
|
+
def plain:boolean false
|
|
975
|
+
|
|
976
|
+
fn stepsFor:int (cap:int len:double) {
|
|
977
|
+
if plain {
|
|
978
|
+
return cap
|
|
979
|
+
}
|
|
980
|
+
return (SVGPathParser.curveSteps(cap len))
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
sfn curveSteps:int (cap:int len:double) {
|
|
984
|
+
def want:int (to_int ((len / 2.0) + 0.999))
|
|
985
|
+
if (want < 2) {
|
|
986
|
+
want = 2
|
|
987
|
+
}
|
|
988
|
+
if (want > cap) {
|
|
989
|
+
want = cap
|
|
990
|
+
}
|
|
991
|
+
return want
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
; The distance between two USER-space points once the transform has put
|
|
995
|
+
; them on the device.
|
|
996
|
+
sfn devLen:double (x0:double y0:double x1:double y1:double ma:double mb:double mc:double md:double) {
|
|
997
|
+
def dx:double (x1 - x0)
|
|
998
|
+
def dy:double (y1 - y0)
|
|
999
|
+
def ax:double ((ma * dx) + (mc * dy))
|
|
1000
|
+
def ay:double ((mb * dx) + (md * dy))
|
|
1001
|
+
return (sqrt ((ax * ax) + (ay * ay)))
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
fn flattenRings:[PathRing] (steps:int ma:double mb:double mc:double md:double me:double mf:double) {
|
|
1005
|
+
def rings:[PathRing]
|
|
1006
|
+
def current (new PathRing())
|
|
1007
|
+
def started:boolean false
|
|
1008
|
+
|
|
1009
|
+
; Pen position in USER units — the curve maths runs before the
|
|
1010
|
+
; transform, so control points and the pen have to agree.
|
|
1011
|
+
def cx:double 0.0
|
|
1012
|
+
def cy:double 0.0
|
|
1013
|
+
def sx:double 0.0
|
|
1014
|
+
def sy:double 0.0
|
|
1015
|
+
|
|
1016
|
+
def n:int (array_length commands)
|
|
1017
|
+
def k:int 0
|
|
1018
|
+
while (k < n) {
|
|
1019
|
+
def cmd:PathCommand (itemAt commands k)
|
|
1020
|
+
|
|
1021
|
+
if (cmd.type == "M") {
|
|
1022
|
+
; A new subpath closes whatever was open.
|
|
1023
|
+
if started {
|
|
1024
|
+
if ((current.pointCount()) >= 2) {
|
|
1025
|
+
push rings current
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
current = (new PathRing())
|
|
1029
|
+
started = true
|
|
1030
|
+
cx = cmd.x
|
|
1031
|
+
cy = cmd.y
|
|
1032
|
+
sx = cmd.x
|
|
1033
|
+
sy = cmd.y
|
|
1034
|
+
push current.pts ((ma * cx) + ((mc * cy) + me))
|
|
1035
|
+
push current.pts ((mb * cx) + ((md * cy) + mf))
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (cmd.type == "L") {
|
|
1039
|
+
cx = cmd.x
|
|
1040
|
+
cy = cmd.y
|
|
1041
|
+
push current.pts ((ma * cx) + ((mc * cy) + me))
|
|
1042
|
+
push current.pts ((mb * cx) + ((md * cy) + mf))
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (cmd.type == "C") {
|
|
1046
|
+
def d1:double (SVGPathParser.devLen(cx cy cmd.x1 cmd.y1 ma mb mc md))
|
|
1047
|
+
def d2:double (SVGPathParser.devLen(cmd.x1 cmd.y1 cmd.x2 cmd.y2 ma mb mc md))
|
|
1048
|
+
def d3:double (SVGPathParser.devLen(cmd.x2 cmd.y2 cmd.x cmd.y ma mb mc md))
|
|
1049
|
+
def cs:int (this.stepsFor(steps ((d1 + d2) + d3)))
|
|
1050
|
+
def s:int 1
|
|
1051
|
+
while (s <= cs) {
|
|
1052
|
+
def tt:double ((to_double s) / (to_double cs))
|
|
1053
|
+
def u:double (1.0 - tt)
|
|
1054
|
+
def b0:double ((u * u) * u)
|
|
1055
|
+
def b1:double (((3.0 * u) * u) * tt)
|
|
1056
|
+
def b2:double (((3.0 * u) * tt) * tt)
|
|
1057
|
+
def b3:double ((tt * tt) * tt)
|
|
1058
|
+
def px:double ((((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x2)) + (b3 * cmd.x))
|
|
1059
|
+
def py:double ((((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y2)) + (b3 * cmd.y))
|
|
1060
|
+
push current.pts ((ma * px) + ((mc * py) + me))
|
|
1061
|
+
push current.pts ((mb * px) + ((md * py) + mf))
|
|
1062
|
+
s = (s + 1)
|
|
1063
|
+
}
|
|
1064
|
+
cx = cmd.x
|
|
1065
|
+
cy = cmd.y
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
if (cmd.type == "Q") {
|
|
1069
|
+
def q1:double (SVGPathParser.devLen(cx cy cmd.x1 cmd.y1 ma mb mc md))
|
|
1070
|
+
def q2:double (SVGPathParser.devLen(cmd.x1 cmd.y1 cmd.x cmd.y ma mb mc md))
|
|
1071
|
+
def qs:int (this.stepsFor(steps (q1 + q2)))
|
|
1072
|
+
def s:int 1
|
|
1073
|
+
while (s <= qs) {
|
|
1074
|
+
def tt:double ((to_double s) / (to_double qs))
|
|
1075
|
+
def u:double (1.0 - tt)
|
|
1076
|
+
def b0:double (u * u)
|
|
1077
|
+
def b1:double ((2.0 * u) * tt)
|
|
1078
|
+
def b2:double (tt * tt)
|
|
1079
|
+
def px:double (((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x))
|
|
1080
|
+
def py:double (((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y))
|
|
1081
|
+
push current.pts ((ma * px) + ((mc * py) + me))
|
|
1082
|
+
push current.pts ((mb * px) + ((md * py) + mf))
|
|
1083
|
+
s = (s + 1)
|
|
1084
|
+
}
|
|
1085
|
+
cx = cmd.x
|
|
1086
|
+
cy = cmd.y
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (cmd.type == "Z") {
|
|
1090
|
+
; The fill closes the ring itself, so Z adds no point — it ends
|
|
1091
|
+
; the ring and returns the pen to the subpath start. The ring
|
|
1092
|
+
; remembers it was closed, for the stroke.
|
|
1093
|
+
if started {
|
|
1094
|
+
current.closed = true
|
|
1095
|
+
if ((current.pointCount()) >= 2) {
|
|
1096
|
+
push rings current
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
current = (new PathRing())
|
|
1100
|
+
started = false
|
|
1101
|
+
cx = sx
|
|
1102
|
+
cy = sy
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
k = (k + 1)
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
; An unclosed final subpath still fills — SVG closes it implicitly for
|
|
1109
|
+
; the purposes of filling.
|
|
1110
|
+
if started {
|
|
1111
|
+
if ((current.pointCount()) >= 2) {
|
|
1112
|
+
push rings current
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
return rings
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
; Flatten with no transform.
|
|
1120
|
+
fn flattenRingsPlain:[PathRing] (steps:int) {
|
|
1121
|
+
plain = true
|
|
1122
|
+
def rings:[PathRing] (this.flattenRings(steps 1.0 0.0 0.0 1.0 0.0 0.0))
|
|
1123
|
+
plain = false
|
|
1124
|
+
return rings
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
; Older flattening that returns every point of every subpath in ONE list.
|
|
1128
|
+
; Kept because existing callers depend on the signature, but it cannot
|
|
1129
|
+
; express a shape with more than one contour: use flattenRings for anything
|
|
1130
|
+
; that will be filled.
|
|
1131
|
+
fn flatten:[double] (steps:int) {
|
|
1132
|
+
def pts:[double]
|
|
1133
|
+
def cx:double 0.0
|
|
1134
|
+
def cy:double 0.0
|
|
1135
|
+
def n:int (array_length commands)
|
|
1136
|
+
def i:int 0
|
|
1137
|
+
while (i < n) {
|
|
1138
|
+
def cmd:PathCommand (itemAt commands i)
|
|
1139
|
+
if (cmd.type == "M") {
|
|
1140
|
+
cx = cmd.x
|
|
1141
|
+
cy = cmd.y
|
|
1142
|
+
push pts cx
|
|
1143
|
+
push pts cy
|
|
1144
|
+
}
|
|
1145
|
+
if (cmd.type == "L") {
|
|
1146
|
+
cx = cmd.x
|
|
1147
|
+
cy = cmd.y
|
|
1148
|
+
push pts cx
|
|
1149
|
+
push pts cy
|
|
1150
|
+
}
|
|
1151
|
+
if (cmd.type == "C") {
|
|
1152
|
+
def s:int 1
|
|
1153
|
+
while (s <= steps) {
|
|
1154
|
+
def tt:double ((to_double s) / (to_double steps))
|
|
1155
|
+
def u:double (1.0 - tt)
|
|
1156
|
+
; cubic Bezier: u^3 P0 + 3u^2 t C1 + 3u t^2 C2 + t^3 P1
|
|
1157
|
+
def b0:double (((u * u) * u))
|
|
1158
|
+
def b1:double ((((3.0 * u) * u) * tt))
|
|
1159
|
+
def b2:double ((((3.0 * u) * tt) * tt))
|
|
1160
|
+
def b3:double (((tt * tt) * tt))
|
|
1161
|
+
def px:double ((((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x2)) + (b3 * cmd.x))
|
|
1162
|
+
def py:double ((((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y2)) + (b3 * cmd.y))
|
|
1163
|
+
push pts px
|
|
1164
|
+
push pts py
|
|
1165
|
+
s = (s + 1)
|
|
1166
|
+
}
|
|
1167
|
+
cx = cmd.x
|
|
1168
|
+
cy = cmd.y
|
|
1169
|
+
}
|
|
1170
|
+
if (cmd.type == "Q") {
|
|
1171
|
+
def s:int 1
|
|
1172
|
+
while (s <= steps) {
|
|
1173
|
+
def tt:double ((to_double s) / (to_double steps))
|
|
1174
|
+
def u:double (1.0 - tt)
|
|
1175
|
+
; quadratic Bezier: u^2 P0 + 2u t C + t^2 P1
|
|
1176
|
+
def b0:double ((u * u))
|
|
1177
|
+
def b1:double (((2.0 * u) * tt))
|
|
1178
|
+
def b2:double ((tt * tt))
|
|
1179
|
+
def px:double (((b0 * cx) + (b1 * cmd.x1)) + (b2 * cmd.x))
|
|
1180
|
+
def py:double (((b0 * cy) + (b1 * cmd.y1)) + (b2 * cmd.y))
|
|
1181
|
+
push pts px
|
|
1182
|
+
push pts py
|
|
1183
|
+
s = (s + 1)
|
|
1184
|
+
}
|
|
1185
|
+
cx = cmd.x
|
|
1186
|
+
cy = cmd.y
|
|
1187
|
+
}
|
|
1188
|
+
if (cmd.type == "A") {
|
|
1189
|
+
; arc approximated as a straight segment to the endpoint.
|
|
1190
|
+
cx = cmd.x
|
|
1191
|
+
cy = cmd.y
|
|
1192
|
+
push pts cx
|
|
1193
|
+
push pts cy
|
|
1194
|
+
}
|
|
1195
|
+
i = (i + 1)
|
|
1196
|
+
}
|
|
1197
|
+
return pts
|
|
1198
|
+
}
|
|
1199
|
+
}
|