@vodailoc/kilo-kit-mcp 1.1.0 → 1.1.1
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/.mcp/kilo-kit.codex-windows.toml +5 -0
- package/LICENSE +190 -190
- package/QUICKSTART.md +265 -255
- package/README.md +290 -266
- package/mcp/README.md +29 -5
- package/mcp/dist/server.js +1 -1
- package/mcp/package.json +1 -2
- package/package.json +3 -2
- package/skills/README.md +647 -647
- package/skills/SKILLS_INDEX.md +139 -139
- package/skills/ai-media/ai-multimodal/.env.example +97 -97
- package/skills/ai-media/ai-multimodal/SKILL.md +357 -357
- package/skills/ai-media/ai-multimodal/references/audio-processing.md +373 -373
- package/skills/ai-media/ai-multimodal/references/image-generation.md +558 -558
- package/skills/ai-media/ai-multimodal/references/video-analysis.md +502 -502
- package/skills/ai-media/ai-multimodal/references/vision-understanding.md +483 -483
- package/skills/ai-media/ai-multimodal/scripts/document_converter.py +395 -395
- package/skills/ai-media/ai-multimodal/scripts/gemini_batch_process.py +480 -480
- package/skills/ai-media/ai-multimodal/scripts/media_optimizer.py +506 -506
- package/skills/ai-media/ai-multimodal/scripts/requirements.txt +26 -26
- package/skills/ai-media/ai-multimodal/scripts/tests/requirements.txt +20 -20
- package/skills/ai-media/ai-multimodal/scripts/tests/test_document_converter.py +299 -299
- package/skills/ai-media/ai-multimodal/scripts/tests/test_gemini_batch_process.py +362 -362
- package/skills/ai-media/ai-multimodal/scripts/tests/test_media_optimizer.py +373 -373
- package/skills/ai-media/media-processing/SKILL.md +358 -358
- package/skills/ai-media/media-processing/references/ffmpeg-encoding.md +358 -358
- package/skills/ai-media/media-processing/references/ffmpeg-filters.md +503 -503
- package/skills/ai-media/media-processing/references/ffmpeg-streaming.md +403 -403
- package/skills/ai-media/media-processing/references/format-compatibility.md +375 -375
- package/skills/ai-media/media-processing/references/imagemagick-batch.md +612 -612
- package/skills/ai-media/media-processing/references/imagemagick-editing.md +623 -623
- package/skills/ai-media/media-processing/scripts/batch_resize.py +342 -342
- package/skills/ai-media/media-processing/scripts/media_convert.py +311 -311
- package/skills/ai-media/media-processing/scripts/requirements.txt +24 -24
- package/skills/ai-media/media-processing/scripts/tests/requirements.txt +2 -2
- package/skills/ai-media/media-processing/scripts/tests/test_batch_resize.py +372 -372
- package/skills/ai-media/media-processing/scripts/tests/test_media_convert.py +259 -259
- package/skills/ai-media/media-processing/scripts/tests/test_video_optimize.py +397 -397
- package/skills/ai-media/media-processing/scripts/video_optimize.py +414 -414
- package/skills/ai-media/screenshot/LICENSE.txt +201 -201
- package/skills/ai-media/screenshot/SKILL.md +267 -267
- package/skills/ai-media/screenshot/agents/openai.yaml +6 -6
- package/skills/ai-media/screenshot/assets/screenshot-small.svg +5 -5
- package/skills/ai-media/screenshot/scripts/ensure_macos_permissions.sh +54 -54
- package/skills/ai-media/screenshot/scripts/macos_display_info.swift +22 -22
- package/skills/ai-media/screenshot/scripts/macos_permissions.swift +40 -40
- package/skills/ai-media/screenshot/scripts/macos_window_info.swift +126 -126
- package/skills/ai-media/screenshot/scripts/take_screenshot.ps1 +163 -163
- package/skills/ai-media/screenshot/scripts/take_screenshot.py +585 -585
- package/skills/ai-media/sora/LICENSE.txt +201 -201
- package/skills/ai-media/sora/SKILL.md +153 -153
- package/skills/ai-media/sora/agents/openai.yaml +6 -6
- package/skills/ai-media/sora/assets/sora-small.svg +4 -4
- package/skills/ai-media/sora/references/cinematic-shots.md +53 -53
- package/skills/ai-media/sora/references/cli.md +248 -248
- package/skills/ai-media/sora/references/codex-network.md +28 -28
- package/skills/ai-media/sora/references/prompting.md +137 -137
- package/skills/ai-media/sora/references/sample-prompts.md +95 -95
- package/skills/ai-media/sora/references/social-ads.md +42 -42
- package/skills/ai-media/sora/references/troubleshooting.md +58 -58
- package/skills/ai-media/sora/references/video-api.md +45 -45
- package/skills/ai-media/sora/scripts/sora.py +970 -970
- package/skills/design/aesthetic/SKILL.md +121 -121
- package/skills/design/aesthetic/assets/design-guideline-template.md +163 -163
- package/skills/design/aesthetic/assets/design-story-template.md +135 -135
- package/skills/design/aesthetic/references/design-principles.md +62 -62
- package/skills/design/aesthetic/references/design-resources.md +75 -75
- package/skills/design/aesthetic/references/micro-interactions.md +53 -53
- package/skills/design/aesthetic/references/storytelling-design.md +50 -50
- package/skills/design/figma/LICENSE.txt +202 -202
- package/skills/design/figma/SKILL.md +42 -42
- package/skills/design/figma/agents/openai.yaml +14 -14
- package/skills/design/figma/assets/figma-small.svg +3 -3
- package/skills/design/figma/assets/icon.svg +28 -28
- package/skills/design/figma/references/figma-mcp-config.md +35 -35
- package/skills/design/figma/references/figma-tools-and-prompts.md +34 -34
- package/skills/design/figma-implement-design/LICENSE.txt +202 -202
- package/skills/design/figma-implement-design/SKILL.md +264 -264
- package/skills/design/figma-implement-design/agents/openai.yaml +14 -14
- package/skills/design/figma-implement-design/assets/figma-small.svg +3 -3
- package/skills/design/figma-implement-design/assets/icon.svg +28 -28
- package/skills/design/frontend-design/SKILL.md +41 -41
- package/skills/design/frontend-design/references/animejs.md +395 -395
- package/skills/design/ui-styling/LICENSE.txt +201 -201
- package/skills/design/ui-styling/SKILL.md +321 -321
- package/skills/design/ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/DMMono-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -94
- package/skills/design/ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Gloock-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Italiana-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Jura-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Lora-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Outfit-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/Tektur-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -93
- package/skills/design/ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -93
- package/skills/design/ui-styling/references/canvas-design-system.md +320 -320
- package/skills/design/ui-styling/references/shadcn-accessibility.md +471 -471
- package/skills/design/ui-styling/references/shadcn-components.md +424 -424
- package/skills/design/ui-styling/references/shadcn-theming.md +373 -373
- package/skills/design/ui-styling/references/tailwind-customization.md +483 -483
- package/skills/design/ui-styling/references/tailwind-responsive.md +382 -382
- package/skills/design/ui-styling/references/tailwind-utilities.md +455 -455
- package/skills/design/ui-styling/scripts/requirements.txt +17 -17
- package/skills/design/ui-styling/scripts/shadcn_add.py +292 -292
- package/skills/design/ui-styling/scripts/tailwind_config_gen.py +456 -456
- package/skills/design/ui-styling/scripts/tests/requirements.txt +3 -3
- package/skills/design/ui-styling/scripts/tests/test_shadcn_add.py +266 -266
- package/skills/design/ui-styling/scripts/tests/test_tailwind_config_gen.py +336 -336
- package/skills/engineering/aspnet-core/LICENSE.txt +201 -201
- package/skills/engineering/aspnet-core/SKILL.md +61 -61
- package/skills/engineering/aspnet-core/agents/openai.yaml +5 -5
- package/skills/engineering/aspnet-core/references/_sections.md +40 -40
- package/skills/engineering/aspnet-core/references/apis-minimal-and-controllers.md +81 -81
- package/skills/engineering/aspnet-core/references/data-state-and-services.md +69 -69
- package/skills/engineering/aspnet-core/references/program-and-pipeline.md +103 -103
- package/skills/engineering/aspnet-core/references/realtime-grpc-and-background-work.md +58 -58
- package/skills/engineering/aspnet-core/references/security-and-identity.md +75 -75
- package/skills/engineering/aspnet-core/references/source-map.md +43 -43
- package/skills/engineering/aspnet-core/references/stack-selection.md +63 -63
- package/skills/engineering/aspnet-core/references/testing-performance-and-operations.md +92 -92
- package/skills/engineering/aspnet-core/references/ui-blazor.md +53 -53
- package/skills/engineering/aspnet-core/references/ui-mvc.md +56 -56
- package/skills/engineering/aspnet-core/references/ui-razor-pages.md +55 -55
- package/skills/engineering/aspnet-core/references/versioning-and-upgrades.md +51 -51
- package/skills/engineering/backend-development/SKILL.md +95 -95
- package/skills/engineering/backend-development/references/backend-api-design.md +495 -495
- package/skills/engineering/backend-development/references/backend-architecture.md +454 -454
- package/skills/engineering/backend-development/references/backend-authentication.md +338 -338
- package/skills/engineering/backend-development/references/backend-code-quality.md +659 -659
- package/skills/engineering/backend-development/references/backend-debugging.md +904 -904
- package/skills/engineering/backend-development/references/backend-devops.md +494 -494
- package/skills/engineering/backend-development/references/backend-mindset.md +387 -387
- package/skills/engineering/backend-development/references/backend-performance.md +397 -397
- package/skills/engineering/backend-development/references/backend-security.md +290 -290
- package/skills/engineering/backend-development/references/backend-technologies.md +256 -256
- package/skills/engineering/backend-development/references/backend-testing.md +429 -429
- package/skills/engineering/better-auth/SKILL.md +204 -204
- package/skills/engineering/better-auth/references/advanced-features.md +553 -553
- package/skills/engineering/better-auth/references/database-integration.md +577 -577
- package/skills/engineering/better-auth/references/email-password-auth.md +416 -416
- package/skills/engineering/better-auth/references/oauth-providers.md +430 -430
- package/skills/engineering/better-auth/scripts/better_auth_init.py +521 -521
- package/skills/engineering/better-auth/scripts/requirements.txt +15 -15
- package/skills/engineering/better-auth/scripts/tests/test_better_auth_init.py +421 -421
- package/skills/engineering/code-review/SKILL.md +140 -140
- package/skills/engineering/code-review/references/code-review-reception.md +208 -208
- package/skills/engineering/code-review/references/requesting-code-review.md +104 -104
- package/skills/engineering/code-review/references/verification-before-completion.md +138 -138
- package/skills/engineering/context-engineering/SKILL.md +86 -86
- package/skills/engineering/context-engineering/references/context-compression.md +84 -84
- package/skills/engineering/context-engineering/references/context-degradation.md +93 -93
- package/skills/engineering/context-engineering/references/context-fundamentals.md +75 -75
- package/skills/engineering/context-engineering/references/context-optimization.md +82 -82
- package/skills/engineering/context-engineering/references/evaluation.md +89 -89
- package/skills/engineering/context-engineering/references/memory-systems.md +88 -88
- package/skills/engineering/context-engineering/references/multi-agent-patterns.md +90 -90
- package/skills/engineering/context-engineering/references/project-development.md +97 -97
- package/skills/engineering/context-engineering/references/tool-design.md +86 -86
- package/skills/engineering/context-engineering/scripts/compression_evaluator.py +329 -329
- package/skills/engineering/context-engineering/scripts/context_analyzer.py +294 -294
- package/skills/engineering/databases/SKILL.md +232 -232
- package/skills/engineering/databases/references/mongodb-aggregation.md +447 -447
- package/skills/engineering/databases/references/mongodb-atlas.md +465 -465
- package/skills/engineering/databases/references/mongodb-crud.md +408 -408
- package/skills/engineering/databases/references/mongodb-indexing.md +442 -442
- package/skills/engineering/databases/references/postgresql-administration.md +594 -594
- package/skills/engineering/databases/references/postgresql-performance.md +527 -527
- package/skills/engineering/databases/references/postgresql-psql-cli.md +467 -467
- package/skills/engineering/databases/references/postgresql-queries.md +475 -475
- package/skills/engineering/databases/scripts/db_backup.py +502 -502
- package/skills/engineering/databases/scripts/db_migrate.py +414 -414
- package/skills/engineering/databases/scripts/db_performance_check.py +444 -444
- package/skills/engineering/databases/scripts/requirements.txt +20 -20
- package/skills/engineering/databases/scripts/tests/requirements.txt +4 -4
- package/skills/engineering/databases/scripts/tests/test_db_backup.py +340 -340
- package/skills/engineering/databases/scripts/tests/test_db_migrate.py +277 -277
- package/skills/engineering/databases/scripts/tests/test_db_performance_check.py +370 -370
- package/skills/engineering/diagnose/SKILL.md +117 -117
- package/skills/engineering/diagnose/scripts/hitl-loop.template.sh +41 -41
- package/skills/engineering/docs-seeker/SKILL.md +207 -207
- package/skills/engineering/docs-seeker/WORKFLOWS.md +505 -505
- package/skills/engineering/docs-seeker/references/best-practices.md +632 -632
- package/skills/engineering/docs-seeker/references/documentation-sources.md +461 -461
- package/skills/engineering/docs-seeker/references/error-handling.md +621 -621
- package/skills/engineering/docs-seeker/references/limitations.md +821 -821
- package/skills/engineering/docs-seeker/references/performance.md +574 -574
- package/skills/engineering/docs-seeker/references/tool-selection.md +262 -262
- package/skills/engineering/frontend-development/SKILL.md +398 -398
- package/skills/engineering/frontend-development/resources/common-patterns.md +330 -330
- package/skills/engineering/frontend-development/resources/complete-examples.md +871 -871
- package/skills/engineering/frontend-development/resources/component-patterns.md +501 -501
- package/skills/engineering/frontend-development/resources/data-fetching.md +766 -766
- package/skills/engineering/frontend-development/resources/file-organization.md +501 -501
- package/skills/engineering/frontend-development/resources/loading-and-error-states.md +500 -500
- package/skills/engineering/frontend-development/resources/performance.md +405 -405
- package/skills/engineering/frontend-development/resources/routing-guide.md +363 -363
- package/skills/engineering/frontend-development/resources/styling-guide.md +427 -427
- package/skills/engineering/frontend-development/resources/typescript-standards.md +417 -417
- package/skills/engineering/improve-codebase-architecture/DEEPENING.md +37 -37
- package/skills/engineering/improve-codebase-architecture/INTERFACE-DESIGN.md +44 -44
- package/skills/engineering/improve-codebase-architecture/LANGUAGE.md +53 -53
- package/skills/engineering/improve-codebase-architecture/SKILL.md +71 -71
- package/skills/engineering/openai-docs/LICENSE.txt +201 -201
- package/skills/engineering/openai-docs/SKILL.md +69 -69
- package/skills/engineering/openai-docs/agents/openai.yaml +14 -14
- package/skills/engineering/openai-docs/assets/openai-small.svg +3 -3
- package/skills/engineering/openai-docs/references/gpt-5p4-prompting-guide.md +433 -433
- package/skills/engineering/openai-docs/references/latest-model.md +35 -35
- package/skills/engineering/openai-docs/references/upgrading-to-gpt-5p4.md +164 -164
- package/skills/engineering/playwright/LICENSE.txt +201 -201
- package/skills/engineering/playwright/NOTICE.txt +14 -14
- package/skills/engineering/playwright/SKILL.md +147 -147
- package/skills/engineering/playwright/agents/openai.yaml +6 -6
- package/skills/engineering/playwright/assets/playwright-small.svg +3 -3
- package/skills/engineering/playwright/references/cli.md +116 -116
- package/skills/engineering/playwright/references/workflows.md +95 -95
- package/skills/engineering/playwright/scripts/playwright_cli.sh +25 -25
- package/skills/engineering/playwright-interactive/LICENSE.txt +201 -201
- package/skills/engineering/playwright-interactive/NOTICE.txt +13 -13
- package/skills/engineering/playwright-interactive/SKILL.md +689 -689
- package/skills/engineering/playwright-interactive/agents/openai.yaml +6 -6
- package/skills/engineering/playwright-interactive/assets/playwright-small.svg +3 -3
- package/skills/engineering/render-deploy/LICENSE.txt +201 -201
- package/skills/engineering/render-deploy/SKILL.md +479 -479
- package/skills/engineering/render-deploy/agents/openai.yaml +14 -14
- package/skills/engineering/render-deploy/assets/docker.yaml +62 -62
- package/skills/engineering/render-deploy/assets/go-api.yaml +35 -35
- package/skills/engineering/render-deploy/assets/nextjs-postgres.yaml +35 -35
- package/skills/engineering/render-deploy/assets/node-express.yaml +25 -25
- package/skills/engineering/render-deploy/assets/python-django.yaml +89 -89
- package/skills/engineering/render-deploy/assets/render-small.svg +3 -3
- package/skills/engineering/render-deploy/assets/static-site.yaml +54 -54
- package/skills/engineering/render-deploy/references/blueprint-spec.md +718 -718
- package/skills/engineering/render-deploy/references/codebase-analysis.md +49 -49
- package/skills/engineering/render-deploy/references/configuration-guide.md +603 -603
- package/skills/engineering/render-deploy/references/deployment-details.md +224 -224
- package/skills/engineering/render-deploy/references/direct-creation.md +113 -113
- package/skills/engineering/render-deploy/references/error-patterns.md +13 -13
- package/skills/engineering/render-deploy/references/post-deploy-checks.md +36 -36
- package/skills/engineering/render-deploy/references/runtimes.md +473 -473
- package/skills/engineering/render-deploy/references/service-types.md +450 -450
- package/skills/engineering/render-deploy/references/troubleshooting-basics.md +36 -36
- package/skills/engineering/repomix/SKILL.md +215 -215
- package/skills/engineering/repomix/references/configuration.md +211 -211
- package/skills/engineering/repomix/references/usage-patterns.md +232 -232
- package/skills/engineering/repomix/scripts/README.md +179 -179
- package/skills/engineering/repomix/scripts/repomix_batch.py +455 -455
- package/skills/engineering/repomix/scripts/repos.example.json +15 -15
- package/skills/engineering/repomix/scripts/requirements.txt +15 -15
- package/skills/engineering/repomix/scripts/tests/test_repomix_batch.py +531 -531
- package/skills/engineering/setup-matt-pocock-skills/SKILL.md +121 -121
- package/skills/engineering/setup-matt-pocock-skills/domain.md +51 -51
- package/skills/engineering/setup-matt-pocock-skills/issue-tracker-github.md +22 -22
- package/skills/engineering/setup-matt-pocock-skills/issue-tracker-gitlab.md +23 -23
- package/skills/engineering/setup-matt-pocock-skills/issue-tracker-local.md +19 -19
- package/skills/engineering/setup-matt-pocock-skills/triage-labels.md +15 -15
- package/skills/engineering/shopify/README.md +66 -66
- package/skills/engineering/shopify/SKILL.md +319 -319
- package/skills/engineering/shopify/references/app-development.md +470 -470
- package/skills/engineering/shopify/references/extensions.md +493 -493
- package/skills/engineering/shopify/references/themes.md +498 -498
- package/skills/engineering/shopify/scripts/requirements.txt +19 -19
- package/skills/engineering/shopify/scripts/shopify_init.py +423 -423
- package/skills/engineering/shopify/scripts/tests/test_shopify_init.py +385 -385
- package/skills/engineering/tdd/SKILL.md +109 -109
- package/skills/engineering/tdd/deep-modules.md +33 -33
- package/skills/engineering/tdd/interface-design.md +31 -31
- package/skills/engineering/tdd/mocking.md +59 -59
- package/skills/engineering/tdd/refactoring.md +10 -10
- package/skills/engineering/tdd/tests.md +61 -61
- package/skills/engineering/to-issues/SKILL.md +81 -81
- package/skills/engineering/to-prd/SKILL.md +74 -74
- package/skills/engineering/triage/AGENT-BRIEF.md +168 -168
- package/skills/engineering/triage/OUT-OF-SCOPE.md +101 -101
- package/skills/engineering/triage/SKILL.md +103 -103
- package/skills/engineering/web-frameworks/SKILL.md +324 -324
- package/skills/engineering/web-frameworks/references/nextjs-app-router.md +465 -465
- package/skills/engineering/web-frameworks/references/nextjs-data-fetching.md +459 -459
- package/skills/engineering/web-frameworks/references/nextjs-optimization.md +511 -511
- package/skills/engineering/web-frameworks/references/nextjs-server-components.md +495 -495
- package/skills/engineering/web-frameworks/references/remix-icon-integration.md +603 -603
- package/skills/engineering/web-frameworks/references/turborepo-caching.md +551 -551
- package/skills/engineering/web-frameworks/references/turborepo-pipelines.md +517 -517
- package/skills/engineering/web-frameworks/references/turborepo-setup.md +542 -542
- package/skills/engineering/web-frameworks/scripts/nextjs_init.py +547 -547
- package/skills/engineering/web-frameworks/scripts/requirements.txt +16 -16
- package/skills/engineering/web-frameworks/scripts/tests/requirements.txt +3 -3
- package/skills/engineering/web-frameworks/scripts/tests/test_nextjs_init.py +319 -319
- package/skills/engineering/web-frameworks/scripts/tests/test_turborepo_migrate.py +374 -374
- package/skills/engineering/web-frameworks/scripts/turborepo_migrate.py +394 -394
- package/skills/engineering/write-a-skill/SKILL.md +117 -117
- package/skills/kilo-kit/SKILL.md +346 -346
- package/skills/kilo-kit/_template/SKILL.md +185 -185
- package/skills/kilo-kit/debugging/root-cause/SKILL.md +360 -360
- package/skills/kilo-kit/debugging/systematic/SKILL.md +339 -339
- package/skills/kilo-kit/debugging/verification/SKILL.md +424 -424
- package/skills/kilo-kit/development/backend/SKILL.md +540 -540
- package/skills/kilo-kit/development/security/SKILL.md +529 -529
- package/skills/kilo-kit/quality/code-review/SKILL.md +297 -297
- package/skills/kilo-kit/quality/testing/SKILL.md +540 -540
- package/skills/kilo-kit/references/output-formats.md +204 -204
- package/skills/kilo-kit/references/patterns.md +156 -156
- package/skills/kilo-kit/references/performance-benchmarks.md +90 -90
- package/skills/operations/chrome-devtools/SKILL.md +392 -392
- package/skills/operations/chrome-devtools/references/cdp-domains.md +694 -694
- package/skills/operations/chrome-devtools/references/performance-guide.md +940 -940
- package/skills/operations/chrome-devtools/references/puppeteer-reference.md +953 -953
- package/skills/operations/chrome-devtools/scripts/PERSISTENT-BROWSER.md +107 -107
- package/skills/operations/chrome-devtools/scripts/README.md +213 -213
- package/skills/operations/chrome-devtools/scripts/__tests__/selector.test.js +210 -210
- package/skills/operations/chrome-devtools/scripts/click.js +79 -79
- package/skills/operations/chrome-devtools/scripts/close-persistent.js +36 -36
- package/skills/operations/chrome-devtools/scripts/console.js +75 -75
- package/skills/operations/chrome-devtools/scripts/evaluate.js +49 -49
- package/skills/operations/chrome-devtools/scripts/fill.js +72 -72
- package/skills/operations/chrome-devtools/scripts/install-deps.sh +181 -181
- package/skills/operations/chrome-devtools/scripts/install.sh +83 -83
- package/skills/operations/chrome-devtools/scripts/launch-persistent.js +71 -71
- package/skills/operations/chrome-devtools/scripts/lib/browser.js +144 -144
- package/skills/operations/chrome-devtools/scripts/lib/selector.js +178 -178
- package/skills/operations/chrome-devtools/scripts/navigate.js +46 -46
- package/skills/operations/chrome-devtools/scripts/network.js +102 -102
- package/skills/operations/chrome-devtools/scripts/package-lock.json +1206 -1206
- package/skills/operations/chrome-devtools/scripts/package.json +15 -15
- package/skills/operations/chrome-devtools/scripts/performance.js +145 -145
- package/skills/operations/chrome-devtools/scripts/screenshot.js +180 -180
- package/skills/operations/chrome-devtools/scripts/snapshot.js +131 -131
- package/skills/operations/devops/.env.example +76 -76
- package/skills/operations/devops/SKILL.md +285 -285
- package/skills/operations/devops/references/browser-rendering.md +305 -305
- package/skills/operations/devops/references/cloudflare-d1-kv.md +123 -123
- package/skills/operations/devops/references/cloudflare-platform.md +271 -271
- package/skills/operations/devops/references/cloudflare-r2-storage.md +280 -280
- package/skills/operations/devops/references/cloudflare-workers-advanced.md +312 -312
- package/skills/operations/devops/references/cloudflare-workers-apis.md +309 -309
- package/skills/operations/devops/references/cloudflare-workers-basics.md +418 -418
- package/skills/operations/devops/references/docker-basics.md +297 -297
- package/skills/operations/devops/references/docker-compose.md +292 -292
- package/skills/operations/devops/references/gcloud-platform.md +297 -297
- package/skills/operations/devops/references/gcloud-services.md +304 -304
- package/skills/operations/devops/scripts/cloudflare_deploy.py +269 -269
- package/skills/operations/devops/scripts/docker_optimize.py +320 -320
- package/skills/operations/devops/scripts/requirements.txt +20 -20
- package/skills/operations/devops/scripts/tests/requirements.txt +3 -3
- package/skills/operations/devops/scripts/tests/test_cloudflare_deploy.py +285 -285
- package/skills/operations/devops/scripts/tests/test_docker_optimize.py +436 -436
- package/skills/operations/mcp-builder/LICENSE.txt +201 -201
- package/skills/operations/mcp-builder/SKILL.md +328 -328
- package/skills/operations/mcp-builder/reference/evaluation.md +601 -601
- package/skills/operations/mcp-builder/reference/mcp_best_practices.md +915 -915
- package/skills/operations/mcp-builder/reference/node_mcp_server.md +915 -915
- package/skills/operations/mcp-builder/reference/python_mcp_server.md +751 -751
- package/skills/operations/mcp-builder/scripts/connections.py +151 -151
- package/skills/operations/mcp-builder/scripts/evaluation.py +373 -373
- package/skills/operations/mcp-builder/scripts/example_evaluation.xml +22 -22
- package/skills/operations/mcp-builder/scripts/requirements.txt +2 -2
- package/skills/operations/mcp-management/README.md +219 -219
- package/skills/operations/mcp-management/SKILL.md +175 -175
- package/skills/operations/mcp-management/assets/tools.json +3043 -3043
- package/skills/operations/mcp-management/references/configuration.md +114 -114
- package/skills/operations/mcp-management/references/gemini-cli-integration.md +201 -201
- package/skills/operations/mcp-management/references/mcp-protocol.md +116 -116
- package/skills/operations/mcp-management/scripts/.env.example +10 -10
- package/skills/operations/mcp-management/scripts/cli.ts +155 -155
- package/skills/operations/mcp-management/scripts/dist/analyze-tools.js +70 -70
- package/skills/operations/mcp-management/scripts/dist/cli.js +131 -131
- package/skills/operations/mcp-management/scripts/dist/mcp-client.js +115 -115
- package/skills/operations/mcp-management/scripts/mcp-client.ts +163 -163
- package/skills/operations/mcp-management/scripts/package.json +18 -18
- package/skills/operations/mcp-management/scripts/tsconfig.json +15 -15
- package/skills/problem-solving/collision-zone-thinking/SKILL.md +62 -62
- package/skills/problem-solving/defense-in-depth/SKILL.md +130 -130
- package/skills/problem-solving/inversion-exercise/SKILL.md +58 -58
- package/skills/problem-solving/meta-pattern-recognition/SKILL.md +54 -54
- package/skills/problem-solving/root-cause-tracing/SKILL.md +177 -177
- package/skills/problem-solving/root-cause-tracing/find-polluter.sh +63 -63
- package/skills/problem-solving/scale-game/SKILL.md +63 -63
- package/skills/problem-solving/sequential-thinking/README.md +118 -118
- package/skills/problem-solving/sequential-thinking/SKILL.md +93 -93
- package/skills/problem-solving/sequential-thinking/references/advanced.md +122 -122
- package/skills/problem-solving/sequential-thinking/references/examples.md +274 -274
- package/skills/problem-solving/simplification-cascades/SKILL.md +76 -76
- package/skills/problem-solving/when-stuck/SKILL.md +88 -88
- package/skills/productivity/caveman/SKILL.md +49 -49
- package/skills/productivity/grill-me/SKILL.md +10 -10
- package/skills/productivity/grill-with-docs/ADR-FORMAT.md +47 -47
- package/skills/productivity/grill-with-docs/CONTEXT-FORMAT.md +77 -77
- package/skills/productivity/grill-with-docs/SKILL.md +88 -88
- package/skills/productivity/writing-skills/graphviz-conventions.dot +171 -171
- package/skills/productivity/zoom-out/SKILL.md +7 -7
- package/skills/writing-docs/doc/LICENSE.txt +201 -201
- package/skills/writing-docs/doc/SKILL.md +80 -80
- package/skills/writing-docs/doc/agents/openai.yaml +6 -6
- package/skills/writing-docs/doc/assets/doc-small.svg +3 -3
- package/skills/writing-docs/doc/scripts/render_docx.py +296 -296
- package/skills/writing-docs/docx/LICENSE.txt +30 -30
- package/skills/writing-docs/docx/SKILL.md +196 -196
- package/skills/writing-docs/docx/docx-js.md +349 -349
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
- package/skills/writing-docs/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
- package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
- package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
- package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
- package/skills/writing-docs/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
- package/skills/writing-docs/docx/ooxml/schemas/mce/mc.xsd +75 -75
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -560
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -67
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -14
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -20
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -13
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
- package/skills/writing-docs/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -8
- package/skills/writing-docs/docx/ooxml/scripts/pack.py +159 -159
- package/skills/writing-docs/docx/ooxml/scripts/unpack.py +29 -29
- package/skills/writing-docs/docx/ooxml/scripts/validate.py +69 -69
- package/skills/writing-docs/docx/ooxml/scripts/validation/__init__.py +15 -15
- package/skills/writing-docs/docx/ooxml/scripts/validation/base.py +951 -951
- package/skills/writing-docs/docx/ooxml/scripts/validation/docx.py +274 -274
- package/skills/writing-docs/docx/ooxml/scripts/validation/pptx.py +315 -315
- package/skills/writing-docs/docx/ooxml/scripts/validation/redlining.py +279 -279
- package/skills/writing-docs/docx/ooxml.md +609 -609
- package/skills/writing-docs/docx/scripts/__init__.py +1 -1
- package/skills/writing-docs/docx/scripts/document.py +1276 -1276
- package/skills/writing-docs/docx/scripts/templates/comments.xml +2 -2
- package/skills/writing-docs/docx/scripts/templates/commentsExtended.xml +2 -2
- package/skills/writing-docs/docx/scripts/templates/commentsExtensible.xml +2 -2
- package/skills/writing-docs/docx/scripts/templates/commentsIds.xml +2 -2
- package/skills/writing-docs/docx/scripts/templates/people.xml +2 -2
- package/skills/writing-docs/docx/scripts/utilities.py +374 -374
- package/skills/writing-docs/mermaidjs-v11/SKILL.md +115 -115
- package/skills/writing-docs/mermaidjs-v11/references/cli-usage.md +228 -228
- package/skills/writing-docs/mermaidjs-v11/references/configuration.md +232 -232
- package/skills/writing-docs/mermaidjs-v11/references/diagram-types.md +315 -315
- package/skills/writing-docs/mermaidjs-v11/references/examples.md +344 -344
- package/skills/writing-docs/mermaidjs-v11/references/integration.md +310 -310
- package/skills/writing-docs/pdf/LICENSE.txt +30 -30
- package/skills/writing-docs/pdf/SKILL.md +294 -294
- package/skills/writing-docs/pdf/forms.md +205 -205
- package/skills/writing-docs/pdf/reference.md +611 -611
- package/skills/writing-docs/pdf/scripts/check_bounding_boxes.py +70 -70
- package/skills/writing-docs/pdf/scripts/check_bounding_boxes_test.py +226 -226
- package/skills/writing-docs/pdf/scripts/check_fillable_fields.py +12 -12
- package/skills/writing-docs/pdf/scripts/convert_pdf_to_images.py +35 -35
- package/skills/writing-docs/pdf/scripts/create_validation_image.py +41 -41
- package/skills/writing-docs/pdf/scripts/extract_form_field_info.py +152 -152
- package/skills/writing-docs/pdf/scripts/fill_fillable_fields.py +114 -114
- package/skills/writing-docs/pdf/scripts/fill_pdf_form_with_annotations.py +107 -107
- package/skills/writing-docs/pptx/LICENSE.txt +30 -30
- package/skills/writing-docs/pptx/SKILL.md +483 -483
- package/skills/writing-docs/pptx/html2pptx.md +624 -624
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
- package/skills/writing-docs/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
- package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
- package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
- package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
- package/skills/writing-docs/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
- package/skills/writing-docs/pptx/ooxml/schemas/mce/mc.xsd +75 -75
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -560
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -67
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -14
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -20
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -13
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
- package/skills/writing-docs/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -8
- package/skills/writing-docs/pptx/ooxml/scripts/pack.py +159 -159
- package/skills/writing-docs/pptx/ooxml/scripts/unpack.py +29 -29
- package/skills/writing-docs/pptx/ooxml/scripts/validate.py +69 -69
- package/skills/writing-docs/pptx/ooxml/scripts/validation/__init__.py +15 -15
- package/skills/writing-docs/pptx/ooxml/scripts/validation/base.py +951 -951
- package/skills/writing-docs/pptx/ooxml/scripts/validation/docx.py +274 -274
- package/skills/writing-docs/pptx/ooxml/scripts/validation/pptx.py +315 -315
- package/skills/writing-docs/pptx/ooxml/scripts/validation/redlining.py +279 -279
- package/skills/writing-docs/pptx/ooxml.md +426 -426
- package/skills/writing-docs/pptx/scripts/html2pptx.js +978 -978
- package/skills/writing-docs/pptx/scripts/inventory.py +1020 -1020
- package/skills/writing-docs/pptx/scripts/rearrange.py +231 -231
- package/skills/writing-docs/pptx/scripts/replace.py +385 -385
- package/skills/writing-docs/pptx/scripts/thumbnail.py +450 -450
- package/skills/writing-docs/slides/LICENSE.txt +201 -201
- package/skills/writing-docs/slides/SKILL.md +71 -71
- package/skills/writing-docs/slides/agents/openai.yaml +6 -6
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/code.js +104 -104
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/image.js +333 -333
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/index.js +33 -33
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/latex.js +51 -51
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/layout.js +643 -643
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/layout_builders.js +358 -358
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/svg.js +36 -36
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/text.js +789 -789
- package/skills/writing-docs/slides/assets/pptxgenjs_helpers/util.js +24 -24
- package/skills/writing-docs/slides/assets/slides-small.svg +3 -3
- package/skills/writing-docs/slides/references/pptxgenjs-helpers.md +61 -61
- package/skills/writing-docs/slides/scripts/create_montage.py +300 -300
- package/skills/writing-docs/slides/scripts/detect_font.py +873 -873
- package/skills/writing-docs/slides/scripts/ensure_raster_image.py +202 -202
- package/skills/writing-docs/slides/scripts/render_slides.py +273 -273
- package/skills/writing-docs/slides/scripts/slides_test.py +201 -201
- package/skills/writing-docs/template-skill/SKILL.md +26 -26
- package/skills/writing-docs/xlsx/LICENSE.txt +30 -30
- package/skills/writing-docs/xlsx/SKILL.md +288 -288
- package/skills/writing-docs/xlsx/recalc.py +177 -177
- package/src/core/KILO_MASTER.md +448 -448
- package/src/tools/validate-skill.js +421 -421
|
@@ -1,643 +1,643 @@
|
|
|
1
|
-
// Copyright (c) OpenAI. All rights reserved.
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
function inferElementType(obj) {
|
|
5
|
-
if (!obj) return "unknown";
|
|
6
|
-
const data = obj.data || obj.options || {};
|
|
7
|
-
// Distinguish lines explicitly via type only. Many objects have a 'line' style; don't misclassify those.
|
|
8
|
-
if (obj.type === "line") return "line";
|
|
9
|
-
if (obj.type && typeof obj.type === "string") return obj.type;
|
|
10
|
-
if (obj.text || typeof data.text === "string") return "text";
|
|
11
|
-
if (data.path || obj.image) return "image";
|
|
12
|
-
if (data.chartType) return "chart";
|
|
13
|
-
if (data.shape || data.line) return "shape";
|
|
14
|
-
if (data.mediaType) return "media";
|
|
15
|
-
if (data.table || Array.isArray(data.rows)) return "table";
|
|
16
|
-
if (data.smartArt) return "smartart";
|
|
17
|
-
return "unknown";
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const TEXT_OVERLAP_ERROR_THRESHOLD = 0.1;
|
|
21
|
-
const RECTIFY_DIRECTION_EQUALITY_TOLERANCE = 0.15;
|
|
22
|
-
|
|
23
|
-
function warnIfSlideHasOverlaps(slide, pptx, options = {}) {
|
|
24
|
-
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
25
|
-
console.warn("Invalid slide object passed to warnIfSlideOverlaps()");
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const opts = {
|
|
29
|
-
// By default, containment cases are very common (e.g., full-slide backgrounds)
|
|
30
|
-
// and usually not actionable. Mute them unless explicitly requested.
|
|
31
|
-
muteContainment:
|
|
32
|
-
options.muteContainment !== undefined ? options.muteContainment : true,
|
|
33
|
-
// Do NOT ignore lines or decorative shapes by default; users want true overlaps.
|
|
34
|
-
ignoreLines:
|
|
35
|
-
options.ignoreLines !== undefined ? options.ignoreLines : false,
|
|
36
|
-
ignoreDecorativeShapes:
|
|
37
|
-
options.ignoreDecorativeShapes !== undefined
|
|
38
|
-
? options.ignoreDecorativeShapes
|
|
39
|
-
: false,
|
|
40
|
-
};
|
|
41
|
-
const slideIndex =
|
|
42
|
-
pptx && Array.isArray(pptx._slides) ? pptx._slides.indexOf(slide) : -1;
|
|
43
|
-
const slideLabel =
|
|
44
|
-
slideIndex >= 0 ? `Slide ${slideIndex + 1}` : "(Unknown slide index)";
|
|
45
|
-
const formatElement = (el) => {
|
|
46
|
-
const cx = (el.x + el.w / 2).toFixed(3);
|
|
47
|
-
const cy = (el.y + el.h / 2).toFixed(3);
|
|
48
|
-
return `element ${el.index} (${el.type}, center_x=${cx}, center_y=${cy})`;
|
|
49
|
-
};
|
|
50
|
-
const elements = slide._slideObjects.map((obj, i) => {
|
|
51
|
-
const {
|
|
52
|
-
x = 0,
|
|
53
|
-
y = 0,
|
|
54
|
-
w = 0,
|
|
55
|
-
h = 0,
|
|
56
|
-
fill,
|
|
57
|
-
line,
|
|
58
|
-
} = obj.data || obj.options || {};
|
|
59
|
-
const type = inferElementType(obj);
|
|
60
|
-
const isDecorative = (() => {
|
|
61
|
-
if (!opts.ignoreDecorativeShapes) return false;
|
|
62
|
-
// Border rectangles used as frames: transparent fill (or fully transparent) with a stroke
|
|
63
|
-
const transparency =
|
|
64
|
-
typeof fill?.transparency === "number" ? fill.transparency : null;
|
|
65
|
-
const hasOnlyBorder = !!line && (!fill || transparency !== null);
|
|
66
|
-
const fullyTransparent = transparency !== null && transparency >= 99;
|
|
67
|
-
return type === "shape" && hasOnlyBorder && fullyTransparent;
|
|
68
|
-
})();
|
|
69
|
-
const ignorable = (opts.ignoreLines && type === "line") || isDecorative;
|
|
70
|
-
return { index: i, type, x, y, w, h, ignorable };
|
|
71
|
-
});
|
|
72
|
-
let overlapCount = 0;
|
|
73
|
-
let containmentCount = 0;
|
|
74
|
-
for (let i = 0; i < elements.length; i++) {
|
|
75
|
-
const a = elements[i];
|
|
76
|
-
if (a.ignorable) continue;
|
|
77
|
-
for (let j = i + 1; j < elements.length; j++) {
|
|
78
|
-
const b = elements[j];
|
|
79
|
-
if (b.ignorable) continue;
|
|
80
|
-
const comparison = compareElementPosition(slide, a.index, b.index);
|
|
81
|
-
if (comparison.relation === "overlapping") {
|
|
82
|
-
// Special-case: diagonal line's bounding box overlapping a rectangle is often a false positive.
|
|
83
|
-
const EPS = 1e-6;
|
|
84
|
-
const getBounds = (e) => ({
|
|
85
|
-
x: e.x,
|
|
86
|
-
y: e.y,
|
|
87
|
-
x2: e.x + e.w,
|
|
88
|
-
y2: e.y + e.h,
|
|
89
|
-
});
|
|
90
|
-
const lineRectFalsePositive = (() => {
|
|
91
|
-
const oneIsLine = (a.type === "line") ^ (b.type === "line");
|
|
92
|
-
if (!oneIsLine) return false;
|
|
93
|
-
const line = a.type === "line" ? a : b;
|
|
94
|
-
const rect = a.type === "line" ? b : a;
|
|
95
|
-
// If line is diagonal, verify actual segment intersects rect; if not, ignore.
|
|
96
|
-
const isDiagonal = line.w > EPS && line.h > EPS;
|
|
97
|
-
const lineSeg = {
|
|
98
|
-
x1: line.x,
|
|
99
|
-
y1: line.y,
|
|
100
|
-
x2: line.x + line.w,
|
|
101
|
-
y2: line.y + line.h,
|
|
102
|
-
};
|
|
103
|
-
const rectB = getBounds(rect);
|
|
104
|
-
const pointInRect = (px, py, rb) =>
|
|
105
|
-
px >= rb.x - EPS &&
|
|
106
|
-
px <= rb.x2 + EPS &&
|
|
107
|
-
py >= rb.y - EPS &&
|
|
108
|
-
py <= rb.y2 + EPS;
|
|
109
|
-
const segsIntersect = (p1, p2, q1, q2) => {
|
|
110
|
-
const cross = (ax, ay, bx, by) => ax * by - ay * bx;
|
|
111
|
-
const d1x = p2.x - p1.x,
|
|
112
|
-
d1y = p2.y - p1.y;
|
|
113
|
-
const d2x = q2.x - q1.x,
|
|
114
|
-
d2y = q2.y - q1.y;
|
|
115
|
-
const denom = cross(d1x, d1y, d2x, d2y);
|
|
116
|
-
if (Math.abs(denom) < EPS) {
|
|
117
|
-
// Parallel: check colinearity and overlapping projections
|
|
118
|
-
const crossCol = cross(q1.x - p1.x, q1.y - p1.y, d1x, d1y);
|
|
119
|
-
if (Math.abs(crossCol) > EPS) return false;
|
|
120
|
-
const proj = (a, b, c) =>
|
|
121
|
-
Math.min(Math.max(a, b), Math.max(Math.min(a, b), c));
|
|
122
|
-
const overlapX = !(
|
|
123
|
-
Math.max(p1.x, p2.x) < Math.min(q1.x, q2.x) - EPS ||
|
|
124
|
-
Math.max(q1.x, q2.x) < Math.min(p1.x, p2.x) - EPS
|
|
125
|
-
);
|
|
126
|
-
const overlapY = !(
|
|
127
|
-
Math.max(p1.y, p2.y) < Math.min(q1.y, q2.y) - EPS ||
|
|
128
|
-
Math.max(q1.y, q2.y) < Math.min(p1.y, p2.y) - EPS
|
|
129
|
-
);
|
|
130
|
-
return overlapX && overlapY;
|
|
131
|
-
}
|
|
132
|
-
const t = cross(q1.x - p1.x, q1.y - p1.y, d2x, d2y) / denom;
|
|
133
|
-
const u = cross(q1.x - p1.x, q1.y - p1.y, d1x, d1y) / denom;
|
|
134
|
-
return t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS;
|
|
135
|
-
};
|
|
136
|
-
const intersectsRect = (seg, rb) => {
|
|
137
|
-
if (
|
|
138
|
-
pointInRect(seg.x1, seg.y1, rb) ||
|
|
139
|
-
pointInRect(seg.x2, seg.y2, rb)
|
|
140
|
-
)
|
|
141
|
-
return true;
|
|
142
|
-
const r1 = { x: rb.x, y: rb.y },
|
|
143
|
-
r2 = { x: rb.x2, y: rb.y },
|
|
144
|
-
r3 = { x: rb.x2, y: rb.y2 },
|
|
145
|
-
r4 = { x: rb.x, y: rb.y2 };
|
|
146
|
-
const p1 = { x: seg.x1, y: seg.y1 },
|
|
147
|
-
p2 = { x: seg.x2, y: seg.y2 };
|
|
148
|
-
return (
|
|
149
|
-
segsIntersect(p1, p2, r1, r2) ||
|
|
150
|
-
segsIntersect(p1, p2, r2, r3) ||
|
|
151
|
-
segsIntersect(p1, p2, r3, r4) ||
|
|
152
|
-
segsIntersect(p1, p2, r4, r1)
|
|
153
|
-
);
|
|
154
|
-
};
|
|
155
|
-
return isDiagonal && !intersectsRect(lineSeg, rectB);
|
|
156
|
-
})();
|
|
157
|
-
if (!lineRectFalsePositive) {
|
|
158
|
-
overlapCount++;
|
|
159
|
-
|
|
160
|
-
const severeTextOverlap = (() => {
|
|
161
|
-
if (!comparison.intersection) return false;
|
|
162
|
-
const exceedsThreshold = (element) =>
|
|
163
|
-
element.type === "text" &&
|
|
164
|
-
comparison.intersection.w >= TEXT_OVERLAP_ERROR_THRESHOLD &&
|
|
165
|
-
comparison.intersection.h >= TEXT_OVERLAP_ERROR_THRESHOLD;
|
|
166
|
-
return exceedsThreshold(a) || exceedsThreshold(b);
|
|
167
|
-
})();
|
|
168
|
-
if (severeTextOverlap) {
|
|
169
|
-
const overlapW = comparison.intersection.w;
|
|
170
|
-
const overlapH = comparison.intersection.h;
|
|
171
|
-
let rectificationSuggestion = "";
|
|
172
|
-
if (overlapW > EPS && overlapH > EPS) {
|
|
173
|
-
const maxOverlap = Math.max(overlapW, overlapH);
|
|
174
|
-
const diffRatio = Math.abs(overlapW - overlapH) / maxOverlap;
|
|
175
|
-
const directions = [];
|
|
176
|
-
// Attempt to determine the primary direction of the overlap. This is the direction
|
|
177
|
-
// in which the overlap is smaller (and so requires the smallest adjustment to rectify).
|
|
178
|
-
if (diffRatio <= RECTIFY_DIRECTION_EQUALITY_TOLERANCE) {
|
|
179
|
-
directions.push("horizontally", "vertically");
|
|
180
|
-
} else if (overlapW < overlapH) {
|
|
181
|
-
directions.push("horizontally");
|
|
182
|
-
} else {
|
|
183
|
-
directions.push("vertically");
|
|
184
|
-
}
|
|
185
|
-
rectificationSuggestion = `Suggestion: reposition elements ${directions.join(
|
|
186
|
-
" and "
|
|
187
|
-
)}.`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
console.error(
|
|
191
|
-
`❌ ${slideLabel}: Severe text overlap detected between ${formatElement(
|
|
192
|
-
a
|
|
193
|
-
)} and ${formatElement(
|
|
194
|
-
b
|
|
195
|
-
)} (overlap_horizontal=${comparison.intersection.w.toFixed(
|
|
196
|
-
3
|
|
197
|
-
)}, overlap_vertical=${comparison.intersection.h.toFixed(
|
|
198
|
-
3
|
|
199
|
-
)}). THIS MUST BE FIXED. ${rectificationSuggestion}`
|
|
200
|
-
);
|
|
201
|
-
} else {
|
|
202
|
-
console.warn(
|
|
203
|
-
`⚠️ ${slideLabel}: Overlap detected between ${formatElement(
|
|
204
|
-
a
|
|
205
|
-
)} and ${formatElement(b)}.`
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
} else if (comparison.relation === "contained") {
|
|
210
|
-
if (!opts.muteContainment) {
|
|
211
|
-
containmentCount++;
|
|
212
|
-
const container = elements[comparison.containerIndex];
|
|
213
|
-
const contained = elements[comparison.containedIndex];
|
|
214
|
-
console.warn(
|
|
215
|
-
`⚠️ ${slideLabel}: ${formatElement(
|
|
216
|
-
contained
|
|
217
|
-
)} is fully contained within ${formatElement(container)}`
|
|
218
|
-
);
|
|
219
|
-
} else {
|
|
220
|
-
// Still count internally when muted? We keep for summary only when un-muted
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (!(overlapCount === 0 && (!containmentCount || opts.muteContainment))) {
|
|
226
|
-
const issues = [];
|
|
227
|
-
if (overlapCount > 0) issues.push(`${overlapCount} overlapping pair(s)`);
|
|
228
|
-
if (!opts.muteContainment && containmentCount > 0)
|
|
229
|
-
issues.push(`${containmentCount} containment case(s)`);
|
|
230
|
-
console.log(`⚠️ ${slideLabel}: Found ${issues.join(" and ")}.`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function compareElementPosition(slide, firstIndex, secondIndex) {
|
|
235
|
-
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
236
|
-
throw new Error("Invalid slide object passed to compareElementPosition()");
|
|
237
|
-
}
|
|
238
|
-
if (
|
|
239
|
-
typeof firstIndex !== "number" ||
|
|
240
|
-
typeof secondIndex !== "number" ||
|
|
241
|
-
!Number.isInteger(firstIndex) ||
|
|
242
|
-
!Number.isInteger(secondIndex)
|
|
243
|
-
) {
|
|
244
|
-
throw new Error("Element indices must be integer values.");
|
|
245
|
-
}
|
|
246
|
-
const elements = slide._slideObjects;
|
|
247
|
-
if (
|
|
248
|
-
firstIndex < 0 ||
|
|
249
|
-
firstIndex >= elements.length ||
|
|
250
|
-
secondIndex < 0 ||
|
|
251
|
-
secondIndex >= elements.length
|
|
252
|
-
) {
|
|
253
|
-
throw new Error(
|
|
254
|
-
"Element index out of bounds for compareElementPosition()."
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
const EPS = 1e-4;
|
|
258
|
-
const getBounds = (obj) => {
|
|
259
|
-
const source = obj?.data || obj?.options || {};
|
|
260
|
-
let x = typeof source.x === "number" ? source.x : 0;
|
|
261
|
-
let y = typeof source.y === "number" ? source.y : 0;
|
|
262
|
-
let w = typeof source.w === "number" ? source.w : 0;
|
|
263
|
-
let h = typeof source.h === "number" ? source.h : 0;
|
|
264
|
-
if (source.sizing && source.sizing.type === "crop") {
|
|
265
|
-
if (typeof source.sizing.w === "number") w = source.sizing.w;
|
|
266
|
-
if (typeof source.sizing.h === "number") h = source.sizing.h;
|
|
267
|
-
}
|
|
268
|
-
return { x, y, w, h, x2: x + w, y2: y + h };
|
|
269
|
-
};
|
|
270
|
-
const boundsA = getBounds(elements[firstIndex]);
|
|
271
|
-
const boundsB = getBounds(elements[secondIndex]);
|
|
272
|
-
const separated =
|
|
273
|
-
boundsA.x2 < boundsB.x - EPS ||
|
|
274
|
-
boundsB.x2 < boundsA.x - EPS ||
|
|
275
|
-
boundsA.y2 < boundsB.y - EPS ||
|
|
276
|
-
boundsB.y2 < boundsA.y - EPS;
|
|
277
|
-
if (separated) {
|
|
278
|
-
return {
|
|
279
|
-
relation: "disjoint",
|
|
280
|
-
containerIndex: null,
|
|
281
|
-
containedIndex: null,
|
|
282
|
-
aBounds: boundsA,
|
|
283
|
-
bBounds: boundsB,
|
|
284
|
-
intersection: null,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
const aContainsB =
|
|
288
|
-
boundsA.x <= boundsB.x + EPS &&
|
|
289
|
-
boundsA.y <= boundsB.y + EPS &&
|
|
290
|
-
boundsA.x2 >= boundsB.x2 - EPS &&
|
|
291
|
-
boundsA.y2 >= boundsB.y2 - EPS;
|
|
292
|
-
const bContainsA =
|
|
293
|
-
boundsB.x <= boundsA.x + EPS &&
|
|
294
|
-
boundsB.y <= boundsA.y + EPS &&
|
|
295
|
-
boundsB.x2 >= boundsA.x2 - EPS &&
|
|
296
|
-
boundsB.y2 >= boundsA.y2 - EPS;
|
|
297
|
-
const ix1 = Math.max(boundsA.x, boundsB.x);
|
|
298
|
-
const iy1 = Math.max(boundsA.y, boundsB.y);
|
|
299
|
-
const ix2 = Math.min(boundsA.x2, boundsB.x2);
|
|
300
|
-
const iy2 = Math.min(boundsA.y2, boundsB.y2);
|
|
301
|
-
const intersectionWidth = Math.max(0, ix2 - ix1);
|
|
302
|
-
const intersectionHeight = Math.max(0, iy2 - iy1);
|
|
303
|
-
const intersection =
|
|
304
|
-
intersectionWidth > EPS && intersectionHeight > EPS
|
|
305
|
-
? { x: ix1, y: iy1, w: intersectionWidth, h: intersectionHeight }
|
|
306
|
-
: null;
|
|
307
|
-
if (aContainsB && !bContainsA) {
|
|
308
|
-
return {
|
|
309
|
-
relation: "contained",
|
|
310
|
-
containerIndex: firstIndex,
|
|
311
|
-
containedIndex: secondIndex,
|
|
312
|
-
aBounds: boundsA,
|
|
313
|
-
bBounds: boundsB,
|
|
314
|
-
intersection,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (bContainsA && !aContainsB) {
|
|
318
|
-
return {
|
|
319
|
-
relation: "contained",
|
|
320
|
-
containerIndex: secondIndex,
|
|
321
|
-
containedIndex: firstIndex,
|
|
322
|
-
aBounds: boundsA,
|
|
323
|
-
bBounds: boundsB,
|
|
324
|
-
intersection,
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
if (intersection) {
|
|
328
|
-
return {
|
|
329
|
-
relation: "overlapping",
|
|
330
|
-
containerIndex: null,
|
|
331
|
-
containedIndex: null,
|
|
332
|
-
aBounds: boundsA,
|
|
333
|
-
bBounds: boundsB,
|
|
334
|
-
intersection,
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
return {
|
|
338
|
-
relation: "touching",
|
|
339
|
-
containerIndex: null,
|
|
340
|
-
containedIndex: null,
|
|
341
|
-
aBounds: boundsA,
|
|
342
|
-
bBounds: boundsB,
|
|
343
|
-
intersection: null,
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const VALID_ALIGNMENTS = new Set([
|
|
348
|
-
"left",
|
|
349
|
-
"right",
|
|
350
|
-
"top",
|
|
351
|
-
"bottom",
|
|
352
|
-
"verticallyCenter",
|
|
353
|
-
"horizontallyCenter",
|
|
354
|
-
]);
|
|
355
|
-
|
|
356
|
-
const getElementBounds = (obj) => {
|
|
357
|
-
const source = obj?.data || obj?.options || {};
|
|
358
|
-
let x = typeof source.x === "number" ? source.x : 0;
|
|
359
|
-
let y = typeof source.y === "number" ? source.y : 0;
|
|
360
|
-
let w = typeof source.w === "number" ? source.w : 0;
|
|
361
|
-
let h = typeof source.h === "number" ? source.h : 0;
|
|
362
|
-
// If an image is placed with crop sizing, pptxgenjs stores a larger virtual image w/h
|
|
363
|
-
// and a viewport in source.sizing.{w,h}. For visual overlap purposes, use the viewport.
|
|
364
|
-
if (source.sizing && source.sizing.type === "crop") {
|
|
365
|
-
if (typeof source.sizing.w === "number") w = source.sizing.w;
|
|
366
|
-
if (typeof source.sizing.h === "number") h = source.sizing.h;
|
|
367
|
-
}
|
|
368
|
-
return { x, y, w, h, x2: x + w, y2: y + h };
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const setElementPosition = (obj, coords) => {
|
|
372
|
-
const ensureTarget = (targetObj) => {
|
|
373
|
-
if (!targetObj || typeof targetObj !== "object") return null;
|
|
374
|
-
return targetObj;
|
|
375
|
-
};
|
|
376
|
-
const targets = [];
|
|
377
|
-
const dataTarget = ensureTarget(obj.data);
|
|
378
|
-
if (dataTarget) targets.push(dataTarget);
|
|
379
|
-
const optionsTarget =
|
|
380
|
-
obj.options && obj.options !== obj.data ? ensureTarget(obj.options) : null;
|
|
381
|
-
if (optionsTarget) targets.push(optionsTarget);
|
|
382
|
-
if (targets.length === 0) {
|
|
383
|
-
obj.data = obj.data && typeof obj.data === "object" ? obj.data : {};
|
|
384
|
-
targets.push(obj.data);
|
|
385
|
-
}
|
|
386
|
-
targets.forEach((target) => {
|
|
387
|
-
if (coords.x !== undefined) target.x = coords.x;
|
|
388
|
-
if (coords.y !== undefined) target.y = coords.y;
|
|
389
|
-
});
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const dimensionKeyPairs = [
|
|
393
|
-
["width", "height"],
|
|
394
|
-
["w", "h"],
|
|
395
|
-
["cx", "cy"],
|
|
396
|
-
["slideWidth", "slideHeight"],
|
|
397
|
-
["slideWidthInches", "slideHeightInches"],
|
|
398
|
-
["widthInches", "heightInches"],
|
|
399
|
-
];
|
|
400
|
-
|
|
401
|
-
const toNumber = (value) => {
|
|
402
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
403
|
-
if (typeof value === "string") {
|
|
404
|
-
const parsed = parseFloat(value);
|
|
405
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
406
|
-
}
|
|
407
|
-
return null;
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
const readDimensionsFromObject = (candidate, seen = new Set()) => {
|
|
411
|
-
if (!candidate || typeof candidate !== "object") return null;
|
|
412
|
-
if (seen.has(candidate)) return null;
|
|
413
|
-
seen.add(candidate);
|
|
414
|
-
for (const [wKey, hKey] of dimensionKeyPairs) {
|
|
415
|
-
const width = toNumber(candidate[wKey]);
|
|
416
|
-
const height = toNumber(candidate[hKey]);
|
|
417
|
-
if (width !== null && height !== null && width > 0 && height > 0) {
|
|
418
|
-
return { width, height };
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const nestedKeys = ["size", "slideSize", "layout", "slideLayout"];
|
|
422
|
-
for (const key of nestedKeys) {
|
|
423
|
-
const nested = readDimensionsFromObject(candidate[key], seen);
|
|
424
|
-
if (nested) return nested;
|
|
425
|
-
}
|
|
426
|
-
return null;
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
const getSlideDimensions = (slide, pptx) => {
|
|
430
|
-
const candidates = [
|
|
431
|
-
slide?._presLayout,
|
|
432
|
-
slide?._slideLayout,
|
|
433
|
-
slide?._pres?.layout,
|
|
434
|
-
slide?._parent?.layout,
|
|
435
|
-
slide?._layout,
|
|
436
|
-
pptx?._presLayout,
|
|
437
|
-
pptx?._layout,
|
|
438
|
-
pptx?.layout,
|
|
439
|
-
pptx?.presLayout,
|
|
440
|
-
];
|
|
441
|
-
for (const candidate of candidates) {
|
|
442
|
-
const dims = readDimensionsFromObject(candidate);
|
|
443
|
-
if (dims) {
|
|
444
|
-
// Some internals are in EMUs; convert if values look too large for inches
|
|
445
|
-
const EMU_PER_IN = 914400;
|
|
446
|
-
const looksEmu = dims.width > 1000 || dims.height > 1000;
|
|
447
|
-
if (looksEmu) {
|
|
448
|
-
return {
|
|
449
|
-
width: dims.width / EMU_PER_IN,
|
|
450
|
-
height: dims.height / EMU_PER_IN,
|
|
451
|
-
source: "emu_converted",
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
return { ...dims, source: "detected" };
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
throw new Error(
|
|
458
|
-
"getSlideDimensions(): Unable to determine slide dimensions from pptxgenjs internals."
|
|
459
|
-
);
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
function alignSlideElements(slide, indices, alignment) {
|
|
463
|
-
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
464
|
-
throw new Error("Invalid slide object passed to alignSlideElements()");
|
|
465
|
-
}
|
|
466
|
-
if (!Array.isArray(indices) || indices.length === 0) {
|
|
467
|
-
throw new Error("indices must be a non-empty array.");
|
|
468
|
-
}
|
|
469
|
-
if (!VALID_ALIGNMENTS.has(alignment)) {
|
|
470
|
-
throw new Error(`Unsupported alignment option: ${alignment}`);
|
|
471
|
-
}
|
|
472
|
-
const uniqueIndices = [...new Set(indices)];
|
|
473
|
-
const elements = slide._slideObjects;
|
|
474
|
-
const selected = uniqueIndices.map((idx) => {
|
|
475
|
-
if (typeof idx !== "number" || !Number.isInteger(idx)) {
|
|
476
|
-
throw new Error("Element indices must be integers.");
|
|
477
|
-
}
|
|
478
|
-
if (idx < 0 || idx >= elements.length) {
|
|
479
|
-
throw new Error("Element index out of bounds for alignSlideElements().");
|
|
480
|
-
}
|
|
481
|
-
const obj = elements[idx];
|
|
482
|
-
const bounds = getElementBounds(obj);
|
|
483
|
-
return { index: idx, obj, bounds };
|
|
484
|
-
});
|
|
485
|
-
if (selected.length < 2) return;
|
|
486
|
-
const minX = Math.min(...selected.map((item) => item.bounds.x));
|
|
487
|
-
const maxX2 = Math.max(...selected.map((item) => item.bounds.x2));
|
|
488
|
-
const minY = Math.min(...selected.map((item) => item.bounds.y));
|
|
489
|
-
const maxY2 = Math.max(...selected.map((item) => item.bounds.y2));
|
|
490
|
-
const centerX = (minX + maxX2) / 2;
|
|
491
|
-
const centerY = (minY + maxY2) / 2;
|
|
492
|
-
selected.forEach(({ obj, bounds }) => {
|
|
493
|
-
const { w, h } = bounds;
|
|
494
|
-
switch (alignment) {
|
|
495
|
-
case "left":
|
|
496
|
-
setElementPosition(obj, { x: minX });
|
|
497
|
-
break;
|
|
498
|
-
case "right":
|
|
499
|
-
setElementPosition(obj, { x: maxX2 - w });
|
|
500
|
-
break;
|
|
501
|
-
case "top":
|
|
502
|
-
setElementPosition(obj, { y: minY });
|
|
503
|
-
break;
|
|
504
|
-
case "bottom":
|
|
505
|
-
setElementPosition(obj, { y: maxY2 - h });
|
|
506
|
-
break;
|
|
507
|
-
case "horizontallyCenter":
|
|
508
|
-
setElementPosition(obj, { x: centerX - w / 2 });
|
|
509
|
-
break;
|
|
510
|
-
case "verticallyCenter":
|
|
511
|
-
setElementPosition(obj, { y: centerY - h / 2 });
|
|
512
|
-
break;
|
|
513
|
-
default:
|
|
514
|
-
throw new Error(`Unhandled alignment option: ${alignment}`);
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function distributeSlideElements(slide, indices, direction) {
|
|
520
|
-
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
521
|
-
throw new Error("Invalid slide object passed to distributeSlideElements()");
|
|
522
|
-
}
|
|
523
|
-
if (!Array.isArray(indices) || indices.length === 0) {
|
|
524
|
-
throw new Error("indices must be a non-empty array.");
|
|
525
|
-
}
|
|
526
|
-
if (direction !== "horizontal" && direction !== "vertical") {
|
|
527
|
-
throw new Error(`Unsupported distribution direction: ${direction}`);
|
|
528
|
-
}
|
|
529
|
-
const uniqueIndices = [...new Set(indices)];
|
|
530
|
-
if (uniqueIndices.length < 2) return;
|
|
531
|
-
const elements = slide._slideObjects;
|
|
532
|
-
const selected = uniqueIndices.map((idx) => {
|
|
533
|
-
if (typeof idx !== "number" || !Number.isInteger(idx)) {
|
|
534
|
-
throw new Error("Element indices must be integers.");
|
|
535
|
-
}
|
|
536
|
-
if (idx < 0 || idx >= elements.length) {
|
|
537
|
-
throw new Error(
|
|
538
|
-
"Element index out of bounds for distributeSlideElements()."
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
const obj = elements[idx];
|
|
542
|
-
const bounds = getElementBounds(obj);
|
|
543
|
-
return { index: idx, obj, bounds };
|
|
544
|
-
});
|
|
545
|
-
const axisStartKey = direction === "horizontal" ? "x" : "y";
|
|
546
|
-
const axisEndKey = direction === "horizontal" ? "x2" : "y2";
|
|
547
|
-
const sizeKey = direction === "horizontal" ? "w" : "h";
|
|
548
|
-
selected.sort((a, b) => {
|
|
549
|
-
const delta = a.bounds[axisStartKey] - b.bounds[axisStartKey];
|
|
550
|
-
return Math.abs(delta) > 1e-6 ? delta : a.index - b.index;
|
|
551
|
-
});
|
|
552
|
-
const minCoord = Math.min(
|
|
553
|
-
...selected.map((item) => item.bounds[axisStartKey])
|
|
554
|
-
);
|
|
555
|
-
const maxCoord = Math.max(...selected.map((item) => item.bounds[axisEndKey]));
|
|
556
|
-
const totalSpan = maxCoord - minCoord;
|
|
557
|
-
const gaps = selected.length - 1;
|
|
558
|
-
const totalSize = selected.reduce(
|
|
559
|
-
(sum, item) => sum + item.bounds[sizeKey],
|
|
560
|
-
0
|
|
561
|
-
);
|
|
562
|
-
const gapSize = gaps > 0 ? (totalSpan - totalSize) / gaps : 0;
|
|
563
|
-
let cursor = minCoord;
|
|
564
|
-
selected.forEach(({ obj, bounds }) => {
|
|
565
|
-
if (direction === "horizontal") {
|
|
566
|
-
setElementPosition(obj, { x: cursor });
|
|
567
|
-
cursor += bounds.w + gapSize;
|
|
568
|
-
} else {
|
|
569
|
-
setElementPosition(obj, { y: cursor });
|
|
570
|
-
cursor += bounds.h + gapSize;
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function warnIfSlideElementsOutOfBounds(slide, pptx) {
|
|
576
|
-
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
577
|
-
console.warn(
|
|
578
|
-
"Invalid slide object passed to warnIfSlideElementsOutOfBounds()"
|
|
579
|
-
);
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
const {
|
|
583
|
-
width: slideWidth,
|
|
584
|
-
height: slideHeight,
|
|
585
|
-
source,
|
|
586
|
-
} = getSlideDimensions(slide, pptx);
|
|
587
|
-
const slideIndex =
|
|
588
|
-
pptx && Array.isArray(pptx._slides) ? pptx._slides.indexOf(slide) : -1;
|
|
589
|
-
const slideLabel =
|
|
590
|
-
slideIndex >= 0 ? `Slide ${slideIndex + 1}` : "(Unknown slide index)";
|
|
591
|
-
if (source === "default") {
|
|
592
|
-
console.warn(
|
|
593
|
-
`⚠️ ${slideLabel}: Unable to determine slide dimensions from pptxgenjs internals; assuming width=${slideWidth}, height=${slideHeight}.`
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
const EPS = 1e-4;
|
|
597
|
-
let outOfBoundsCount = 0;
|
|
598
|
-
const formatElement = (idx, type, bounds) => {
|
|
599
|
-
const cx = (bounds.x + bounds.w / 2).toFixed(3);
|
|
600
|
-
const cy = (bounds.y + bounds.h / 2).toFixed(3);
|
|
601
|
-
return `Element ${idx} (${type}, center_x=${cx}, center_y=${cy})`;
|
|
602
|
-
};
|
|
603
|
-
slide._slideObjects.forEach((obj, index) => {
|
|
604
|
-
const bounds = getElementBounds(obj);
|
|
605
|
-
const type = inferElementType(obj);
|
|
606
|
-
const violations = [];
|
|
607
|
-
if (bounds.x < -EPS) violations.push(`left=${bounds.x.toFixed(3)} < 0`);
|
|
608
|
-
if (bounds.y < -EPS) violations.push(`top=${bounds.y.toFixed(3)} < 0`);
|
|
609
|
-
if (bounds.x2 > slideWidth + EPS)
|
|
610
|
-
violations.push(
|
|
611
|
-
`right=${bounds.x2.toFixed(3)} > width=${slideWidth.toFixed(3)}`
|
|
612
|
-
);
|
|
613
|
-
if (bounds.y2 > slideHeight + EPS)
|
|
614
|
-
violations.push(
|
|
615
|
-
`bottom=${bounds.y2.toFixed(3)} > height=${slideHeight.toFixed(3)}`
|
|
616
|
-
);
|
|
617
|
-
if (violations.length > 0) {
|
|
618
|
-
outOfBoundsCount++;
|
|
619
|
-
console.warn(
|
|
620
|
-
`⚠️ ${slideLabel}: ${formatElement(
|
|
621
|
-
index,
|
|
622
|
-
type,
|
|
623
|
-
bounds
|
|
624
|
-
)} exceeds slide bounds (${violations.join(", ")}).`
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
if (outOfBoundsCount > 0) {
|
|
629
|
-
console.log(
|
|
630
|
-
`⚠️ ${slideLabel}: Found ${outOfBoundsCount} element(s) extending beyond the slide bounds.`
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
module.exports = {
|
|
636
|
-
inferElementType,
|
|
637
|
-
compareElementPosition,
|
|
638
|
-
warnIfSlideHasOverlaps,
|
|
639
|
-
alignSlideElements,
|
|
640
|
-
distributeSlideElements,
|
|
641
|
-
warnIfSlideElementsOutOfBounds,
|
|
642
|
-
getSlideDimensions,
|
|
643
|
-
};
|
|
1
|
+
// Copyright (c) OpenAI. All rights reserved.
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
function inferElementType(obj) {
|
|
5
|
+
if (!obj) return "unknown";
|
|
6
|
+
const data = obj.data || obj.options || {};
|
|
7
|
+
// Distinguish lines explicitly via type only. Many objects have a 'line' style; don't misclassify those.
|
|
8
|
+
if (obj.type === "line") return "line";
|
|
9
|
+
if (obj.type && typeof obj.type === "string") return obj.type;
|
|
10
|
+
if (obj.text || typeof data.text === "string") return "text";
|
|
11
|
+
if (data.path || obj.image) return "image";
|
|
12
|
+
if (data.chartType) return "chart";
|
|
13
|
+
if (data.shape || data.line) return "shape";
|
|
14
|
+
if (data.mediaType) return "media";
|
|
15
|
+
if (data.table || Array.isArray(data.rows)) return "table";
|
|
16
|
+
if (data.smartArt) return "smartart";
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TEXT_OVERLAP_ERROR_THRESHOLD = 0.1;
|
|
21
|
+
const RECTIFY_DIRECTION_EQUALITY_TOLERANCE = 0.15;
|
|
22
|
+
|
|
23
|
+
function warnIfSlideHasOverlaps(slide, pptx, options = {}) {
|
|
24
|
+
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
25
|
+
console.warn("Invalid slide object passed to warnIfSlideOverlaps()");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const opts = {
|
|
29
|
+
// By default, containment cases are very common (e.g., full-slide backgrounds)
|
|
30
|
+
// and usually not actionable. Mute them unless explicitly requested.
|
|
31
|
+
muteContainment:
|
|
32
|
+
options.muteContainment !== undefined ? options.muteContainment : true,
|
|
33
|
+
// Do NOT ignore lines or decorative shapes by default; users want true overlaps.
|
|
34
|
+
ignoreLines:
|
|
35
|
+
options.ignoreLines !== undefined ? options.ignoreLines : false,
|
|
36
|
+
ignoreDecorativeShapes:
|
|
37
|
+
options.ignoreDecorativeShapes !== undefined
|
|
38
|
+
? options.ignoreDecorativeShapes
|
|
39
|
+
: false,
|
|
40
|
+
};
|
|
41
|
+
const slideIndex =
|
|
42
|
+
pptx && Array.isArray(pptx._slides) ? pptx._slides.indexOf(slide) : -1;
|
|
43
|
+
const slideLabel =
|
|
44
|
+
slideIndex >= 0 ? `Slide ${slideIndex + 1}` : "(Unknown slide index)";
|
|
45
|
+
const formatElement = (el) => {
|
|
46
|
+
const cx = (el.x + el.w / 2).toFixed(3);
|
|
47
|
+
const cy = (el.y + el.h / 2).toFixed(3);
|
|
48
|
+
return `element ${el.index} (${el.type}, center_x=${cx}, center_y=${cy})`;
|
|
49
|
+
};
|
|
50
|
+
const elements = slide._slideObjects.map((obj, i) => {
|
|
51
|
+
const {
|
|
52
|
+
x = 0,
|
|
53
|
+
y = 0,
|
|
54
|
+
w = 0,
|
|
55
|
+
h = 0,
|
|
56
|
+
fill,
|
|
57
|
+
line,
|
|
58
|
+
} = obj.data || obj.options || {};
|
|
59
|
+
const type = inferElementType(obj);
|
|
60
|
+
const isDecorative = (() => {
|
|
61
|
+
if (!opts.ignoreDecorativeShapes) return false;
|
|
62
|
+
// Border rectangles used as frames: transparent fill (or fully transparent) with a stroke
|
|
63
|
+
const transparency =
|
|
64
|
+
typeof fill?.transparency === "number" ? fill.transparency : null;
|
|
65
|
+
const hasOnlyBorder = !!line && (!fill || transparency !== null);
|
|
66
|
+
const fullyTransparent = transparency !== null && transparency >= 99;
|
|
67
|
+
return type === "shape" && hasOnlyBorder && fullyTransparent;
|
|
68
|
+
})();
|
|
69
|
+
const ignorable = (opts.ignoreLines && type === "line") || isDecorative;
|
|
70
|
+
return { index: i, type, x, y, w, h, ignorable };
|
|
71
|
+
});
|
|
72
|
+
let overlapCount = 0;
|
|
73
|
+
let containmentCount = 0;
|
|
74
|
+
for (let i = 0; i < elements.length; i++) {
|
|
75
|
+
const a = elements[i];
|
|
76
|
+
if (a.ignorable) continue;
|
|
77
|
+
for (let j = i + 1; j < elements.length; j++) {
|
|
78
|
+
const b = elements[j];
|
|
79
|
+
if (b.ignorable) continue;
|
|
80
|
+
const comparison = compareElementPosition(slide, a.index, b.index);
|
|
81
|
+
if (comparison.relation === "overlapping") {
|
|
82
|
+
// Special-case: diagonal line's bounding box overlapping a rectangle is often a false positive.
|
|
83
|
+
const EPS = 1e-6;
|
|
84
|
+
const getBounds = (e) => ({
|
|
85
|
+
x: e.x,
|
|
86
|
+
y: e.y,
|
|
87
|
+
x2: e.x + e.w,
|
|
88
|
+
y2: e.y + e.h,
|
|
89
|
+
});
|
|
90
|
+
const lineRectFalsePositive = (() => {
|
|
91
|
+
const oneIsLine = (a.type === "line") ^ (b.type === "line");
|
|
92
|
+
if (!oneIsLine) return false;
|
|
93
|
+
const line = a.type === "line" ? a : b;
|
|
94
|
+
const rect = a.type === "line" ? b : a;
|
|
95
|
+
// If line is diagonal, verify actual segment intersects rect; if not, ignore.
|
|
96
|
+
const isDiagonal = line.w > EPS && line.h > EPS;
|
|
97
|
+
const lineSeg = {
|
|
98
|
+
x1: line.x,
|
|
99
|
+
y1: line.y,
|
|
100
|
+
x2: line.x + line.w,
|
|
101
|
+
y2: line.y + line.h,
|
|
102
|
+
};
|
|
103
|
+
const rectB = getBounds(rect);
|
|
104
|
+
const pointInRect = (px, py, rb) =>
|
|
105
|
+
px >= rb.x - EPS &&
|
|
106
|
+
px <= rb.x2 + EPS &&
|
|
107
|
+
py >= rb.y - EPS &&
|
|
108
|
+
py <= rb.y2 + EPS;
|
|
109
|
+
const segsIntersect = (p1, p2, q1, q2) => {
|
|
110
|
+
const cross = (ax, ay, bx, by) => ax * by - ay * bx;
|
|
111
|
+
const d1x = p2.x - p1.x,
|
|
112
|
+
d1y = p2.y - p1.y;
|
|
113
|
+
const d2x = q2.x - q1.x,
|
|
114
|
+
d2y = q2.y - q1.y;
|
|
115
|
+
const denom = cross(d1x, d1y, d2x, d2y);
|
|
116
|
+
if (Math.abs(denom) < EPS) {
|
|
117
|
+
// Parallel: check colinearity and overlapping projections
|
|
118
|
+
const crossCol = cross(q1.x - p1.x, q1.y - p1.y, d1x, d1y);
|
|
119
|
+
if (Math.abs(crossCol) > EPS) return false;
|
|
120
|
+
const proj = (a, b, c) =>
|
|
121
|
+
Math.min(Math.max(a, b), Math.max(Math.min(a, b), c));
|
|
122
|
+
const overlapX = !(
|
|
123
|
+
Math.max(p1.x, p2.x) < Math.min(q1.x, q2.x) - EPS ||
|
|
124
|
+
Math.max(q1.x, q2.x) < Math.min(p1.x, p2.x) - EPS
|
|
125
|
+
);
|
|
126
|
+
const overlapY = !(
|
|
127
|
+
Math.max(p1.y, p2.y) < Math.min(q1.y, q2.y) - EPS ||
|
|
128
|
+
Math.max(q1.y, q2.y) < Math.min(p1.y, p2.y) - EPS
|
|
129
|
+
);
|
|
130
|
+
return overlapX && overlapY;
|
|
131
|
+
}
|
|
132
|
+
const t = cross(q1.x - p1.x, q1.y - p1.y, d2x, d2y) / denom;
|
|
133
|
+
const u = cross(q1.x - p1.x, q1.y - p1.y, d1x, d1y) / denom;
|
|
134
|
+
return t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS;
|
|
135
|
+
};
|
|
136
|
+
const intersectsRect = (seg, rb) => {
|
|
137
|
+
if (
|
|
138
|
+
pointInRect(seg.x1, seg.y1, rb) ||
|
|
139
|
+
pointInRect(seg.x2, seg.y2, rb)
|
|
140
|
+
)
|
|
141
|
+
return true;
|
|
142
|
+
const r1 = { x: rb.x, y: rb.y },
|
|
143
|
+
r2 = { x: rb.x2, y: rb.y },
|
|
144
|
+
r3 = { x: rb.x2, y: rb.y2 },
|
|
145
|
+
r4 = { x: rb.x, y: rb.y2 };
|
|
146
|
+
const p1 = { x: seg.x1, y: seg.y1 },
|
|
147
|
+
p2 = { x: seg.x2, y: seg.y2 };
|
|
148
|
+
return (
|
|
149
|
+
segsIntersect(p1, p2, r1, r2) ||
|
|
150
|
+
segsIntersect(p1, p2, r2, r3) ||
|
|
151
|
+
segsIntersect(p1, p2, r3, r4) ||
|
|
152
|
+
segsIntersect(p1, p2, r4, r1)
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
return isDiagonal && !intersectsRect(lineSeg, rectB);
|
|
156
|
+
})();
|
|
157
|
+
if (!lineRectFalsePositive) {
|
|
158
|
+
overlapCount++;
|
|
159
|
+
|
|
160
|
+
const severeTextOverlap = (() => {
|
|
161
|
+
if (!comparison.intersection) return false;
|
|
162
|
+
const exceedsThreshold = (element) =>
|
|
163
|
+
element.type === "text" &&
|
|
164
|
+
comparison.intersection.w >= TEXT_OVERLAP_ERROR_THRESHOLD &&
|
|
165
|
+
comparison.intersection.h >= TEXT_OVERLAP_ERROR_THRESHOLD;
|
|
166
|
+
return exceedsThreshold(a) || exceedsThreshold(b);
|
|
167
|
+
})();
|
|
168
|
+
if (severeTextOverlap) {
|
|
169
|
+
const overlapW = comparison.intersection.w;
|
|
170
|
+
const overlapH = comparison.intersection.h;
|
|
171
|
+
let rectificationSuggestion = "";
|
|
172
|
+
if (overlapW > EPS && overlapH > EPS) {
|
|
173
|
+
const maxOverlap = Math.max(overlapW, overlapH);
|
|
174
|
+
const diffRatio = Math.abs(overlapW - overlapH) / maxOverlap;
|
|
175
|
+
const directions = [];
|
|
176
|
+
// Attempt to determine the primary direction of the overlap. This is the direction
|
|
177
|
+
// in which the overlap is smaller (and so requires the smallest adjustment to rectify).
|
|
178
|
+
if (diffRatio <= RECTIFY_DIRECTION_EQUALITY_TOLERANCE) {
|
|
179
|
+
directions.push("horizontally", "vertically");
|
|
180
|
+
} else if (overlapW < overlapH) {
|
|
181
|
+
directions.push("horizontally");
|
|
182
|
+
} else {
|
|
183
|
+
directions.push("vertically");
|
|
184
|
+
}
|
|
185
|
+
rectificationSuggestion = `Suggestion: reposition elements ${directions.join(
|
|
186
|
+
" and "
|
|
187
|
+
)}.`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.error(
|
|
191
|
+
`❌ ${slideLabel}: Severe text overlap detected between ${formatElement(
|
|
192
|
+
a
|
|
193
|
+
)} and ${formatElement(
|
|
194
|
+
b
|
|
195
|
+
)} (overlap_horizontal=${comparison.intersection.w.toFixed(
|
|
196
|
+
3
|
|
197
|
+
)}, overlap_vertical=${comparison.intersection.h.toFixed(
|
|
198
|
+
3
|
|
199
|
+
)}). THIS MUST BE FIXED. ${rectificationSuggestion}`
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
console.warn(
|
|
203
|
+
`⚠️ ${slideLabel}: Overlap detected between ${formatElement(
|
|
204
|
+
a
|
|
205
|
+
)} and ${formatElement(b)}.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} else if (comparison.relation === "contained") {
|
|
210
|
+
if (!opts.muteContainment) {
|
|
211
|
+
containmentCount++;
|
|
212
|
+
const container = elements[comparison.containerIndex];
|
|
213
|
+
const contained = elements[comparison.containedIndex];
|
|
214
|
+
console.warn(
|
|
215
|
+
`⚠️ ${slideLabel}: ${formatElement(
|
|
216
|
+
contained
|
|
217
|
+
)} is fully contained within ${formatElement(container)}`
|
|
218
|
+
);
|
|
219
|
+
} else {
|
|
220
|
+
// Still count internally when muted? We keep for summary only when un-muted
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!(overlapCount === 0 && (!containmentCount || opts.muteContainment))) {
|
|
226
|
+
const issues = [];
|
|
227
|
+
if (overlapCount > 0) issues.push(`${overlapCount} overlapping pair(s)`);
|
|
228
|
+
if (!opts.muteContainment && containmentCount > 0)
|
|
229
|
+
issues.push(`${containmentCount} containment case(s)`);
|
|
230
|
+
console.log(`⚠️ ${slideLabel}: Found ${issues.join(" and ")}.`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function compareElementPosition(slide, firstIndex, secondIndex) {
|
|
235
|
+
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
236
|
+
throw new Error("Invalid slide object passed to compareElementPosition()");
|
|
237
|
+
}
|
|
238
|
+
if (
|
|
239
|
+
typeof firstIndex !== "number" ||
|
|
240
|
+
typeof secondIndex !== "number" ||
|
|
241
|
+
!Number.isInteger(firstIndex) ||
|
|
242
|
+
!Number.isInteger(secondIndex)
|
|
243
|
+
) {
|
|
244
|
+
throw new Error("Element indices must be integer values.");
|
|
245
|
+
}
|
|
246
|
+
const elements = slide._slideObjects;
|
|
247
|
+
if (
|
|
248
|
+
firstIndex < 0 ||
|
|
249
|
+
firstIndex >= elements.length ||
|
|
250
|
+
secondIndex < 0 ||
|
|
251
|
+
secondIndex >= elements.length
|
|
252
|
+
) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Element index out of bounds for compareElementPosition()."
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const EPS = 1e-4;
|
|
258
|
+
const getBounds = (obj) => {
|
|
259
|
+
const source = obj?.data || obj?.options || {};
|
|
260
|
+
let x = typeof source.x === "number" ? source.x : 0;
|
|
261
|
+
let y = typeof source.y === "number" ? source.y : 0;
|
|
262
|
+
let w = typeof source.w === "number" ? source.w : 0;
|
|
263
|
+
let h = typeof source.h === "number" ? source.h : 0;
|
|
264
|
+
if (source.sizing && source.sizing.type === "crop") {
|
|
265
|
+
if (typeof source.sizing.w === "number") w = source.sizing.w;
|
|
266
|
+
if (typeof source.sizing.h === "number") h = source.sizing.h;
|
|
267
|
+
}
|
|
268
|
+
return { x, y, w, h, x2: x + w, y2: y + h };
|
|
269
|
+
};
|
|
270
|
+
const boundsA = getBounds(elements[firstIndex]);
|
|
271
|
+
const boundsB = getBounds(elements[secondIndex]);
|
|
272
|
+
const separated =
|
|
273
|
+
boundsA.x2 < boundsB.x - EPS ||
|
|
274
|
+
boundsB.x2 < boundsA.x - EPS ||
|
|
275
|
+
boundsA.y2 < boundsB.y - EPS ||
|
|
276
|
+
boundsB.y2 < boundsA.y - EPS;
|
|
277
|
+
if (separated) {
|
|
278
|
+
return {
|
|
279
|
+
relation: "disjoint",
|
|
280
|
+
containerIndex: null,
|
|
281
|
+
containedIndex: null,
|
|
282
|
+
aBounds: boundsA,
|
|
283
|
+
bBounds: boundsB,
|
|
284
|
+
intersection: null,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const aContainsB =
|
|
288
|
+
boundsA.x <= boundsB.x + EPS &&
|
|
289
|
+
boundsA.y <= boundsB.y + EPS &&
|
|
290
|
+
boundsA.x2 >= boundsB.x2 - EPS &&
|
|
291
|
+
boundsA.y2 >= boundsB.y2 - EPS;
|
|
292
|
+
const bContainsA =
|
|
293
|
+
boundsB.x <= boundsA.x + EPS &&
|
|
294
|
+
boundsB.y <= boundsA.y + EPS &&
|
|
295
|
+
boundsB.x2 >= boundsA.x2 - EPS &&
|
|
296
|
+
boundsB.y2 >= boundsA.y2 - EPS;
|
|
297
|
+
const ix1 = Math.max(boundsA.x, boundsB.x);
|
|
298
|
+
const iy1 = Math.max(boundsA.y, boundsB.y);
|
|
299
|
+
const ix2 = Math.min(boundsA.x2, boundsB.x2);
|
|
300
|
+
const iy2 = Math.min(boundsA.y2, boundsB.y2);
|
|
301
|
+
const intersectionWidth = Math.max(0, ix2 - ix1);
|
|
302
|
+
const intersectionHeight = Math.max(0, iy2 - iy1);
|
|
303
|
+
const intersection =
|
|
304
|
+
intersectionWidth > EPS && intersectionHeight > EPS
|
|
305
|
+
? { x: ix1, y: iy1, w: intersectionWidth, h: intersectionHeight }
|
|
306
|
+
: null;
|
|
307
|
+
if (aContainsB && !bContainsA) {
|
|
308
|
+
return {
|
|
309
|
+
relation: "contained",
|
|
310
|
+
containerIndex: firstIndex,
|
|
311
|
+
containedIndex: secondIndex,
|
|
312
|
+
aBounds: boundsA,
|
|
313
|
+
bBounds: boundsB,
|
|
314
|
+
intersection,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (bContainsA && !aContainsB) {
|
|
318
|
+
return {
|
|
319
|
+
relation: "contained",
|
|
320
|
+
containerIndex: secondIndex,
|
|
321
|
+
containedIndex: firstIndex,
|
|
322
|
+
aBounds: boundsA,
|
|
323
|
+
bBounds: boundsB,
|
|
324
|
+
intersection,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (intersection) {
|
|
328
|
+
return {
|
|
329
|
+
relation: "overlapping",
|
|
330
|
+
containerIndex: null,
|
|
331
|
+
containedIndex: null,
|
|
332
|
+
aBounds: boundsA,
|
|
333
|
+
bBounds: boundsB,
|
|
334
|
+
intersection,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
relation: "touching",
|
|
339
|
+
containerIndex: null,
|
|
340
|
+
containedIndex: null,
|
|
341
|
+
aBounds: boundsA,
|
|
342
|
+
bBounds: boundsB,
|
|
343
|
+
intersection: null,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const VALID_ALIGNMENTS = new Set([
|
|
348
|
+
"left",
|
|
349
|
+
"right",
|
|
350
|
+
"top",
|
|
351
|
+
"bottom",
|
|
352
|
+
"verticallyCenter",
|
|
353
|
+
"horizontallyCenter",
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
const getElementBounds = (obj) => {
|
|
357
|
+
const source = obj?.data || obj?.options || {};
|
|
358
|
+
let x = typeof source.x === "number" ? source.x : 0;
|
|
359
|
+
let y = typeof source.y === "number" ? source.y : 0;
|
|
360
|
+
let w = typeof source.w === "number" ? source.w : 0;
|
|
361
|
+
let h = typeof source.h === "number" ? source.h : 0;
|
|
362
|
+
// If an image is placed with crop sizing, pptxgenjs stores a larger virtual image w/h
|
|
363
|
+
// and a viewport in source.sizing.{w,h}. For visual overlap purposes, use the viewport.
|
|
364
|
+
if (source.sizing && source.sizing.type === "crop") {
|
|
365
|
+
if (typeof source.sizing.w === "number") w = source.sizing.w;
|
|
366
|
+
if (typeof source.sizing.h === "number") h = source.sizing.h;
|
|
367
|
+
}
|
|
368
|
+
return { x, y, w, h, x2: x + w, y2: y + h };
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const setElementPosition = (obj, coords) => {
|
|
372
|
+
const ensureTarget = (targetObj) => {
|
|
373
|
+
if (!targetObj || typeof targetObj !== "object") return null;
|
|
374
|
+
return targetObj;
|
|
375
|
+
};
|
|
376
|
+
const targets = [];
|
|
377
|
+
const dataTarget = ensureTarget(obj.data);
|
|
378
|
+
if (dataTarget) targets.push(dataTarget);
|
|
379
|
+
const optionsTarget =
|
|
380
|
+
obj.options && obj.options !== obj.data ? ensureTarget(obj.options) : null;
|
|
381
|
+
if (optionsTarget) targets.push(optionsTarget);
|
|
382
|
+
if (targets.length === 0) {
|
|
383
|
+
obj.data = obj.data && typeof obj.data === "object" ? obj.data : {};
|
|
384
|
+
targets.push(obj.data);
|
|
385
|
+
}
|
|
386
|
+
targets.forEach((target) => {
|
|
387
|
+
if (coords.x !== undefined) target.x = coords.x;
|
|
388
|
+
if (coords.y !== undefined) target.y = coords.y;
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const dimensionKeyPairs = [
|
|
393
|
+
["width", "height"],
|
|
394
|
+
["w", "h"],
|
|
395
|
+
["cx", "cy"],
|
|
396
|
+
["slideWidth", "slideHeight"],
|
|
397
|
+
["slideWidthInches", "slideHeightInches"],
|
|
398
|
+
["widthInches", "heightInches"],
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
const toNumber = (value) => {
|
|
402
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
403
|
+
if (typeof value === "string") {
|
|
404
|
+
const parsed = parseFloat(value);
|
|
405
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const readDimensionsFromObject = (candidate, seen = new Set()) => {
|
|
411
|
+
if (!candidate || typeof candidate !== "object") return null;
|
|
412
|
+
if (seen.has(candidate)) return null;
|
|
413
|
+
seen.add(candidate);
|
|
414
|
+
for (const [wKey, hKey] of dimensionKeyPairs) {
|
|
415
|
+
const width = toNumber(candidate[wKey]);
|
|
416
|
+
const height = toNumber(candidate[hKey]);
|
|
417
|
+
if (width !== null && height !== null && width > 0 && height > 0) {
|
|
418
|
+
return { width, height };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const nestedKeys = ["size", "slideSize", "layout", "slideLayout"];
|
|
422
|
+
for (const key of nestedKeys) {
|
|
423
|
+
const nested = readDimensionsFromObject(candidate[key], seen);
|
|
424
|
+
if (nested) return nested;
|
|
425
|
+
}
|
|
426
|
+
return null;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const getSlideDimensions = (slide, pptx) => {
|
|
430
|
+
const candidates = [
|
|
431
|
+
slide?._presLayout,
|
|
432
|
+
slide?._slideLayout,
|
|
433
|
+
slide?._pres?.layout,
|
|
434
|
+
slide?._parent?.layout,
|
|
435
|
+
slide?._layout,
|
|
436
|
+
pptx?._presLayout,
|
|
437
|
+
pptx?._layout,
|
|
438
|
+
pptx?.layout,
|
|
439
|
+
pptx?.presLayout,
|
|
440
|
+
];
|
|
441
|
+
for (const candidate of candidates) {
|
|
442
|
+
const dims = readDimensionsFromObject(candidate);
|
|
443
|
+
if (dims) {
|
|
444
|
+
// Some internals are in EMUs; convert if values look too large for inches
|
|
445
|
+
const EMU_PER_IN = 914400;
|
|
446
|
+
const looksEmu = dims.width > 1000 || dims.height > 1000;
|
|
447
|
+
if (looksEmu) {
|
|
448
|
+
return {
|
|
449
|
+
width: dims.width / EMU_PER_IN,
|
|
450
|
+
height: dims.height / EMU_PER_IN,
|
|
451
|
+
source: "emu_converted",
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return { ...dims, source: "detected" };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
throw new Error(
|
|
458
|
+
"getSlideDimensions(): Unable to determine slide dimensions from pptxgenjs internals."
|
|
459
|
+
);
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
function alignSlideElements(slide, indices, alignment) {
|
|
463
|
+
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
464
|
+
throw new Error("Invalid slide object passed to alignSlideElements()");
|
|
465
|
+
}
|
|
466
|
+
if (!Array.isArray(indices) || indices.length === 0) {
|
|
467
|
+
throw new Error("indices must be a non-empty array.");
|
|
468
|
+
}
|
|
469
|
+
if (!VALID_ALIGNMENTS.has(alignment)) {
|
|
470
|
+
throw new Error(`Unsupported alignment option: ${alignment}`);
|
|
471
|
+
}
|
|
472
|
+
const uniqueIndices = [...new Set(indices)];
|
|
473
|
+
const elements = slide._slideObjects;
|
|
474
|
+
const selected = uniqueIndices.map((idx) => {
|
|
475
|
+
if (typeof idx !== "number" || !Number.isInteger(idx)) {
|
|
476
|
+
throw new Error("Element indices must be integers.");
|
|
477
|
+
}
|
|
478
|
+
if (idx < 0 || idx >= elements.length) {
|
|
479
|
+
throw new Error("Element index out of bounds for alignSlideElements().");
|
|
480
|
+
}
|
|
481
|
+
const obj = elements[idx];
|
|
482
|
+
const bounds = getElementBounds(obj);
|
|
483
|
+
return { index: idx, obj, bounds };
|
|
484
|
+
});
|
|
485
|
+
if (selected.length < 2) return;
|
|
486
|
+
const minX = Math.min(...selected.map((item) => item.bounds.x));
|
|
487
|
+
const maxX2 = Math.max(...selected.map((item) => item.bounds.x2));
|
|
488
|
+
const minY = Math.min(...selected.map((item) => item.bounds.y));
|
|
489
|
+
const maxY2 = Math.max(...selected.map((item) => item.bounds.y2));
|
|
490
|
+
const centerX = (minX + maxX2) / 2;
|
|
491
|
+
const centerY = (minY + maxY2) / 2;
|
|
492
|
+
selected.forEach(({ obj, bounds }) => {
|
|
493
|
+
const { w, h } = bounds;
|
|
494
|
+
switch (alignment) {
|
|
495
|
+
case "left":
|
|
496
|
+
setElementPosition(obj, { x: minX });
|
|
497
|
+
break;
|
|
498
|
+
case "right":
|
|
499
|
+
setElementPosition(obj, { x: maxX2 - w });
|
|
500
|
+
break;
|
|
501
|
+
case "top":
|
|
502
|
+
setElementPosition(obj, { y: minY });
|
|
503
|
+
break;
|
|
504
|
+
case "bottom":
|
|
505
|
+
setElementPosition(obj, { y: maxY2 - h });
|
|
506
|
+
break;
|
|
507
|
+
case "horizontallyCenter":
|
|
508
|
+
setElementPosition(obj, { x: centerX - w / 2 });
|
|
509
|
+
break;
|
|
510
|
+
case "verticallyCenter":
|
|
511
|
+
setElementPosition(obj, { y: centerY - h / 2 });
|
|
512
|
+
break;
|
|
513
|
+
default:
|
|
514
|
+
throw new Error(`Unhandled alignment option: ${alignment}`);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function distributeSlideElements(slide, indices, direction) {
|
|
520
|
+
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
521
|
+
throw new Error("Invalid slide object passed to distributeSlideElements()");
|
|
522
|
+
}
|
|
523
|
+
if (!Array.isArray(indices) || indices.length === 0) {
|
|
524
|
+
throw new Error("indices must be a non-empty array.");
|
|
525
|
+
}
|
|
526
|
+
if (direction !== "horizontal" && direction !== "vertical") {
|
|
527
|
+
throw new Error(`Unsupported distribution direction: ${direction}`);
|
|
528
|
+
}
|
|
529
|
+
const uniqueIndices = [...new Set(indices)];
|
|
530
|
+
if (uniqueIndices.length < 2) return;
|
|
531
|
+
const elements = slide._slideObjects;
|
|
532
|
+
const selected = uniqueIndices.map((idx) => {
|
|
533
|
+
if (typeof idx !== "number" || !Number.isInteger(idx)) {
|
|
534
|
+
throw new Error("Element indices must be integers.");
|
|
535
|
+
}
|
|
536
|
+
if (idx < 0 || idx >= elements.length) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
"Element index out of bounds for distributeSlideElements()."
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
const obj = elements[idx];
|
|
542
|
+
const bounds = getElementBounds(obj);
|
|
543
|
+
return { index: idx, obj, bounds };
|
|
544
|
+
});
|
|
545
|
+
const axisStartKey = direction === "horizontal" ? "x" : "y";
|
|
546
|
+
const axisEndKey = direction === "horizontal" ? "x2" : "y2";
|
|
547
|
+
const sizeKey = direction === "horizontal" ? "w" : "h";
|
|
548
|
+
selected.sort((a, b) => {
|
|
549
|
+
const delta = a.bounds[axisStartKey] - b.bounds[axisStartKey];
|
|
550
|
+
return Math.abs(delta) > 1e-6 ? delta : a.index - b.index;
|
|
551
|
+
});
|
|
552
|
+
const minCoord = Math.min(
|
|
553
|
+
...selected.map((item) => item.bounds[axisStartKey])
|
|
554
|
+
);
|
|
555
|
+
const maxCoord = Math.max(...selected.map((item) => item.bounds[axisEndKey]));
|
|
556
|
+
const totalSpan = maxCoord - minCoord;
|
|
557
|
+
const gaps = selected.length - 1;
|
|
558
|
+
const totalSize = selected.reduce(
|
|
559
|
+
(sum, item) => sum + item.bounds[sizeKey],
|
|
560
|
+
0
|
|
561
|
+
);
|
|
562
|
+
const gapSize = gaps > 0 ? (totalSpan - totalSize) / gaps : 0;
|
|
563
|
+
let cursor = minCoord;
|
|
564
|
+
selected.forEach(({ obj, bounds }) => {
|
|
565
|
+
if (direction === "horizontal") {
|
|
566
|
+
setElementPosition(obj, { x: cursor });
|
|
567
|
+
cursor += bounds.w + gapSize;
|
|
568
|
+
} else {
|
|
569
|
+
setElementPosition(obj, { y: cursor });
|
|
570
|
+
cursor += bounds.h + gapSize;
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function warnIfSlideElementsOutOfBounds(slide, pptx) {
|
|
576
|
+
if (!slide || !Array.isArray(slide._slideObjects)) {
|
|
577
|
+
console.warn(
|
|
578
|
+
"Invalid slide object passed to warnIfSlideElementsOutOfBounds()"
|
|
579
|
+
);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const {
|
|
583
|
+
width: slideWidth,
|
|
584
|
+
height: slideHeight,
|
|
585
|
+
source,
|
|
586
|
+
} = getSlideDimensions(slide, pptx);
|
|
587
|
+
const slideIndex =
|
|
588
|
+
pptx && Array.isArray(pptx._slides) ? pptx._slides.indexOf(slide) : -1;
|
|
589
|
+
const slideLabel =
|
|
590
|
+
slideIndex >= 0 ? `Slide ${slideIndex + 1}` : "(Unknown slide index)";
|
|
591
|
+
if (source === "default") {
|
|
592
|
+
console.warn(
|
|
593
|
+
`⚠️ ${slideLabel}: Unable to determine slide dimensions from pptxgenjs internals; assuming width=${slideWidth}, height=${slideHeight}.`
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
const EPS = 1e-4;
|
|
597
|
+
let outOfBoundsCount = 0;
|
|
598
|
+
const formatElement = (idx, type, bounds) => {
|
|
599
|
+
const cx = (bounds.x + bounds.w / 2).toFixed(3);
|
|
600
|
+
const cy = (bounds.y + bounds.h / 2).toFixed(3);
|
|
601
|
+
return `Element ${idx} (${type}, center_x=${cx}, center_y=${cy})`;
|
|
602
|
+
};
|
|
603
|
+
slide._slideObjects.forEach((obj, index) => {
|
|
604
|
+
const bounds = getElementBounds(obj);
|
|
605
|
+
const type = inferElementType(obj);
|
|
606
|
+
const violations = [];
|
|
607
|
+
if (bounds.x < -EPS) violations.push(`left=${bounds.x.toFixed(3)} < 0`);
|
|
608
|
+
if (bounds.y < -EPS) violations.push(`top=${bounds.y.toFixed(3)} < 0`);
|
|
609
|
+
if (bounds.x2 > slideWidth + EPS)
|
|
610
|
+
violations.push(
|
|
611
|
+
`right=${bounds.x2.toFixed(3)} > width=${slideWidth.toFixed(3)}`
|
|
612
|
+
);
|
|
613
|
+
if (bounds.y2 > slideHeight + EPS)
|
|
614
|
+
violations.push(
|
|
615
|
+
`bottom=${bounds.y2.toFixed(3)} > height=${slideHeight.toFixed(3)}`
|
|
616
|
+
);
|
|
617
|
+
if (violations.length > 0) {
|
|
618
|
+
outOfBoundsCount++;
|
|
619
|
+
console.warn(
|
|
620
|
+
`⚠️ ${slideLabel}: ${formatElement(
|
|
621
|
+
index,
|
|
622
|
+
type,
|
|
623
|
+
bounds
|
|
624
|
+
)} exceeds slide bounds (${violations.join(", ")}).`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
if (outOfBoundsCount > 0) {
|
|
629
|
+
console.log(
|
|
630
|
+
`⚠️ ${slideLabel}: Found ${outOfBoundsCount} element(s) extending beyond the slide bounds.`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
module.exports = {
|
|
636
|
+
inferElementType,
|
|
637
|
+
compareElementPosition,
|
|
638
|
+
warnIfSlideHasOverlaps,
|
|
639
|
+
alignSlideElements,
|
|
640
|
+
distributeSlideElements,
|
|
641
|
+
warnIfSlideElementsOutOfBounds,
|
|
642
|
+
getSlideDimensions,
|
|
643
|
+
};
|