ranger-compiler 3.5.0 → 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 +1160 -19
- package/LICENSE +3 -1
- package/README.md +124 -29
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79924 -56922
- 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,1674 @@
|
|
|
1
|
+
# EVG Rendering System - Development Plan
|
|
2
|
+
|
|
3
|
+
## Implementation Status Summary
|
|
4
|
+
|
|
5
|
+
This section summarizes what is currently implemented in the PDF renderer vs. what's defined in the EVG SPEC (`lib/evg/SPEC.md`).
|
|
6
|
+
|
|
7
|
+
### ✅ IMPLEMENTED (Working in PDF Renderer)
|
|
8
|
+
|
|
9
|
+
| Feature | Status | Notes |
|
|
10
|
+
|---------|--------|-------|
|
|
11
|
+
| **Elements** | | |
|
|
12
|
+
| `<View>` / `<div>` | ✅ Full | Container element |
|
|
13
|
+
| `<Label>` / `<span>` | ✅ Full | Text element |
|
|
14
|
+
| `<Image>` / `<img>` | ✅ Full | JPEG with EXIF orientation, resize |
|
|
15
|
+
| `<Path>` / `<path>` | ✅ Full | SVG path rendering |
|
|
16
|
+
| `<Rect>` | ✅ Full | Rectangle element |
|
|
17
|
+
| `<Print>`, `<Section>`, `<Page>` | ✅ Full | Multi-page document structure |
|
|
18
|
+
| **Dimensions** | | |
|
|
19
|
+
| `width`, `height` | ✅ Full | px, %, em, hp, fill units |
|
|
20
|
+
| `minWidth`, `minHeight` | ✅ Full | |
|
|
21
|
+
| `maxWidth`, `maxHeight` | ✅ Full | |
|
|
22
|
+
| **Spacing** | | |
|
|
23
|
+
| `margin` (all sides) | ✅ Full | margin-top/right/bottom/left |
|
|
24
|
+
| `padding` (all sides) | ✅ Full | padding-top/right/bottom/left |
|
|
25
|
+
| **Position** | | |
|
|
26
|
+
| `left`, `top`, `right`, `bottom` | ✅ Full | Absolute positioning |
|
|
27
|
+
| `position: absolute/relative` | ✅ Full | |
|
|
28
|
+
| **Layout** | | |
|
|
29
|
+
| `display: flex/block` | ✅ Full | |
|
|
30
|
+
| `flexDirection` | ✅ Full | row, column |
|
|
31
|
+
| `justifyContent` | ✅ Full | flex-start, center, flex-end, space-between |
|
|
32
|
+
| `alignItems` | ✅ Full | flex-start, center, flex-end |
|
|
33
|
+
| `gap` | ✅ Full | Flexbox gap |
|
|
34
|
+
| `align` | ✅ Full | Horizontal alignment |
|
|
35
|
+
| `verticalAlign` | ✅ Full | Vertical alignment |
|
|
36
|
+
| `overflow: page-break` | ✅ Full | Multi-page content flow |
|
|
37
|
+
| **Visual** | | |
|
|
38
|
+
| `backgroundColor` | ✅ Full | Solid colors |
|
|
39
|
+
| `color` | ✅ Full | Text color |
|
|
40
|
+
| `borderWidth` | ✅ Full | All sides |
|
|
41
|
+
| `borderColor` | ✅ Full | |
|
|
42
|
+
| `borderRadius` | ✅ Full | Rounded corners |
|
|
43
|
+
| `clipPath` | ✅ Full | `circle`, `ellipse` clipping |
|
|
44
|
+
| **Typography** | | |
|
|
45
|
+
| `fontSize` | ✅ Full | px, em units |
|
|
46
|
+
| `fontFamily` | ✅ Full | TrueType fonts with Unicode support |
|
|
47
|
+
| `fontWeight` | ✅ Partial | Bold via font name (e.g., "Noto Sans Bold") |
|
|
48
|
+
| `textAlign` | ✅ Full | left, center, right |
|
|
49
|
+
| `lineHeight` | ✅ Full | Line spacing multiplier |
|
|
50
|
+
| **Images** | | |
|
|
51
|
+
| `src` / `imageUrl` | ✅ Full | Local file paths |
|
|
52
|
+
| EXIF Orientation | ✅ Full | Auto-rotate based on camera orientation |
|
|
53
|
+
| Image Resize | ✅ Full | `maxImageSize`, `imageQuality` on Print |
|
|
54
|
+
| **SVG Paths** | | |
|
|
55
|
+
| `d` / `svgPath` | ✅ Full | M, L, C, Q, Z commands |
|
|
56
|
+
| `viewBox` | ✅ Full | SVG viewBox scaling |
|
|
57
|
+
| `fill` / `fillColor` | ✅ Full | |
|
|
58
|
+
| `stroke` / `strokeColor` | ✅ Partial | |
|
|
59
|
+
|
|
60
|
+
### ⚠️ PARSED BUT NOT RENDERED (In EVGElement but not used by PDF renderer)
|
|
61
|
+
|
|
62
|
+
| Feature | Status | Notes |
|
|
63
|
+
|---------|--------|-------|
|
|
64
|
+
| `opacity` | ⚠️ Parsed | Parsed in JSXToEVG.rgr, **NOT rendered in PDF** |
|
|
65
|
+
| `rotate` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
66
|
+
| `scale` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
67
|
+
| `shadowRadius` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
68
|
+
| `shadowColor` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
69
|
+
| `shadowOffsetX` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
70
|
+
| `shadowOffsetY` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
71
|
+
| `overflow` | ⚠️ Defined | Defined in EVGElement.rgr, **NOT parsed or rendered** |
|
|
72
|
+
|
|
73
|
+
### ❌ NOT IMPLEMENTED (In SPEC but missing from implementation)
|
|
74
|
+
|
|
75
|
+
| Feature | Priority | Notes |
|
|
76
|
+
|---------|----------|-------|
|
|
77
|
+
| **Transforms** | | |
|
|
78
|
+
| `rotate` | HIGH | Rotation in degrees - needs PDF transformation matrix |
|
|
79
|
+
| `scale` | HIGH | Scale factor - needs PDF transformation matrix |
|
|
80
|
+
| `transform` | MEDIUM | CSS-style transform string |
|
|
81
|
+
| **Gradients** | | |
|
|
82
|
+
| `linear-gradient` | HIGH | PDF Type 2 (Axial) shading pattern |
|
|
83
|
+
| `radial-gradient` | MEDIUM | PDF Type 3 (Radial) shading pattern |
|
|
84
|
+
| **Shadows** | | |
|
|
85
|
+
| `shadow-radius` | MEDIUM | Box shadow blur - complex in PDF |
|
|
86
|
+
| `shadow-color` | MEDIUM | Shadow color |
|
|
87
|
+
| `shadow-offset-x/y` | MEDIUM | Shadow position offset |
|
|
88
|
+
| **Visual** | | |
|
|
89
|
+
| `opacity` | HIGH | PDF ExtGState transparency |
|
|
90
|
+
| `overflow: hidden` | HIGH | Clip children to View bounds - needs PDF clipping rect |
|
|
91
|
+
| `overflow: scroll` | LOW | Not applicable to PDF |
|
|
92
|
+
| **Image Features** | | |
|
|
93
|
+
| `viewBox` on Image | HIGH | Crop/zoom into image region (NEW) |
|
|
94
|
+
| `backgroundImage` | HIGH | View with background image (NEW) |
|
|
95
|
+
| `backgroundViewBox` | MEDIUM | Crop background image (NEW) |
|
|
96
|
+
| `backgroundSize` | MEDIUM | cover, contain, stretch (NEW) |
|
|
97
|
+
| **New Elements** | | |
|
|
98
|
+
| `<Layer>` | HIGH | Stacking layer for overlays (NEW) |
|
|
99
|
+
| **New Renderers** | | |
|
|
100
|
+
| HTML Renderer | **HIGHEST** | Unit testing, fast preview, development iteration |
|
|
101
|
+
| **Interactive** | | |
|
|
102
|
+
| `<input>` | LOW | For web renderer only |
|
|
103
|
+
| `<textarea>` | LOW | For web renderer only |
|
|
104
|
+
|
|
105
|
+
### Implementation Complexity
|
|
106
|
+
|
|
107
|
+
| Feature | PDF Implementation Complexity | Notes |
|
|
108
|
+
|---------|-------------------------------|-------|
|
|
109
|
+
| `opacity` | Low | ExtGState with `ca` and `CA` operators |
|
|
110
|
+
| `rotate` | Medium | PDF `cm` transformation matrix |
|
|
111
|
+
| `scale` | Medium | PDF `cm` transformation matrix |
|
|
112
|
+
| `overflow: hidden` | Low | PDF `re ... W n` clipping rectangle |
|
|
113
|
+
| `linear-gradient` | Medium | PDF Shading Pattern Type 2 |
|
|
114
|
+
| `radial-gradient` | Medium-High | PDF Shading Pattern Type 3 |
|
|
115
|
+
| `shadow` | High | No native PDF shadow - must fake with blurred shapes |
|
|
116
|
+
| `viewBox` on Image | Medium | Calculate source rect, use PDF image matrix |
|
|
117
|
+
| `backgroundImage` | Medium | Draw image before content, use clipping |
|
|
118
|
+
| `Layer` | Low | Just render order + optional blend mode |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Overview
|
|
123
|
+
|
|
124
|
+
This document outlines the plan for enhancing the EVG (Element Visual Graph) rendering system with:
|
|
125
|
+
1. **HTML Renderer** - Fast HTML/CSS output for unit testing and live preview (HIGH PRIORITY)
|
|
126
|
+
2. **Web-based Visual Debugger** - Preview and debug layouts in browser before PDF/JPEG export
|
|
127
|
+
3. **New Visual Features** - Gradients, layers, enhanced image handling
|
|
128
|
+
4. **Metadata System** - Image EXIF data integration, GPS coordinates
|
|
129
|
+
5. **Map Components** - GPS-based image positioning
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Architecture
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
137
|
+
│ TSX/JSX Source │
|
|
138
|
+
│ <Print><Page><View>...<Image src="photo.jpg"/>...</View></Page> │
|
|
139
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
140
|
+
│
|
|
141
|
+
▼
|
|
142
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
143
|
+
│ Component Engine │
|
|
144
|
+
│ (gallery/pdf_writer/src/jsx/) │
|
|
145
|
+
│ Parses TSX → EVG Element Tree with metadata │
|
|
146
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
147
|
+
│
|
|
148
|
+
▼
|
|
149
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
150
|
+
│ EVG Element Tree │
|
|
151
|
+
│ (lib/evg/EVGElement.rgr) │
|
|
152
|
+
│ - Auto-generated IDs / User-defined IDs │
|
|
153
|
+
│ - Image metadata (EXIF, GPS) attached to elements │
|
|
154
|
+
│ - Gradient definitions, Layer stacking │
|
|
155
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
156
|
+
│
|
|
157
|
+
┌─────────────┼─────────────┐
|
|
158
|
+
▼ ▼ ▼
|
|
159
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
160
|
+
│ Web Renderer │ │ PDF Renderer │ │ JPEG Renderer│
|
|
161
|
+
│ (HTML5) │ │ (existing) │ │ (existing) │
|
|
162
|
+
│ Debug UI │ │ │ │ │
|
|
163
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Phase 0: HTML Renderer (HIGH PRIORITY)
|
|
169
|
+
|
|
170
|
+
### 0.1 Why HTML First?
|
|
171
|
+
|
|
172
|
+
The HTML renderer should be implemented **before** enhancing the PDF renderer because:
|
|
173
|
+
|
|
174
|
+
1. **Unit Testing** - HTML output can be parsed and validated programmatically
|
|
175
|
+
2. **Fast Preview** - No PDF generation overhead, instant browser preview
|
|
176
|
+
3. **Debugging** - Browser DevTools for inspecting layout issues
|
|
177
|
+
4. **Reference Implementation** - Verify layout algorithm correctness before PDF
|
|
178
|
+
5. **Development Speed** - Iterate quickly on new features
|
|
179
|
+
|
|
180
|
+
### 0.2 HTML/CSS vs SVG vs Canvas
|
|
181
|
+
|
|
182
|
+
| Approach | Pros | Cons | Best For |
|
|
183
|
+
|----------|------|------|----------|
|
|
184
|
+
| **HTML/CSS (DIV)** | Native flexbox, easy debugging, testable | Font metrics differ from PDF | Unit tests, preview |
|
|
185
|
+
| **SVG** | Vector, precise positioning, clipPath support | No text wrapping, complex layout | Icons, paths |
|
|
186
|
+
| **Canvas** | Pixel-perfect, closest to PDF | Not inspectable, no DOM | Final preview |
|
|
187
|
+
|
|
188
|
+
**Recommendation:** Start with **HTML/CSS** for testing, add **Canvas** later for pixel-accurate preview.
|
|
189
|
+
|
|
190
|
+
### 0.3 Feature Comparison: HTML vs PDF
|
|
191
|
+
|
|
192
|
+
| Feature | HTML/CSS Support | Notes |
|
|
193
|
+
|---------|------------------|-------|
|
|
194
|
+
| **Layout** | | |
|
|
195
|
+
| Flexbox | ✅ Native | `display: flex` |
|
|
196
|
+
| Absolute positioning | ✅ Native | `position: absolute` |
|
|
197
|
+
| Margin/Padding | ✅ Native | CSS box model |
|
|
198
|
+
| `overflow: hidden` | ✅ Native | CSS `overflow: hidden` |
|
|
199
|
+
| `overflow: page-break` | ✅ Supported | Use layout data to split into page divs |
|
|
200
|
+
| **Text & Fonts** | | |
|
|
201
|
+
| Font metrics | ✅ Supported | Use same TTF parser data as PDF renderer |
|
|
202
|
+
| Text measurement | ✅ Supported | Pre-computed from TTF or Canvas API |
|
|
203
|
+
| TrueType fonts | ✅ Supported | `@font-face` with same TTF files |
|
|
204
|
+
| **Visual** | | |
|
|
205
|
+
| `backgroundColor` | ✅ Native | CSS `background-color` |
|
|
206
|
+
| `borderRadius` | ✅ Native | CSS `border-radius` |
|
|
207
|
+
| `opacity` | ✅ Native | CSS `opacity` |
|
|
208
|
+
| `linear-gradient` | ✅ Native | CSS `linear-gradient()` |
|
|
209
|
+
| `radial-gradient` | ✅ Native | CSS `radial-gradient()` |
|
|
210
|
+
| `shadow` | ✅ Native | CSS `box-shadow` |
|
|
211
|
+
| CMYK colors | ✅ Converted | CMYK→RGB math conversion |
|
|
212
|
+
| **Transforms** | | |
|
|
213
|
+
| `rotate` | ✅ Native | CSS `transform: rotate()` |
|
|
214
|
+
| `scale` | ✅ Native | CSS `transform: scale()` |
|
|
215
|
+
| **Images** | | |
|
|
216
|
+
| `<Image>` | ✅ Native | `<img>` tag |
|
|
217
|
+
| `viewBox` (crop) | ✅ CSS | `object-fit` + `object-position` |
|
|
218
|
+
| `backgroundImage` | ✅ Native | CSS `background-image` |
|
|
219
|
+
| EXIF orientation | ⚠️ Browser | Modern browsers auto-rotate |
|
|
220
|
+
| **Paths** | | |
|
|
221
|
+
| SVG `<Path>` | ✅ Inline SVG | Embed `<svg>` element |
|
|
222
|
+
| `clipPath` | ✅ CSS/SVG | `clip-path: circle()` or SVG `<clipPath>` |
|
|
223
|
+
| Custom SVG paths | ✅ SVG | Inline SVG with `<clipPath>` element |
|
|
224
|
+
| **Multi-page** | | |
|
|
225
|
+
| Multiple pages | ✅ Supported | Each page as `<div class="evg-page">` |
|
|
226
|
+
| Page breaks | ✅ Supported | Layout engine provides page assignments |
|
|
227
|
+
|
|
228
|
+
### 0.4 Font Rendering: CSS vs PDF Attributes
|
|
229
|
+
|
|
230
|
+
To ensure identical text rendering between HTML and PDF, we must understand which attributes affect text measurement and rendering in each system.
|
|
231
|
+
|
|
232
|
+
#### 0.4.1 Text Measurement Attributes Comparison
|
|
233
|
+
|
|
234
|
+
| Attribute | CSS Property | PDF Operator/Property | Affects Measurement? | Notes |
|
|
235
|
+
|-----------|--------------|----------------------|---------------------|-------|
|
|
236
|
+
| **Font Selection** | | | | |
|
|
237
|
+
| Font family | `font-family` | `/FontName Tf` | ✅ Yes | Must use same TTF file |
|
|
238
|
+
| Font size | `font-size` | `fontSize Tf` | ✅ Yes | Direct scaling factor |
|
|
239
|
+
| Font weight | `font-weight` | Different font file | ✅ Yes | Bold = different glyphs |
|
|
240
|
+
| Font style | `font-style` | Different font file | ✅ Yes | Italic = different glyphs |
|
|
241
|
+
| **Spacing** | | | | |
|
|
242
|
+
| Letter spacing | `letter-spacing` | `Tc` (char spacing) | ✅ Yes | Added to each glyph advance |
|
|
243
|
+
| Word spacing | `word-spacing` | `Tw` (word spacing) | ✅ Yes | Added to space character |
|
|
244
|
+
| Line height | `line-height` | `TL` (leading) | ✅ Yes | Vertical spacing between lines |
|
|
245
|
+
| **Rendering** | | | | |
|
|
246
|
+
| Text color | `color` | `rg` / `RG` (fill) | ❌ No | Visual only |
|
|
247
|
+
| Text shadow | `text-shadow` | N/A (simulate) | ❌ No | Visual only |
|
|
248
|
+
| Text decoration | `text-decoration` | Manual lines | ❌ No | Visual only |
|
|
249
|
+
| **Transform** | | | | |
|
|
250
|
+
| Text scale X | `transform: scaleX()` | `Tm` matrix | ⚠️ Complex | Horizontal scaling |
|
|
251
|
+
| Text rotation | `transform: rotate()` | `Tm` matrix | ⚠️ Complex | Rotation matrix |
|
|
252
|
+
| **Alignment** | | | | |
|
|
253
|
+
| Text align | `text-align` | Manual positioning | ❌ No | Layout, not measurement |
|
|
254
|
+
| Vertical align | `vertical-align` | Manual positioning | ❌ No | Layout, not measurement |
|
|
255
|
+
|
|
256
|
+
#### 0.4.2 TrueType Font Tables for Measurement
|
|
257
|
+
|
|
258
|
+
| Table | Purpose | Used For |
|
|
259
|
+
|-------|---------|----------|
|
|
260
|
+
| `head` | Font header | `unitsPerEm` - scaling factor |
|
|
261
|
+
| `hhea` | Horizontal header | `ascender`, `descender`, `lineGap` |
|
|
262
|
+
| `hmtx` | Horizontal metrics | Glyph advance widths |
|
|
263
|
+
| `cmap` | Character map | Unicode → Glyph ID mapping |
|
|
264
|
+
| `kern` | Kerning (optional) | Pair-wise spacing adjustments |
|
|
265
|
+
| `GPOS` | Glyph positioning (optional) | Advanced kerning, ligatures |
|
|
266
|
+
| `OS/2` | OS/2 metrics | `sTypoAscender`, `sTypoDescender`, `sxHeight` |
|
|
267
|
+
|
|
268
|
+
#### 0.4.3 Text Width Calculation Formula
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
textWidth = Σ (glyphAdvance[char] + letterSpacing) × (fontSize / unitsPerEm)
|
|
272
|
+
+ (wordCount - 1) × wordSpacing
|
|
273
|
+
|
|
274
|
+
Where:
|
|
275
|
+
- glyphAdvance[char] = from hmtx table
|
|
276
|
+
- letterSpacing = CSS letter-spacing / PDF Tc
|
|
277
|
+
- fontSize = CSS font-size / PDF Tf size
|
|
278
|
+
- unitsPerEm = from head table (typically 1000 or 2048)
|
|
279
|
+
- wordSpacing = CSS word-spacing / PDF Tw
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### 0.4.4 Line Height Calculation
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
lineHeight = (ascender - descender + lineGap) × (fontSize / unitsPerEm) × lineHeightMultiplier
|
|
286
|
+
|
|
287
|
+
CSS:
|
|
288
|
+
line-height: 1.5; /* multiplier */
|
|
289
|
+
line-height: 24px; /* absolute */
|
|
290
|
+
|
|
291
|
+
PDF:
|
|
292
|
+
TL operator sets leading (line spacing)
|
|
293
|
+
T* moves to next line using TL value
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### 0.4.5 Critical CSS Properties for Matching PDF
|
|
297
|
+
|
|
298
|
+
```css
|
|
299
|
+
/* Reset browser defaults that affect text measurement */
|
|
300
|
+
.evg-text {
|
|
301
|
+
/* Font selection - MUST match PDF font exactly */
|
|
302
|
+
font-family: 'Noto Sans', sans-serif;
|
|
303
|
+
font-size: 16px;
|
|
304
|
+
font-weight: 400; /* normal */
|
|
305
|
+
font-style: normal;
|
|
306
|
+
|
|
307
|
+
/* Spacing - affects measurement */
|
|
308
|
+
letter-spacing: 0; /* or specific value */
|
|
309
|
+
word-spacing: 0; /* or specific value */
|
|
310
|
+
line-height: 1.2; /* must match PDF TL calculation */
|
|
311
|
+
|
|
312
|
+
/* Text rendering hints */
|
|
313
|
+
text-rendering: geometricPrecision; /* prioritize accurate positioning */
|
|
314
|
+
-webkit-font-smoothing: antialiased;
|
|
315
|
+
|
|
316
|
+
/* Disable browser adjustments */
|
|
317
|
+
font-kerning: normal; /* or 'none' to disable */
|
|
318
|
+
font-feature-settings: normal;
|
|
319
|
+
text-size-adjust: 100%; /* prevent mobile scaling */
|
|
320
|
+
|
|
321
|
+
/* Whitespace handling */
|
|
322
|
+
white-space: pre-wrap; /* preserve spaces, wrap lines */
|
|
323
|
+
word-break: break-word;
|
|
324
|
+
|
|
325
|
+
/* Box model for text containers */
|
|
326
|
+
margin: 0;
|
|
327
|
+
padding: 0;
|
|
328
|
+
border: 0;
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### 0.4.6 PDF Text Operators Reference
|
|
333
|
+
|
|
334
|
+
| Operator | Syntax | Description | CSS Equivalent |
|
|
335
|
+
|----------|--------|-------------|----------------|
|
|
336
|
+
| `Tf` | `/FontName size Tf` | Select font and size | `font-family`, `font-size` |
|
|
337
|
+
| `Tc` | `spacing Tc` | Character spacing | `letter-spacing` |
|
|
338
|
+
| `Tw` | `spacing Tw` | Word spacing | `word-spacing` |
|
|
339
|
+
| `TL` | `leading TL` | Line leading (height) | `line-height` |
|
|
340
|
+
| `Tr` | `mode Tr` | Render mode (fill/stroke) | `color`, `-webkit-text-stroke` |
|
|
341
|
+
| `Ts` | `rise Ts` | Text rise (baseline shift) | `vertical-align` |
|
|
342
|
+
| `Tm` | `a b c d e f Tm` | Text matrix (transform) | `transform` |
|
|
343
|
+
| `Tj` | `(text) Tj` | Show text | N/A |
|
|
344
|
+
| `TJ` | `[(text) kern (text)] TJ` | Show text with positioning | `letter-spacing` per char |
|
|
345
|
+
| `T*` | `T*` | Move to next line | N/A (implicit in HTML) |
|
|
346
|
+
|
|
347
|
+
#### 0.4.7 Verification Test Cases
|
|
348
|
+
|
|
349
|
+
```ranger
|
|
350
|
+
class FontMetricsTest {
|
|
351
|
+
fn testSingleCharWidth:boolean () {
|
|
352
|
+
; Measure single character in both systems
|
|
353
|
+
def font "Noto Sans"
|
|
354
|
+
def size 16.0
|
|
355
|
+
def char "A"
|
|
356
|
+
|
|
357
|
+
def pdfWidth (pdfMeasureText char font size)
|
|
358
|
+
def htmlWidth (htmlMeasureText char font size)
|
|
359
|
+
|
|
360
|
+
; Allow 0.1px tolerance for rounding
|
|
361
|
+
assert (abs(pdfWidth - htmlWidth) < 0.1)
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
fn testMultilineHeight:boolean () {
|
|
366
|
+
; Verify line height matches
|
|
367
|
+
def text "Line 1\nLine 2\nLine 3"
|
|
368
|
+
def font "Noto Sans"
|
|
369
|
+
def size 16.0
|
|
370
|
+
def lineHeight 1.5
|
|
371
|
+
|
|
372
|
+
def pdfHeight (pdfMeasureTextHeight text font size lineHeight)
|
|
373
|
+
def htmlHeight (htmlMeasureTextHeight text font size lineHeight)
|
|
374
|
+
|
|
375
|
+
assert (abs(pdfHeight - htmlHeight) < 0.5)
|
|
376
|
+
return true
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
fn testKerning:boolean () {
|
|
380
|
+
; Test kerning pairs like "AV", "To", "WA"
|
|
381
|
+
def pairs ["AV" "To" "WA" "VA" "Ty"]
|
|
382
|
+
; ... verify kerning affects width consistently
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
fn testLetterSpacing:boolean () {
|
|
386
|
+
; Verify letter-spacing / Tc behaves identically
|
|
387
|
+
def text "Hello"
|
|
388
|
+
def spacing 2.0 ; 2px letter spacing
|
|
389
|
+
|
|
390
|
+
def pdfWidth (pdfMeasureText text font size spacing)
|
|
391
|
+
def htmlWidth (htmlMeasureText text font size spacing)
|
|
392
|
+
|
|
393
|
+
; spacing adds (strlen - 1) * spacing to width
|
|
394
|
+
assert (abs(pdfWidth - htmlWidth) < 0.1)
|
|
395
|
+
return true
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### 0.4.8 Known Differences to Handle
|
|
401
|
+
|
|
402
|
+
| Issue | Cause | Solution |
|
|
403
|
+
|-------|-------|----------|
|
|
404
|
+
| **Sub-pixel rendering** | Browsers round to device pixels | Use `text-rendering: geometricPrecision` |
|
|
405
|
+
| **Font hinting** | TTF hints adjust at small sizes | May cause ±1px differences |
|
|
406
|
+
| **Kerning tables** | GPOS vs kern table priority | Explicitly enable/disable kerning |
|
|
407
|
+
| **Default line-height** | Browsers use ~1.2, varies by font | Always set explicit `line-height` |
|
|
408
|
+
| **Whitespace collapse** | HTML collapses multiple spaces | Use `white-space: pre-wrap` |
|
|
409
|
+
| **Word breaking** | Different break algorithms | May cause line wrapping differences |
|
|
410
|
+
|
|
411
|
+
### 0.5 Font Metrics & Text Measurement
|
|
412
|
+
|
|
413
|
+
The HTML renderer can achieve **pixel-perfect text layout** by using the same font metrics system as the PDF renderer:
|
|
414
|
+
|
|
415
|
+
**Approach 1: TrueType Font Parsing (Server-side / Pre-computed)**
|
|
416
|
+
```
|
|
417
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
418
|
+
│ TrueType Font File (.ttf) │
|
|
419
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
420
|
+
│
|
|
421
|
+
▼
|
|
422
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
423
|
+
│ TrueTypeParser.rgr (existing) │
|
|
424
|
+
│ - Parse cmap table (character to glyph mapping) │
|
|
425
|
+
│ - Parse hmtx table (horizontal metrics - advance widths) │
|
|
426
|
+
│ - Parse head table (unitsPerEm) │
|
|
427
|
+
│ - Calculate: charWidth = (advanceWidth / unitsPerEm) * fontSize │
|
|
428
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
429
|
+
│
|
|
430
|
+
▼
|
|
431
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
432
|
+
│ Font Metrics Lookup Table │
|
|
433
|
+
│ { │
|
|
434
|
+
│ "Noto Sans": { │
|
|
435
|
+
│ "unitsPerEm": 1000, │
|
|
436
|
+
│ "ascender": 1069, │
|
|
437
|
+
│ "descender": -293, │
|
|
438
|
+
│ "glyphWidths": { "A": 633, "B": 653, "C": 698, ... } │
|
|
439
|
+
│ } │
|
|
440
|
+
│ } │
|
|
441
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
442
|
+
│
|
|
443
|
+
┌─────────────┴─────────────┐
|
|
444
|
+
▼ ▼
|
|
445
|
+
┌──────────────────┐ ┌──────────────────┐
|
|
446
|
+
│ PDF Renderer │ │ HTML Renderer │
|
|
447
|
+
│ (same metrics) │ │ (same metrics) │
|
|
448
|
+
└──────────────────┘ └──────────────────┘
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Approach 2: Canvas API (Browser runtime)**
|
|
452
|
+
```javascript
|
|
453
|
+
// Browser-side font measurement using Canvas API
|
|
454
|
+
function measureText(text, fontFamily, fontSize) {
|
|
455
|
+
const canvas = document.createElement('canvas');
|
|
456
|
+
const ctx = canvas.getContext('2d');
|
|
457
|
+
ctx.font = `${fontSize}px "${fontFamily}"`;
|
|
458
|
+
const metrics = ctx.measureText(text);
|
|
459
|
+
return {
|
|
460
|
+
width: metrics.width,
|
|
461
|
+
actualBoundingBoxAscent: metrics.actualBoundingBoxAscent,
|
|
462
|
+
actualBoundingBoxDescent: metrics.actualBoundingBoxDescent
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Recommended Strategy:**
|
|
468
|
+
1. **Pre-compute** font metrics from TTF files using existing `TrueTypeParser.rgr`
|
|
469
|
+
2. **Embed metrics** in HTML output as JSON or generate CSS with exact measurements
|
|
470
|
+
3. **Fallback** to Canvas API for dynamic/unknown fonts in browser
|
|
471
|
+
|
|
472
|
+
This ensures **identical text layout** between PDF and HTML renderers.
|
|
473
|
+
|
|
474
|
+
### 0.6 Page Breaks in HTML
|
|
475
|
+
|
|
476
|
+
Page breaks ARE possible in HTML because we control the layout engine:
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
480
|
+
│ EVG Layout Engine │
|
|
481
|
+
│ - Already calculates element positions │
|
|
482
|
+
│ - Already tracks page assignments (calculatedPage) │
|
|
483
|
+
│ - Knows exact page dimensions and margins │
|
|
484
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
485
|
+
│
|
|
486
|
+
▼
|
|
487
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
488
|
+
│ HTML Page Rendering │
|
|
489
|
+
│ │
|
|
490
|
+
│ Page 1: Page 2: │
|
|
491
|
+
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
|
492
|
+
│ │ <div class="page-1">│ │ <div class="page-2">│ │
|
|
493
|
+
│ │ Element A │ │ Element D │ │
|
|
494
|
+
│ │ Element B │ │ Element E │ │
|
|
495
|
+
│ │ Element C │ │ (continued...) │ │
|
|
496
|
+
│ │ (page ends here) │ │ │ │
|
|
497
|
+
│ └─────────────────────┘ └─────────────────────┘ │
|
|
498
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Implementation:**
|
|
502
|
+
```html
|
|
503
|
+
<div class="evg-document">
|
|
504
|
+
<!-- Page 1 -->
|
|
505
|
+
<div class="evg-page" style="width: 595px; height: 842px;">
|
|
506
|
+
<!-- Elements with calculatedPage == 0 -->
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
<!-- Page 2 -->
|
|
510
|
+
<div class="evg-page" style="width: 595px; height: 842px;">
|
|
511
|
+
<!-- Elements with calculatedPage == 1 -->
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### 0.7 CMYK Color Conversion
|
|
517
|
+
|
|
518
|
+
CMYK to RGB conversion is straightforward mathematics:
|
|
519
|
+
|
|
520
|
+
```ranger
|
|
521
|
+
class ColorConverter {
|
|
522
|
+
; CMYK to RGB (simple conversion)
|
|
523
|
+
sfn cmykToRgb:EVGColor (c:double m:double y:double k:double) {
|
|
524
|
+
; Formula: R = 255 × (1-C) × (1-K)
|
|
525
|
+
def r:int (to_int (255.0 * (1.0 - c) * (1.0 - k)))
|
|
526
|
+
def g:int (to_int (255.0 * (1.0 - m) * (1.0 - k)))
|
|
527
|
+
def b:int (to_int (255.0 * (1.0 - y) * (1.0 - k)))
|
|
528
|
+
return (EVGColor.create(r g b 255))
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
; RGB to CMYK
|
|
532
|
+
sfn rgbToCmyk:[double] (r:int g:int b:int) {
|
|
533
|
+
def rn:double ((to_double r) / 255.0)
|
|
534
|
+
def gn:double ((to_double g) / 255.0)
|
|
535
|
+
def bn:double ((to_double b) / 255.0)
|
|
536
|
+
|
|
537
|
+
def k:double (1.0 - (max3 rn gn bn))
|
|
538
|
+
if (k == 1.0) {
|
|
539
|
+
return [0.0 0.0 0.0 1.0] ; Pure black
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
def c:double ((1.0 - rn - k) / (1.0 - k))
|
|
543
|
+
def m:double ((1.0 - gn - k) / (1.0 - k))
|
|
544
|
+
def y:double ((1.0 - bn - k) / (1.0 - k))
|
|
545
|
+
|
|
546
|
+
return [c m y k]
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**For accurate CMYK preview:**
|
|
552
|
+
- Convert CMYK to RGB for browser display
|
|
553
|
+
- Show warning indicator for CMYK colors (since RGB gamut differs)
|
|
554
|
+
- Optional: Use ICC color profiles for more accurate conversion
|
|
555
|
+
|
|
556
|
+
### 0.8 What CAN'T Be Done in HTML?
|
|
557
|
+
|
|
558
|
+
| Feature | Issue | Solution |
|
|
559
|
+
|---------|-------|----------|
|
|
560
|
+
| **PDF-specific metadata** | Title, author, keywords | N/A for preview (PDF only) |
|
|
561
|
+
| **Embedded fonts** | PDF embeds font subsets | Use `@font-face` with TTF/WOFF |
|
|
562
|
+
| **Exact CMYK gamut** | Browsers use RGB | Convert + show indicator |
|
|
563
|
+
| **PDF encryption** | Security features | N/A for preview |
|
|
564
|
+
|
|
565
|
+
**Everything else CAN be done** - font metrics, page breaks, and color conversion are all solvable.
|
|
566
|
+
|
|
567
|
+
### 0.9 Implementation Plan
|
|
568
|
+
|
|
569
|
+
**Files to create:**
|
|
570
|
+
```
|
|
571
|
+
lib/evg/web/
|
|
572
|
+
├── EVGHTMLRenderer.rgr # Main HTML renderer
|
|
573
|
+
├── EVGCSSGenerator.rgr # CSS style generation
|
|
574
|
+
├── EVGHTMLDocument.rgr # HTML document wrapper
|
|
575
|
+
└── tests/
|
|
576
|
+
├── html_render_test.rgr # Unit tests
|
|
577
|
+
└── expected/ # Expected HTML output files
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**EVGHTMLRenderer Interface:**
|
|
581
|
+
```ranger
|
|
582
|
+
class EVGHTMLRenderer {
|
|
583
|
+
def indentLevel:int 0
|
|
584
|
+
def cssStyles:[string] ; Collected CSS rules
|
|
585
|
+
def usedFonts:[string] ; Fonts to include as web fonts
|
|
586
|
+
|
|
587
|
+
fn render:string (root:EVGElement)
|
|
588
|
+
fn renderElement:string (el:EVGElement)
|
|
589
|
+
fn renderView:string (el:EVGElement)
|
|
590
|
+
fn renderLabel:string (el:EVGElement)
|
|
591
|
+
fn renderImage:string (el:EVGElement)
|
|
592
|
+
fn renderPath:string (el:EVGElement)
|
|
593
|
+
|
|
594
|
+
fn generateCSS:string (el:EVGElement)
|
|
595
|
+
fn generateInlineStyle:string (el:EVGElement)
|
|
596
|
+
|
|
597
|
+
; Output options
|
|
598
|
+
def outputMode:string "inline" ; "inline" | "stylesheet" | "both"
|
|
599
|
+
def prettyPrint:boolean true
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### 0.10 HTML Output Example
|
|
604
|
+
|
|
605
|
+
**Input TSX:**
|
|
606
|
+
```tsx
|
|
607
|
+
<Page width="595px" height="842px" padding="40px">
|
|
608
|
+
<View backgroundColor="#f5f5f5" padding="20px" borderRadius="10px">
|
|
609
|
+
<Label fontSize="24px" color="#2c3e50">Hello World</Label>
|
|
610
|
+
<Image src="photo.jpg" width="200px" height="150px" />
|
|
611
|
+
</View>
|
|
612
|
+
</Page>
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Output HTML:**
|
|
616
|
+
```html
|
|
617
|
+
<!DOCTYPE html>
|
|
618
|
+
<html>
|
|
619
|
+
<head>
|
|
620
|
+
<meta charset="UTF-8">
|
|
621
|
+
<style>
|
|
622
|
+
@font-face {
|
|
623
|
+
font-family: 'Noto Sans';
|
|
624
|
+
src: url('./fonts/NotoSans-Regular.ttf');
|
|
625
|
+
}
|
|
626
|
+
.evg-page {
|
|
627
|
+
width: 595px;
|
|
628
|
+
height: 842px;
|
|
629
|
+
padding: 40px;
|
|
630
|
+
box-sizing: border-box;
|
|
631
|
+
background: white;
|
|
632
|
+
position: relative;
|
|
633
|
+
overflow: hidden;
|
|
634
|
+
}
|
|
635
|
+
.evg-view-1 {
|
|
636
|
+
background-color: #f5f5f5;
|
|
637
|
+
padding: 20px;
|
|
638
|
+
border-radius: 10px;
|
|
639
|
+
display: flex;
|
|
640
|
+
flex-direction: column;
|
|
641
|
+
}
|
|
642
|
+
.evg-label-1 {
|
|
643
|
+
font-size: 24px;
|
|
644
|
+
color: #2c3e50;
|
|
645
|
+
font-family: 'Noto Sans', sans-serif;
|
|
646
|
+
}
|
|
647
|
+
.evg-image-1 {
|
|
648
|
+
width: 200px;
|
|
649
|
+
height: 150px;
|
|
650
|
+
object-fit: cover;
|
|
651
|
+
}
|
|
652
|
+
</style>
|
|
653
|
+
</head>
|
|
654
|
+
<body>
|
|
655
|
+
<div class="evg-page">
|
|
656
|
+
<div class="evg-view-1">
|
|
657
|
+
<span class="evg-label-1">Hello World</span>
|
|
658
|
+
<img class="evg-image-1" src="photo.jpg" alt="">
|
|
659
|
+
</div>
|
|
660
|
+
</div>
|
|
661
|
+
</body>
|
|
662
|
+
</html>
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### 0.11 Unit Testing with HTML
|
|
666
|
+
|
|
667
|
+
**Test approach:**
|
|
668
|
+
```ranger
|
|
669
|
+
Import "EVGHTMLRenderer.rgr"
|
|
670
|
+
Import "JSXToEVG.rgr"
|
|
671
|
+
|
|
672
|
+
class HTMLRenderTest {
|
|
673
|
+
fn testSimpleView:boolean () {
|
|
674
|
+
def tsx "<View width=\"100px\" height=\"50px\" backgroundColor=\"#ff0000\"></View>"
|
|
675
|
+
def parser (new JSXToEVG())
|
|
676
|
+
def root:EVGElement (parser.parse(tsx))
|
|
677
|
+
|
|
678
|
+
def renderer (new EVGHTMLRenderer())
|
|
679
|
+
def html:string (renderer.render(root))
|
|
680
|
+
|
|
681
|
+
; Verify output contains expected CSS
|
|
682
|
+
assert (contains html "width: 100px")
|
|
683
|
+
assert (contains html "height: 50px")
|
|
684
|
+
assert (contains html "background-color: #ff0000")
|
|
685
|
+
|
|
686
|
+
return true
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
fn testFlexLayout:boolean () {
|
|
690
|
+
def tsx "<View display=\"flex\" justifyContent=\"center\" alignItems=\"center\">...</View>"
|
|
691
|
+
; ... verify flex CSS output
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
fn testGradient:boolean () {
|
|
695
|
+
def tsx "<View background=\"linear-gradient(45deg, red, blue)\">...</View>"
|
|
696
|
+
; ... verify gradient CSS output
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 0.12 Live Preview Server
|
|
702
|
+
|
|
703
|
+
```ranger
|
|
704
|
+
class EVGPreviewServer {
|
|
705
|
+
def port:int 8080
|
|
706
|
+
def watchPath:string ""
|
|
707
|
+
def renderer:EVGHTMLRenderer
|
|
708
|
+
|
|
709
|
+
fn start:void () {
|
|
710
|
+
; Start HTTP server
|
|
711
|
+
; Watch TSX files for changes
|
|
712
|
+
; WebSocket for hot reload
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
fn handleRequest:string (path:string) {
|
|
716
|
+
if (path == "/") {
|
|
717
|
+
return this.renderPreview()
|
|
718
|
+
}
|
|
719
|
+
if (startsWith path "/fonts/") {
|
|
720
|
+
return this.serveFont(path)
|
|
721
|
+
}
|
|
722
|
+
if (startsWith path "/images/") {
|
|
723
|
+
return this.serveImage(path)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**Usage:**
|
|
730
|
+
```bash
|
|
731
|
+
evg preview example.tsx --port=8080
|
|
732
|
+
# Opens browser at http://localhost:8080
|
|
733
|
+
# Auto-refreshes on file changes
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
### 0.13 Implementation Priority
|
|
737
|
+
|
|
738
|
+
1. **Phase 0.1:** Basic HTML structure (View, Label) - 1 day
|
|
739
|
+
2. **Phase 0.2:** CSS generation (colors, dimensions, spacing) - 1 day
|
|
740
|
+
3. **Phase 0.3:** Flexbox layout mapping - 1 day
|
|
741
|
+
4. **Phase 0.4:** Image rendering with viewBox - 1 day
|
|
742
|
+
5. **Phase 0.5:** SVG Path embedding - 1 day
|
|
743
|
+
6. **Phase 0.6:** Gradients and shadows - 1 day
|
|
744
|
+
7. **Phase 0.7:** Unit test framework - 1 day
|
|
745
|
+
8. **Phase 0.8:** Preview server with hot reload - 2 days
|
|
746
|
+
|
|
747
|
+
**Total: ~10 days**
|
|
748
|
+
|
|
749
|
+
---
|
|
750
|
+
|
|
751
|
+
## Phase 1: Web-based Visual Debugger
|
|
752
|
+
|
|
753
|
+
### 1.1 HTML5/Canvas Renderer
|
|
754
|
+
|
|
755
|
+
Create a new renderer that outputs the EVG tree as HTML/CSS or Canvas drawings for browser preview.
|
|
756
|
+
|
|
757
|
+
**Files to create:**
|
|
758
|
+
- `lib/evg/web/EVGWebRenderer.rgr` - Renders EVG to HTML/CSS
|
|
759
|
+
- `lib/evg/web/EVGCanvasRenderer.rgr` - Renders EVG to Canvas commands
|
|
760
|
+
- `lib/evg/web/evg_preview_server.rgr` - Simple HTTP server for preview
|
|
761
|
+
|
|
762
|
+
**Features:**
|
|
763
|
+
- Real-time preview of layout
|
|
764
|
+
- Element inspector (click to see properties)
|
|
765
|
+
- Highlight element boundaries and padding/margins
|
|
766
|
+
- Show calculated sizes vs. specified sizes
|
|
767
|
+
- Export to PDF/JPEG from preview
|
|
768
|
+
|
|
769
|
+
### 1.2 Debug UI Components
|
|
770
|
+
|
|
771
|
+
```
|
|
772
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
773
|
+
│ EVG Visual Debugger [Export ▼] │
|
|
774
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
775
|
+
│ ┌───────────────────────────────────┐ ┌───────────────────────────┐ │
|
|
776
|
+
│ │ │ │ Element Inspector │ │
|
|
777
|
+
│ │ │ ├───────────────────────────┤ │
|
|
778
|
+
│ │ [Page Preview] │ │ ID: header-title │ │
|
|
779
|
+
│ │ │ │ Type: Label │ │
|
|
780
|
+
│ │ │ │ Position: (40, 40) │ │
|
|
781
|
+
│ │ │ │ Size: 515 x 32 │ │
|
|
782
|
+
│ │ │ │ Font: Noto Sans 24px │ │
|
|
783
|
+
│ │ │ │ Color: #2c3e50 │ │
|
|
784
|
+
│ │ │ │ │ │
|
|
785
|
+
│ │ │ │ ─── Computed ─── │ │
|
|
786
|
+
│ │ │ │ Padding: 0 │ │
|
|
787
|
+
│ │ │ │ Margin: 0 0 20 0 │ │
|
|
788
|
+
│ └───────────────────────────────────┘ └───────────────────────────┘ │
|
|
789
|
+
│ Page: [1] [2] [3] [4] Zoom: [100%] │
|
|
790
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
---
|
|
794
|
+
|
|
795
|
+
## Phase 1.5: Incremental Rendering & Live Preview
|
|
796
|
+
|
|
797
|
+
### 1.5.1 Problem Statement
|
|
798
|
+
|
|
799
|
+
Current PDF rendering:
|
|
800
|
+
- Regenerates entire PDF on every change
|
|
801
|
+
- Slow for large documents (100+ pages)
|
|
802
|
+
- No live preview during development
|
|
803
|
+
- Must export and open externally to see results
|
|
804
|
+
|
|
805
|
+
Goal: **Sub-second preview updates** during development
|
|
806
|
+
|
|
807
|
+
### 1.5.2 Incremental PDF Rendering Architecture
|
|
808
|
+
|
|
809
|
+
```
|
|
810
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
811
|
+
│ TSX Source (File Watch) │
|
|
812
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
813
|
+
│
|
|
814
|
+
▼
|
|
815
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
816
|
+
│ Change Detection Engine │
|
|
817
|
+
│ - Parse TSX to AST │
|
|
818
|
+
│ - Diff against previous AST │
|
|
819
|
+
│ - Identify changed pages/elements │
|
|
820
|
+
│ - Track dependency graph (shared styles, images) │
|
|
821
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
822
|
+
│
|
|
823
|
+
┌─────────────┼─────────────┐
|
|
824
|
+
▼ ▼ ▼
|
|
825
|
+
No Changes Page Changed Global Change
|
|
826
|
+
│ │ │
|
|
827
|
+
│ ▼ ▼
|
|
828
|
+
│ Re-render Full Re-render
|
|
829
|
+
│ affected all pages
|
|
830
|
+
│ pages only
|
|
831
|
+
│ │ │
|
|
832
|
+
└─────────────┴─────────────┘
|
|
833
|
+
│
|
|
834
|
+
▼
|
|
835
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
836
|
+
│ PDF Update Strategy │
|
|
837
|
+
│ Option A: Page-level PDF replacement │
|
|
838
|
+
│ Option B: Render to PNG per page + composite │
|
|
839
|
+
│ Option C: Hybrid preview (web) + full export (PDF) │
|
|
840
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### 1.5.3 Change Detection Strategy
|
|
844
|
+
|
|
845
|
+
**Page-level hashing:**
|
|
846
|
+
```
|
|
847
|
+
Page 1: hash("content...") = "a1b2c3" → unchanged
|
|
848
|
+
Page 2: hash("content...") = "d4e5f6" → CHANGED (was "x7y8z9")
|
|
849
|
+
Page 3: hash("content...") = "g7h8i9" → unchanged
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**What triggers full re-render:**
|
|
853
|
+
- Shared style changes (affects all pages using that style)
|
|
854
|
+
- Font changes
|
|
855
|
+
- Global variable changes
|
|
856
|
+
- Page count changes (PDF cross-reference table must update)
|
|
857
|
+
|
|
858
|
+
**What triggers partial re-render:**
|
|
859
|
+
- Single page content change
|
|
860
|
+
- Image replacement (same dimensions)
|
|
861
|
+
- Text edits within a page
|
|
862
|
+
|
|
863
|
+
### 1.5.4 Live Preview Implementation Options
|
|
864
|
+
|
|
865
|
+
#### Option A: Native PDF Viewer Wrapper (macOS)
|
|
866
|
+
|
|
867
|
+
Wrap `evg` binary with a native macOS app that:
|
|
868
|
+
1. Watches TSX files for changes (FSEvents)
|
|
869
|
+
2. Calls `evg` to render changed pages to PNG
|
|
870
|
+
3. Displays PNGs in scrollable preview
|
|
871
|
+
4. Full PDF export on demand
|
|
872
|
+
|
|
873
|
+
**Pros:**
|
|
874
|
+
- Native performance
|
|
875
|
+
- True WYSIWYG (same renderer)
|
|
876
|
+
- No browser needed
|
|
877
|
+
|
|
878
|
+
**Cons:**
|
|
879
|
+
- macOS only initially
|
|
880
|
+
- Requires Swift/Objective-C wrapper
|
|
881
|
+
|
|
882
|
+
```
|
|
883
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
884
|
+
│ EVG Live Preview (macOS) [Export] │
|
|
885
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
886
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
887
|
+
│ │ │ │
|
|
888
|
+
│ │ ┌────────────────────────────────────────────────────┐ │ │
|
|
889
|
+
│ │ │ │ │ │
|
|
890
|
+
│ │ │ Page 1 (PNG preview) │ │ │
|
|
891
|
+
│ │ │ │ │ │
|
|
892
|
+
│ │ └────────────────────────────────────────────────────┘ │ │
|
|
893
|
+
│ │ │ │
|
|
894
|
+
│ │ ┌────────────────────────────────────────────────────┐ │ │
|
|
895
|
+
│ │ │ │ │ │
|
|
896
|
+
│ │ │ Page 2 (PNG preview) │ │ │
|
|
897
|
+
│ │ │ *** UPDATED *** │ │ │
|
|
898
|
+
│ │ └────────────────────────────────────────────────────┘ │ │
|
|
899
|
+
│ │ │ │
|
|
900
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
901
|
+
│ Page 2 of 10 | Last update: 0.3s | Watching: example.tsx │
|
|
902
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
**Implementation (Swift):**
|
|
906
|
+
```swift
|
|
907
|
+
// EVGPreviewApp.swift
|
|
908
|
+
class EVGPreviewController {
|
|
909
|
+
var pageCache: [Int: NSImage] = [:]
|
|
910
|
+
var pageHashes: [Int: String] = [:]
|
|
911
|
+
|
|
912
|
+
func onFileChanged(path: String) {
|
|
913
|
+
let changedPages = detectChangedPages(path)
|
|
914
|
+
for pageNum in changedPages {
|
|
915
|
+
// Render only changed page to PNG
|
|
916
|
+
let png = renderPageToPNG(pageNum)
|
|
917
|
+
pageCache[pageNum] = png
|
|
918
|
+
updatePreview(pageNum)
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
func renderPageToPNG(_ page: Int) -> NSImage {
|
|
923
|
+
// Call evg binary with --page=N --format=png
|
|
924
|
+
let task = Process()
|
|
925
|
+
task.executableURL = URL(fileURLWithPath: "/usr/local/bin/evg")
|
|
926
|
+
task.arguments = [sourcePath, "--page=\(page)", "--format=png", "-o", "-"]
|
|
927
|
+
// ... capture stdout as PNG data
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
#### Option B: Electron/Tauri Wrapper (Cross-platform)
|
|
933
|
+
|
|
934
|
+
**Pros:**
|
|
935
|
+
- Cross-platform (macOS, Windows, Linux)
|
|
936
|
+
- Web technologies for UI
|
|
937
|
+
- Hot-reload built-in
|
|
938
|
+
|
|
939
|
+
**Cons:**
|
|
940
|
+
- Larger bundle size (Electron)
|
|
941
|
+
- Tauri requires Rust knowledge
|
|
942
|
+
|
|
943
|
+
#### Option C: VS Code Extension
|
|
944
|
+
|
|
945
|
+
Integrate preview directly into VS Code:
|
|
946
|
+
|
|
947
|
+
**Pros:**
|
|
948
|
+
- No separate app needed
|
|
949
|
+
- Side-by-side with code
|
|
950
|
+
- Familiar environment
|
|
951
|
+
|
|
952
|
+
**Cons:**
|
|
953
|
+
- Limited to VS Code users
|
|
954
|
+
- WebView has some limitations
|
|
955
|
+
|
|
956
|
+
```
|
|
957
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
958
|
+
│ VS Code │
|
|
959
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
960
|
+
│ example.tsx │ EVG Preview │
|
|
961
|
+
│ ──────────────────────│─────────────────────────────────────────────│
|
|
962
|
+
│ <Print> │ ┌────────────────────────────────────────┐ │
|
|
963
|
+
│ <Page> │ │ │ │
|
|
964
|
+
│ <View> │ │ [Live Page Preview] │ │
|
|
965
|
+
│ <Label>Hello │ │ │ │
|
|
966
|
+
│ ... │ │ │ │
|
|
967
|
+
│ │ └────────────────────────────────────────┘ │
|
|
968
|
+
│ │ Page: [1] [2] [3] [Export PDF] │
|
|
969
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
#### Option D: Browser-based with WebSocket Hot Reload
|
|
973
|
+
|
|
974
|
+
**Pros:**
|
|
975
|
+
- No installation (just open browser)
|
|
976
|
+
- Works on any OS
|
|
977
|
+
- Easy to share previews remotely
|
|
978
|
+
|
|
979
|
+
**Cons:**
|
|
980
|
+
- Requires running server
|
|
981
|
+
- Slight rendering differences from PDF
|
|
982
|
+
|
|
983
|
+
### 1.5.5 Recommended Approach: Hybrid System
|
|
984
|
+
|
|
985
|
+
**Development workflow:**
|
|
986
|
+
1. **Primary: Web preview** (fast, cross-platform, hot-reload)
|
|
987
|
+
2. **Verification: PNG per-page** (pixel-accurate to PDF)
|
|
988
|
+
3. **Final: Full PDF export** (production output)
|
|
989
|
+
|
|
990
|
+
**Implementation phases:**
|
|
991
|
+
|
|
992
|
+
**Phase 1.5a: Single-page PNG render mode**
|
|
993
|
+
```bash
|
|
994
|
+
# New CLI option: render single page to PNG
|
|
995
|
+
evg example.tsx --page=2 --format=png -o page2.png
|
|
996
|
+
|
|
997
|
+
# Render all pages as PNGs to directory
|
|
998
|
+
evg example.tsx --format=png --pages-dir=./preview/
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
**Phase 1.5b: File watcher with incremental render**
|
|
1002
|
+
```bash
|
|
1003
|
+
# Watch mode with live preview server
|
|
1004
|
+
evg example.tsx --watch --preview-port=8080
|
|
1005
|
+
|
|
1006
|
+
# Opens browser at http://localhost:8080
|
|
1007
|
+
# WebSocket pushes updates on file change
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
**Phase 1.5c: Native preview app (optional)**
|
|
1011
|
+
```bash
|
|
1012
|
+
# macOS app that uses evg internally
|
|
1013
|
+
EVGPreview.app
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
### 1.5.6 PDF Incremental Update (Advanced)
|
|
1017
|
+
|
|
1018
|
+
PDF format allows incremental updates (appending changes without rewriting):
|
|
1019
|
+
|
|
1020
|
+
```
|
|
1021
|
+
Original PDF:
|
|
1022
|
+
┌─────────────────────────────────────────┐
|
|
1023
|
+
│ Header │
|
|
1024
|
+
│ Object 1 (Page 1 content) │
|
|
1025
|
+
│ Object 2 (Page 2 content) │
|
|
1026
|
+
│ Object 3 (Page 3 content) │
|
|
1027
|
+
│ XRef Table │
|
|
1028
|
+
│ Trailer │
|
|
1029
|
+
└─────────────────────────────────────────┘
|
|
1030
|
+
|
|
1031
|
+
After incremental update (Page 2 changed):
|
|
1032
|
+
┌─────────────────────────────────────────┐
|
|
1033
|
+
│ [Original PDF bytes unchanged] │
|
|
1034
|
+
│ ... │
|
|
1035
|
+
│ ─────── Incremental Update ─────── │
|
|
1036
|
+
│ Object 2' (New Page 2 content) │
|
|
1037
|
+
│ XRef Table (updated) │
|
|
1038
|
+
│ Trailer (updated) │
|
|
1039
|
+
└─────────────────────────────────────────┘
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
**Challenges:**
|
|
1043
|
+
- PDF viewers may not support incremental updates well
|
|
1044
|
+
- File size grows with each update
|
|
1045
|
+
- Complex implementation
|
|
1046
|
+
|
|
1047
|
+
**Recommendation:** Use PNG preview for development, full PDF for export.
|
|
1048
|
+
|
|
1049
|
+
### 1.5.7 Performance Targets
|
|
1050
|
+
|
|
1051
|
+
| Operation | Target Time | Notes |
|
|
1052
|
+
|-----------|-------------|-------|
|
|
1053
|
+
| Detect changes | < 50ms | AST diff |
|
|
1054
|
+
| Render single page to PNG | < 200ms | Typical page |
|
|
1055
|
+
| WebSocket push to browser | < 10ms | PNG base64 |
|
|
1056
|
+
| **Total update latency** | **< 300ms** | From save to preview |
|
|
1057
|
+
|
|
1058
|
+
---
|
|
1059
|
+
|
|
1060
|
+
## Phase 2: New Visual Features
|
|
1061
|
+
|
|
1062
|
+
### 2.1 Linear Gradients
|
|
1063
|
+
|
|
1064
|
+
**TSX Syntax:**
|
|
1065
|
+
```tsx
|
|
1066
|
+
<View
|
|
1067
|
+
width="100%"
|
|
1068
|
+
height="200px"
|
|
1069
|
+
background="linear-gradient(45deg, #ff6b6b, #4ecdc4)"
|
|
1070
|
+
/>
|
|
1071
|
+
|
|
1072
|
+
// Or with explicit stops
|
|
1073
|
+
<View
|
|
1074
|
+
width="100%"
|
|
1075
|
+
height="200px"
|
|
1076
|
+
background="linear-gradient(to right, #ff6b6b 0%, #feca57 50%, #4ecdc4 100%)"
|
|
1077
|
+
/>
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
**Implementation:**
|
|
1081
|
+
- `EVGGradient.rgr` - Gradient definition class
|
|
1082
|
+
- PDF: Use shading patterns (Type 2 - Axial)
|
|
1083
|
+
- Web: CSS `linear-gradient()`
|
|
1084
|
+
|
|
1085
|
+
### 2.2 Radial Gradients
|
|
1086
|
+
|
|
1087
|
+
**TSX Syntax:**
|
|
1088
|
+
```tsx
|
|
1089
|
+
<View
|
|
1090
|
+
width="200px"
|
|
1091
|
+
height="200px"
|
|
1092
|
+
background="radial-gradient(circle at center, #ffffff, #000000)"
|
|
1093
|
+
/>
|
|
1094
|
+
|
|
1095
|
+
// Elliptical
|
|
1096
|
+
<View
|
|
1097
|
+
width="300px"
|
|
1098
|
+
height="200px"
|
|
1099
|
+
background="radial-gradient(ellipse at 30% 50%, #ff6b6b 0%, transparent 70%)"
|
|
1100
|
+
/>
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
**Implementation:**
|
|
1104
|
+
- PDF: Use shading patterns (Type 3 - Radial)
|
|
1105
|
+
- Web: CSS `radial-gradient()`
|
|
1106
|
+
|
|
1107
|
+
### 2.3 Layer Element
|
|
1108
|
+
|
|
1109
|
+
A `Layer` element renders on top of its parent with the same dimensions, enabling stacking of content.
|
|
1110
|
+
|
|
1111
|
+
**TSX Syntax:**
|
|
1112
|
+
```tsx
|
|
1113
|
+
<View width="400px" height="300px">
|
|
1114
|
+
{/* Base layer - image */}
|
|
1115
|
+
<Layer>
|
|
1116
|
+
<Image src="photo.jpg" width="100%" height="100%" />
|
|
1117
|
+
</Layer>
|
|
1118
|
+
|
|
1119
|
+
{/* Overlay layer - gradient */}
|
|
1120
|
+
<Layer>
|
|
1121
|
+
<View
|
|
1122
|
+
width="100%"
|
|
1123
|
+
height="100%"
|
|
1124
|
+
background="linear-gradient(to top, rgba(0,0,0,0.7), transparent)"
|
|
1125
|
+
/>
|
|
1126
|
+
</Layer>
|
|
1127
|
+
|
|
1128
|
+
{/* Text layer */}
|
|
1129
|
+
<Layer verticalAlign="bottom" padding="20px">
|
|
1130
|
+
<Label fontSize="24px" color="white">Photo Title</Label>
|
|
1131
|
+
<Label fontSize="14px" color="white" opacity="0.8">Subtitle text</Label>
|
|
1132
|
+
</Layer>
|
|
1133
|
+
</View>
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
**Properties:**
|
|
1137
|
+
- `verticalAlign`: "top" | "center" | "bottom"
|
|
1138
|
+
- `align`: "left" | "center" | "right"
|
|
1139
|
+
- `padding`: Inset from parent edges
|
|
1140
|
+
- `opacity`: Layer transparency
|
|
1141
|
+
|
|
1142
|
+
**Implementation:**
|
|
1143
|
+
- Layer inherits parent's calculated width/height
|
|
1144
|
+
- Layers are rendered in order (first = bottom, last = top)
|
|
1145
|
+
- PDF: Save/restore graphics state for each layer
|
|
1146
|
+
|
|
1147
|
+
### 2.4 Image viewBox (Crop/Zoom)
|
|
1148
|
+
|
|
1149
|
+
The `viewBox` attribute allows cropping/zooming into a specific region of an image using relative percentages.
|
|
1150
|
+
|
|
1151
|
+
**TSX Syntax:**
|
|
1152
|
+
```tsx
|
|
1153
|
+
// Full image (default)
|
|
1154
|
+
<Image src="photo.jpg" width="400px" height="300px" />
|
|
1155
|
+
|
|
1156
|
+
// Zoom into center 80% of image (10% crop from each edge)
|
|
1157
|
+
<Image
|
|
1158
|
+
src="photo.jpg"
|
|
1159
|
+
width="400px"
|
|
1160
|
+
height="300px"
|
|
1161
|
+
viewBox="10% 10% 80% 80%"
|
|
1162
|
+
/>
|
|
1163
|
+
|
|
1164
|
+
// Zoom into top-left quadrant
|
|
1165
|
+
<Image
|
|
1166
|
+
src="photo.jpg"
|
|
1167
|
+
width="400px"
|
|
1168
|
+
height="300px"
|
|
1169
|
+
viewBox="0% 0% 50% 50%"
|
|
1170
|
+
/>
|
|
1171
|
+
|
|
1172
|
+
// Zoom into bottom-right area
|
|
1173
|
+
<Image
|
|
1174
|
+
src="photo.jpg"
|
|
1175
|
+
width="400px"
|
|
1176
|
+
height="300px"
|
|
1177
|
+
viewBox="40% 40% 60% 60%"
|
|
1178
|
+
/>
|
|
1179
|
+
|
|
1180
|
+
// Also supports pixel values
|
|
1181
|
+
<Image
|
|
1182
|
+
src="photo.jpg"
|
|
1183
|
+
width="400px"
|
|
1184
|
+
height="300px"
|
|
1185
|
+
viewBox="100px 100px 800px 600px"
|
|
1186
|
+
/>
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
**viewBox Format:** `"x y width height"`
|
|
1190
|
+
- `x` - Horizontal offset (% or px from left edge)
|
|
1191
|
+
- `y` - Vertical offset (% or px from top edge)
|
|
1192
|
+
- `width` - Width of visible area (% or px)
|
|
1193
|
+
- `height` - Height of visible area (% or px)
|
|
1194
|
+
|
|
1195
|
+
**Implementation:**
|
|
1196
|
+
- Calculate source rectangle in original image pixels
|
|
1197
|
+
- Scale/stretch source rectangle to fill element dimensions
|
|
1198
|
+
- PDF: Use image clipping and transformation matrix
|
|
1199
|
+
- Web: Use CSS `object-position` and `object-fit`, or Canvas `drawImage()` with source rect
|
|
1200
|
+
|
|
1201
|
+
**Use Cases:**
|
|
1202
|
+
- Portrait crop from landscape photo
|
|
1203
|
+
- Focus on face/subject
|
|
1204
|
+
- Create zoom effects
|
|
1205
|
+
- Ken Burns style animations (future)
|
|
1206
|
+
|
|
1207
|
+
### 2.5 Image Element as Container
|
|
1208
|
+
|
|
1209
|
+
`Image` element children render inside a virtual layer over the image.
|
|
1210
|
+
|
|
1211
|
+
**TSX Syntax:**
|
|
1212
|
+
```tsx
|
|
1213
|
+
<Image src="photo.jpg" width="400px" height="300px">
|
|
1214
|
+
<View
|
|
1215
|
+
position="absolute"
|
|
1216
|
+
bottom="0"
|
|
1217
|
+
width="100%"
|
|
1218
|
+
padding="10px"
|
|
1219
|
+
background="rgba(0,0,0,0.5)"
|
|
1220
|
+
>
|
|
1221
|
+
<Label color="white">{image.date}</Label>
|
|
1222
|
+
</View>
|
|
1223
|
+
</Image>
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
### 2.6 Background Images
|
|
1227
|
+
|
|
1228
|
+
Views can have background images with optional viewBox cropping.
|
|
1229
|
+
|
|
1230
|
+
**TSX Syntax:**
|
|
1231
|
+
```tsx
|
|
1232
|
+
// Simple background image
|
|
1233
|
+
<View
|
|
1234
|
+
width="100%"
|
|
1235
|
+
height="400px"
|
|
1236
|
+
backgroundImage="photo.jpg"
|
|
1237
|
+
backgroundSize="cover"
|
|
1238
|
+
>
|
|
1239
|
+
<Label fontSize="32px" color="white">Title Text</Label>
|
|
1240
|
+
</View>
|
|
1241
|
+
|
|
1242
|
+
// Background with viewBox zoom
|
|
1243
|
+
<View
|
|
1244
|
+
width="100%"
|
|
1245
|
+
height="400px"
|
|
1246
|
+
backgroundImage="photo.jpg"
|
|
1247
|
+
backgroundViewBox="20% 10% 60% 80%"
|
|
1248
|
+
backgroundSize="cover"
|
|
1249
|
+
>
|
|
1250
|
+
<Label fontSize="32px" color="white">Zoomed Background</Label>
|
|
1251
|
+
</View>
|
|
1252
|
+
|
|
1253
|
+
// Combined with gradient overlay
|
|
1254
|
+
<View
|
|
1255
|
+
width="100%"
|
|
1256
|
+
height="400px"
|
|
1257
|
+
backgroundImage="photo.jpg"
|
|
1258
|
+
backgroundSize="cover"
|
|
1259
|
+
>
|
|
1260
|
+
<Layer background="linear-gradient(to top, rgba(0,0,0,0.8), transparent)">
|
|
1261
|
+
<View padding="20px" verticalAlign="bottom">
|
|
1262
|
+
<Label fontSize="32px" color="white">Hero Title</Label>
|
|
1263
|
+
<Label fontSize="16px" color="white" opacity="0.8">Subtitle</Label>
|
|
1264
|
+
</View>
|
|
1265
|
+
</Layer>
|
|
1266
|
+
</View>
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
**backgroundSize options:**
|
|
1270
|
+
- `"cover"` - Scale to cover entire element (may crop)
|
|
1271
|
+
- `"contain"` - Scale to fit within element (may have gaps)
|
|
1272
|
+
- `"stretch"` - Stretch to fill exactly (may distort)
|
|
1273
|
+
- `"100% 100%"` - Explicit size
|
|
1274
|
+
|
|
1275
|
+
---
|
|
1276
|
+
|
|
1277
|
+
## Phase 3: Element ID System
|
|
1278
|
+
|
|
1279
|
+
### 3.1 Automatic ID Generation
|
|
1280
|
+
|
|
1281
|
+
Every element gets a unique ID automatically.
|
|
1282
|
+
|
|
1283
|
+
**Format:** `{elementType}_{index}` or `{elementType}_{parentId}_{index}`
|
|
1284
|
+
|
|
1285
|
+
```tsx
|
|
1286
|
+
// Auto-generated IDs
|
|
1287
|
+
<View> {/* id: "view_0" */}
|
|
1288
|
+
<Label>Text</Label> {/* id: "label_view_0_0" */}
|
|
1289
|
+
<Image src="..." /> {/* id: "image_view_0_1" */}
|
|
1290
|
+
</View>
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
### 3.2 User-defined IDs
|
|
1294
|
+
|
|
1295
|
+
Users can set custom IDs for elements they need to reference.
|
|
1296
|
+
|
|
1297
|
+
**TSX Syntax:**
|
|
1298
|
+
```tsx
|
|
1299
|
+
<View id="main-container">
|
|
1300
|
+
<Label id="title">Hello World</Label>
|
|
1301
|
+
<Image id="hero-image" src="photo.jpg" />
|
|
1302
|
+
</View>
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
### 3.3 ID Registry API
|
|
1306
|
+
|
|
1307
|
+
```typescript
|
|
1308
|
+
// Get element by ID
|
|
1309
|
+
const element = evg.getElementById("hero-image");
|
|
1310
|
+
|
|
1311
|
+
// Get all elements of type
|
|
1312
|
+
const allImages = evg.getElementsByType("image");
|
|
1313
|
+
|
|
1314
|
+
// Get element metadata
|
|
1315
|
+
const metadata = evg.getElementMetadata("hero-image");
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
---
|
|
1319
|
+
|
|
1320
|
+
## Phase 4: Image Metadata System
|
|
1321
|
+
|
|
1322
|
+
### 4.1 EXIF Data Extraction
|
|
1323
|
+
|
|
1324
|
+
Extend existing `JPEGMetadata.rgr` to provide structured metadata.
|
|
1325
|
+
|
|
1326
|
+
**Available metadata:**
|
|
1327
|
+
```typescript
|
|
1328
|
+
interface ImageMetadata {
|
|
1329
|
+
// Basic
|
|
1330
|
+
width: number;
|
|
1331
|
+
height: number;
|
|
1332
|
+
orientation: number;
|
|
1333
|
+
|
|
1334
|
+
// Camera
|
|
1335
|
+
make?: string;
|
|
1336
|
+
model?: string;
|
|
1337
|
+
exposureTime?: string;
|
|
1338
|
+
fNumber?: number;
|
|
1339
|
+
iso?: number;
|
|
1340
|
+
focalLength?: number;
|
|
1341
|
+
|
|
1342
|
+
// Date/Time
|
|
1343
|
+
dateTime?: string; // "2024:06:15 14:32:45"
|
|
1344
|
+
dateTimeOriginal?: string;
|
|
1345
|
+
|
|
1346
|
+
// GPS
|
|
1347
|
+
gps?: {
|
|
1348
|
+
latitude: number; // Decimal degrees (positive = N)
|
|
1349
|
+
longitude: number; // Decimal degrees (positive = E)
|
|
1350
|
+
altitude?: number; // Meters
|
|
1351
|
+
timestamp?: string;
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
### 4.2 Context Access in TSX
|
|
1357
|
+
|
|
1358
|
+
**Option A: Context Provider**
|
|
1359
|
+
```tsx
|
|
1360
|
+
<Image src="photo.jpg" width="400px" height="300px">
|
|
1361
|
+
<ImageContext>
|
|
1362
|
+
{(meta) => (
|
|
1363
|
+
<View position="absolute" bottom="0" padding="10px">
|
|
1364
|
+
<Label color="white">{meta.dateTime}</Label>
|
|
1365
|
+
{meta.gps && (
|
|
1366
|
+
<Label color="white" fontSize="12px">
|
|
1367
|
+
📍 {meta.gps.latitude.toFixed(4)}, {meta.gps.longitude.toFixed(4)}
|
|
1368
|
+
</Label>
|
|
1369
|
+
)}
|
|
1370
|
+
</View>
|
|
1371
|
+
)}
|
|
1372
|
+
</ImageContext>
|
|
1373
|
+
</Image>
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
**Option B: Special Variables**
|
|
1377
|
+
```tsx
|
|
1378
|
+
<Image src="photo.jpg" width="400px" height="300px">
|
|
1379
|
+
<Label color="white">${image.dateTime}</Label>
|
|
1380
|
+
<Label color="white">${image.gps.latitude}</Label>
|
|
1381
|
+
</Image>
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
### 4.3 Metadata API by Element ID
|
|
1385
|
+
|
|
1386
|
+
```typescript
|
|
1387
|
+
// Get metadata for specific image element
|
|
1388
|
+
const meta = evg.getImageMetadata("hero-image");
|
|
1389
|
+
console.log(meta.gps.latitude, meta.gps.longitude);
|
|
1390
|
+
|
|
1391
|
+
// Get all images with GPS data
|
|
1392
|
+
const geoImages = evg.getImagesWithGPS();
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
---
|
|
1396
|
+
|
|
1397
|
+
## Phase 5: MapBox Component
|
|
1398
|
+
|
|
1399
|
+
### 5.1 Concept
|
|
1400
|
+
|
|
1401
|
+
A `MapBox` component that:
|
|
1402
|
+
1. Takes a list of images with GPS coordinates
|
|
1403
|
+
2. Calculates bounding box from coordinate extremes
|
|
1404
|
+
3. Positions images relative to their GPS location within the box
|
|
1405
|
+
4. Optionally draws route/path between images
|
|
1406
|
+
|
|
1407
|
+
### 5.2 TSX Syntax
|
|
1408
|
+
|
|
1409
|
+
**Basic Usage:**
|
|
1410
|
+
```tsx
|
|
1411
|
+
<MapBox
|
|
1412
|
+
width="600px"
|
|
1413
|
+
height="400px"
|
|
1414
|
+
images={[
|
|
1415
|
+
"IMG_001.jpg",
|
|
1416
|
+
"IMG_002.jpg",
|
|
1417
|
+
"IMG_003.jpg"
|
|
1418
|
+
]}
|
|
1419
|
+
markerSize="60px"
|
|
1420
|
+
showRoute={true}
|
|
1421
|
+
routeColor="#3498db"
|
|
1422
|
+
/>
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
**Advanced Usage:**
|
|
1426
|
+
```tsx
|
|
1427
|
+
<MapBox
|
|
1428
|
+
width="600px"
|
|
1429
|
+
height="400px"
|
|
1430
|
+
padding="20px"
|
|
1431
|
+
backgroundColor="#f0f0f0"
|
|
1432
|
+
borderRadius="10px"
|
|
1433
|
+
>
|
|
1434
|
+
{/* Images auto-positioned by GPS */}
|
|
1435
|
+
<MapImage src="IMG_001.jpg" markerStyle="circle" />
|
|
1436
|
+
<MapImage src="IMG_002.jpg" markerStyle="square" />
|
|
1437
|
+
<MapImage src="IMG_003.jpg" markerStyle="circle" />
|
|
1438
|
+
|
|
1439
|
+
{/* Route line connecting images by time order */}
|
|
1440
|
+
<MapRoute color="#3498db" width="2px" dashed={true} />
|
|
1441
|
+
|
|
1442
|
+
{/* Custom marker at specific coordinates */}
|
|
1443
|
+
<MapMarker lat={60.1699} lon={24.9384}>
|
|
1444
|
+
<View padding="5px" backgroundColor="red" borderRadius="50%">
|
|
1445
|
+
<Label color="white" fontSize="12px">Start</Label>
|
|
1446
|
+
</View>
|
|
1447
|
+
</MapMarker>
|
|
1448
|
+
</MapBox>
|
|
1449
|
+
```
|
|
1450
|
+
|
|
1451
|
+
### 5.3 Implementation Details
|
|
1452
|
+
|
|
1453
|
+
**Coordinate Transformation:**
|
|
1454
|
+
```
|
|
1455
|
+
GPS Bounds:
|
|
1456
|
+
minLat = min(all latitudes)
|
|
1457
|
+
maxLat = max(all latitudes)
|
|
1458
|
+
minLon = min(all longitudes)
|
|
1459
|
+
maxLon = max(all longitudes)
|
|
1460
|
+
|
|
1461
|
+
Mapping function:
|
|
1462
|
+
x = (lon - minLon) / (maxLon - minLon) * boxWidth
|
|
1463
|
+
y = (maxLat - lat) / (maxLat - minLat) * boxHeight // Inverted Y
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
**Features:**
|
|
1467
|
+
- Auto-calculate bounds from image GPS or manual bounds
|
|
1468
|
+
- Padding to prevent markers at edges
|
|
1469
|
+
- Scale bar showing distance
|
|
1470
|
+
- North arrow indicator
|
|
1471
|
+
- Optional background map tile integration (future)
|
|
1472
|
+
|
|
1473
|
+
### 5.4 MapBox Props
|
|
1474
|
+
|
|
1475
|
+
```typescript
|
|
1476
|
+
interface MapBoxProps {
|
|
1477
|
+
// Dimensions
|
|
1478
|
+
width: string | number;
|
|
1479
|
+
height: string | number;
|
|
1480
|
+
|
|
1481
|
+
// Content
|
|
1482
|
+
images?: string[]; // Array of image paths
|
|
1483
|
+
|
|
1484
|
+
// Bounds (optional - auto-calculated if not specified)
|
|
1485
|
+
bounds?: {
|
|
1486
|
+
north: number; // Max latitude
|
|
1487
|
+
south: number; // Min latitude
|
|
1488
|
+
east: number; // Max longitude
|
|
1489
|
+
west: number; // Min longitude
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
// Markers
|
|
1493
|
+
markerSize?: string | number;
|
|
1494
|
+
markerStyle?: "circle" | "square" | "thumbnail";
|
|
1495
|
+
|
|
1496
|
+
// Route
|
|
1497
|
+
showRoute?: boolean;
|
|
1498
|
+
routeColor?: string;
|
|
1499
|
+
routeWidth?: string | number;
|
|
1500
|
+
routeDashed?: boolean;
|
|
1501
|
+
sortBy?: "time" | "none"; // How to order route points
|
|
1502
|
+
|
|
1503
|
+
// Visual
|
|
1504
|
+
backgroundColor?: string;
|
|
1505
|
+
padding?: string | number;
|
|
1506
|
+
showScale?: boolean;
|
|
1507
|
+
showNorthArrow?: boolean;
|
|
1508
|
+
}
|
|
1509
|
+
```
|
|
1510
|
+
|
|
1511
|
+
---
|
|
1512
|
+
|
|
1513
|
+
## Phase 6: Implementation Priority
|
|
1514
|
+
|
|
1515
|
+
### Sprint 1: Foundation (Week 1-2)
|
|
1516
|
+
- [ ] Element ID system (auto + manual)
|
|
1517
|
+
- [ ] Image metadata extraction enhancement
|
|
1518
|
+
- [ ] Metadata API by element ID
|
|
1519
|
+
|
|
1520
|
+
### Sprint 2: Visual Features (Week 3-4)
|
|
1521
|
+
- [ ] Linear gradient support (PDF + Web)
|
|
1522
|
+
- [ ] Radial gradient support (PDF + Web)
|
|
1523
|
+
- [ ] Layer element implementation
|
|
1524
|
+
|
|
1525
|
+
### Sprint 3: Web Debugger (Week 5-6)
|
|
1526
|
+
- [ ] HTML/CSS renderer for EVG
|
|
1527
|
+
- [ ] Debug UI with element inspector
|
|
1528
|
+
- [ ] Hot-reload preview
|
|
1529
|
+
|
|
1530
|
+
### Sprint 4: MapBox (Week 7-8)
|
|
1531
|
+
- [ ] GPS coordinate extraction from images
|
|
1532
|
+
- [ ] Coordinate transformation
|
|
1533
|
+
- [ ] MapBox component with route drawing
|
|
1534
|
+
- [ ] MapImage and MapMarker sub-components
|
|
1535
|
+
|
|
1536
|
+
---
|
|
1537
|
+
|
|
1538
|
+
## File Structure
|
|
1539
|
+
|
|
1540
|
+
```
|
|
1541
|
+
gallery/
|
|
1542
|
+
├── evg/
|
|
1543
|
+
│ ├── EVGElement.rgr # Core element (add ID, metadata)
|
|
1544
|
+
│ ├── EVGLayout.rgr # Layout engine
|
|
1545
|
+
│ ├── EVGGradient.rgr # NEW: Gradient definitions
|
|
1546
|
+
│ ├── EVGLayer.rgr # NEW: Layer element
|
|
1547
|
+
│ ├── EVGMapBox.rgr # NEW: Map component
|
|
1548
|
+
│ └── web/
|
|
1549
|
+
│ ├── EVGWebRenderer.rgr # NEW: HTML/CSS output
|
|
1550
|
+
│ ├── EVGCanvasRenderer.rgr # NEW: Canvas output
|
|
1551
|
+
│ └── evg_debug_server.rgr # NEW: Preview server
|
|
1552
|
+
│
|
|
1553
|
+
├── pdf_writer/
|
|
1554
|
+
│ ├── src/
|
|
1555
|
+
│ │ ├── core/
|
|
1556
|
+
│ │ │ ├── EVGPDFRenderer.rgr # Update for gradients, layers
|
|
1557
|
+
│ │ │ └── PDFGradient.rgr # NEW: PDF gradient patterns
|
|
1558
|
+
│ │ ├── jpeg/
|
|
1559
|
+
│ │ │ └── JPEGMetadata.rgr # Enhance EXIF parsing
|
|
1560
|
+
│ │ └── jsx/
|
|
1561
|
+
│ │ └── ComponentEngine.rgr # Add Layer, MapBox components
|
|
1562
|
+
│ └── examples/
|
|
1563
|
+
│ ├── test_gradients.tsx # NEW: Gradient examples
|
|
1564
|
+
│ ├── test_layers.tsx # NEW: Layer examples
|
|
1565
|
+
│ └── test_mapbox.tsx # NEW: MapBox examples
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1568
|
+
---
|
|
1569
|
+
|
|
1570
|
+
## Notes
|
|
1571
|
+
|
|
1572
|
+
- **Backward Compatibility**: All existing TSX files must continue to work
|
|
1573
|
+
- **Performance**: Lazy-load image metadata only when accessed
|
|
1574
|
+
- **Memory**: Cache parsed metadata to avoid re-reading files
|
|
1575
|
+
- **Web Debugger**: Should work without requiring Node.js (pure Ranger HTTP server)
|
|
1576
|
+
|
|
1577
|
+
---
|
|
1578
|
+
|
|
1579
|
+
## Priority Implementation Order
|
|
1580
|
+
|
|
1581
|
+
Based on critical use cases (text overlays, image cropping, backgrounds), here is the recommended implementation order:
|
|
1582
|
+
|
|
1583
|
+
### HIGH PRIORITY (Implement First)
|
|
1584
|
+
|
|
1585
|
+
1. **Layer Element** (Phase 2.3)
|
|
1586
|
+
- Essential for text overlays on images
|
|
1587
|
+
- Background image + gradient overlay patterns
|
|
1588
|
+
- Stacking multiple visual elements
|
|
1589
|
+
- *Implementation:* Add Layer to component parser, update all renderers
|
|
1590
|
+
|
|
1591
|
+
2. **Image viewBox** (Phase 2.4)
|
|
1592
|
+
- Crop/zoom into specific image regions
|
|
1593
|
+
- Portrait crop from landscape photos
|
|
1594
|
+
- Focus on subjects without pre-processing images
|
|
1595
|
+
- *Implementation:* Add viewBox parsing, calculate source rectangles
|
|
1596
|
+
|
|
1597
|
+
3. **Background Images** (Phase 2.6)
|
|
1598
|
+
- Views with background images
|
|
1599
|
+
- Combined with viewBox for background cropping
|
|
1600
|
+
- Essential for hero sections, cards, etc.
|
|
1601
|
+
- *Implementation:* Add backgroundImage/backgroundViewBox attributes
|
|
1602
|
+
|
|
1603
|
+
4. **Linear Gradients** (Phase 2.1)
|
|
1604
|
+
- Gradient overlays for readability
|
|
1605
|
+
- Visual effects and transitions
|
|
1606
|
+
- *Implementation:* Parse gradient syntax, implement in PDF/Canvas
|
|
1607
|
+
|
|
1608
|
+
### MEDIUM PRIORITY (Implement Second)
|
|
1609
|
+
|
|
1610
|
+
5. **Image as Container** (Phase 2.5)
|
|
1611
|
+
- Child elements overlay on images
|
|
1612
|
+
- Alternative to Layer for simple cases
|
|
1613
|
+
- *Implementation:* Handle Image children as overlay layer
|
|
1614
|
+
|
|
1615
|
+
6. **Radial Gradients** (Phase 2.2)
|
|
1616
|
+
- Spotlight/vignette effects
|
|
1617
|
+
- *Implementation:* Similar to linear but with radial math
|
|
1618
|
+
|
|
1619
|
+
7. **Web Debugger** (Phase 1)
|
|
1620
|
+
- Interactive preview without PDF export
|
|
1621
|
+
- Layout debugging tools
|
|
1622
|
+
- *Implementation:* HTML/CSS renderer, debug server
|
|
1623
|
+
|
|
1624
|
+
### LOWER PRIORITY (Implement Later)
|
|
1625
|
+
|
|
1626
|
+
8. **Element IDs** (Phase 3)
|
|
1627
|
+
- Debugging and referencing
|
|
1628
|
+
- *Implementation:* Auto-generate or parse id attribute
|
|
1629
|
+
|
|
1630
|
+
9. **Image Metadata** (Phase 4)
|
|
1631
|
+
- EXIF, GPS extraction
|
|
1632
|
+
- Context for templates
|
|
1633
|
+
- *Implementation:* Enhance JPEG parser
|
|
1634
|
+
|
|
1635
|
+
10. **MapBox Component** (Phase 5)
|
|
1636
|
+
- GPS-based image positioning
|
|
1637
|
+
- *Implementation:* MapBox API integration
|
|
1638
|
+
|
|
1639
|
+
---
|
|
1640
|
+
|
|
1641
|
+
## Quick Start Examples
|
|
1642
|
+
|
|
1643
|
+
### Text on Image (Layer + Background)
|
|
1644
|
+
```tsx
|
|
1645
|
+
<View width="600px" height="400px" backgroundImage="sunset.jpg" backgroundSize="cover">
|
|
1646
|
+
<Layer background="linear-gradient(to top, rgba(0,0,0,0.7), transparent)">
|
|
1647
|
+
<View verticalAlign="bottom" padding="20px">
|
|
1648
|
+
<Label fontSize="28px" color="white" fontWeight="bold">Beautiful Sunset</Label>
|
|
1649
|
+
<Label fontSize="14px" color="white" opacity="0.8">Location: Finland</Label>
|
|
1650
|
+
</View>
|
|
1651
|
+
</Layer>
|
|
1652
|
+
</View>
|
|
1653
|
+
```
|
|
1654
|
+
|
|
1655
|
+
### Cropped Portrait Image
|
|
1656
|
+
```tsx
|
|
1657
|
+
<Image
|
|
1658
|
+
src="landscape_photo.jpg"
|
|
1659
|
+
width="200px"
|
|
1660
|
+
height="300px"
|
|
1661
|
+
viewBox="25% 10% 50% 80%"
|
|
1662
|
+
/>
|
|
1663
|
+
```
|
|
1664
|
+
|
|
1665
|
+
### Image Gallery Card
|
|
1666
|
+
```tsx
|
|
1667
|
+
<View width="300px" height="200px" borderRadius="10px" overflow="hidden">
|
|
1668
|
+
<Image src="photo.jpg" width="100%" height="100%" viewBox="10% 0% 80% 100%">
|
|
1669
|
+
<View position="absolute" bottom="0" width="100%" background="rgba(0,0,0,0.6)" padding="10px">
|
|
1670
|
+
<Label color="white">Photo Title</Label>
|
|
1671
|
+
</View>
|
|
1672
|
+
</Image>
|
|
1673
|
+
</View>
|
|
1674
|
+
```
|