android2harmony 0.1.3 → 0.1.5
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/README.md +1 -407
- package/agents/self-tester.md +376 -339
- package/dist/index.js +131 -586
- package/dist/index.js.map +4 -4
- package/package.json +36 -37
- package/skills/a2h-resource-convert/SKILL.md +902 -0
- package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
- package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
- package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2166 -0
- package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
- package/skills/a2h-resource-convert/scripts/package.json +3 -0
- package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
- package/skills/a2h-ui-transfer/SKILL.md +420 -0
- package/skills/a2h-ui-transfer/references/conversion-procedure.md +572 -0
- package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
- package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
- package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +397 -283
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
- package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
- package/skills/a2h-ui-transfer/scripts/package.json +3 -0
- package/skills/hmos-incremental-ui-align/README.md +251 -251
- package/skills/hmos-incremental-ui-align/SKILL.md +364 -364
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
- package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
- package/skills/hmos-batch-ui-align/SKILL.md +0 -141
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
- package/skills/hmos-resources-convert/SKILL.md +0 -654
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
- package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
- package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
- package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
- package/tools/autotest/deps/autotest-agent-0.1.1.tgz +0 -0
- package/tools/autotest/engine/batch-launcher.ts +0 -326
- package/tools/autotest/engine/report-tool.ts +0 -773
- package/tools/autotest/engine/self-test-runner.ts +0 -1024
- package/tools/autotest/engine/testcases-tool.ts +0 -246
- package/tools/autotest/resolve-metadata-tool.ts +0 -143
- package/tools/autotest/runner/logger.ts +0 -45
- package/tools/autotest/runner/process-utils.ts +0 -41
- package/tools/autotest/validate.ts +0 -115
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md
RENAMED
|
@@ -310,6 +310,17 @@ Recommended fallback in `notes`:
|
|
|
310
310
|
- `Original Android type: raw`
|
|
311
311
|
- `Original Android type: menu`
|
|
312
312
|
|
|
313
|
+
### 8.4 Lottie Animation Special Case
|
|
314
|
+
|
|
315
|
+
Lottie JSON files live under Android `assets/` (passthrough) or `res/raw/` (raw type). Both normalize to HarmonyOS `rawfile/`. Record them with `type_category = raw` and identify the Lottie subtype in `notes` (`Lottie animation JSON`). Do NOT invent a new top-level `lottie` type category.
|
|
316
|
+
|
|
317
|
+
For each Lottie row:
|
|
318
|
+
- `source_category` follows the normal rules. Local Lottie under app/module `assets/` or `res/raw/` → `应用自身资源` or `项目内模块资源`. Runtime URL (`lottie_url` or code loading a `Uri`) → `运行时远程资源`.
|
|
319
|
+
- `screens` MUST identify the Activity/Fragment that plays the animation, using the evidence sources listed in `references/lottie-conversion-rules.md` (XML host layout → hosting Activity/Fragment, factory-call enclosing class, or picker navigation target). The `Screens` value is the Android host; the proposed HarmonyOS page name goes into `notes` as `page=<PageName>` (or into an optional `Suggested HarmonyOS Page` column if the mapping template exposes one).
|
|
320
|
+
- `function` should describe the animation's role, e.g. `Refresh loading animation`, `Splash intro animation`, `Wave background animation`.
|
|
321
|
+
|
|
322
|
+
See `references/lottie-conversion-rules.md` for the complete Lottie handling rules.
|
|
323
|
+
|
|
313
324
|
---
|
|
314
325
|
|
|
315
326
|
## 9. Mapping Kind Rules
|
|
@@ -321,6 +332,7 @@ Suggested `mapping_kind` values:
|
|
|
321
332
|
- `layered-image conversion`
|
|
322
333
|
- `generated media`
|
|
323
334
|
- `merged into json`
|
|
335
|
+
- `lottie migration` — Lottie JSON copied from Android `assets/` or `res/raw/` to `rawfile/lottie/<normalized>.json`. Use in place of plain `direct copy` for Lottie assets so the row is easy to filter.
|
|
324
336
|
- `unmappable`
|
|
325
337
|
- `fallback applied`
|
|
326
338
|
- `placeholder created`
|
|
@@ -336,7 +348,7 @@ Use the simplest accurate label.
|
|
|
336
348
|
### 10.1 One-to-One
|
|
337
349
|
|
|
338
350
|
Example:
|
|
339
|
-
- `res/drawable/home_login_btn.png` →
|
|
351
|
+
- `res/drawable/home_login_btn.png` → `<ui_module>/src/main/resources/base/media/home_login_btn.png`
|
|
340
352
|
|
|
341
353
|
Emit one mapping row.
|
|
342
354
|
|
|
@@ -389,7 +401,7 @@ Include bullets for:
|
|
|
389
401
|
- `harmony_project_dir` (HarmonyOS project output path)
|
|
390
402
|
- Resource source
|
|
391
403
|
- Resource source path
|
|
392
|
-
-
|
|
404
|
+
- Decode result
|
|
393
405
|
- Generation timestamp
|
|
394
406
|
|
|
395
407
|
### 11.3 Android Resource Inventory
|
|
@@ -425,6 +437,19 @@ Include a compact summary table or grouped bullets covering:
|
|
|
425
437
|
- library-only resources inferred from dependencies or APK
|
|
426
438
|
- runtime remote resources
|
|
427
439
|
|
|
440
|
+
### 11.5.1 Lottie Animation Table
|
|
441
|
+
|
|
442
|
+
If the project contains any Lottie animation JSONs (identified by structure signature per `references/lottie-conversion-rules.md`), include a dedicated table listing every detected animation. Required columns:
|
|
443
|
+
|
|
444
|
+
- `Android Source` — full path (e.g., `assets/AndroidWave.json`, `res/raw/lottielogo.json`, or `(remote) https://...`)
|
|
445
|
+
- `Target Rawfile Path` — `rawfile/lottie/<normalized>.json` or `N/A`
|
|
446
|
+
- `Host Activity/Fragment` — the Android class that plays the animation (or `Global` / `Launcher` / `Unknown`)
|
|
447
|
+
- `Host Layout / Loader` — the layout XML with `lottie_fileName` / `lottie_rawRes`, or the Kotlin/Java call site
|
|
448
|
+
- `Suggested HarmonyOS Page` — proposed ArkUI page name (e.g., `PlayerPage`)
|
|
449
|
+
- `Notes` — rename records, collision disambiguation, remote URL, or "no static references found"
|
|
450
|
+
|
|
451
|
+
This is the audit surface used by the HarmonyOS developer to wire `@ohos/lottie` calls to the correct pages. Never omit a detected Lottie asset from this table.
|
|
452
|
+
|
|
428
453
|
### 11.6 Quick Findings
|
|
429
454
|
|
|
430
455
|
Provide short bullets highlighting review-relevant findings, such as:
|
|
@@ -161,6 +161,65 @@ Only include transform components that differ from defaults (translate 0,0; scal
|
|
|
161
161
|
|
|
162
162
|
Generate unique IDs for clip paths (e.g., `clip_1`, `clip_2`, or derive from `android:name`).
|
|
163
163
|
|
|
164
|
+
#### `clip-path` is geometry, not decoration — it is mandatory
|
|
165
|
+
|
|
166
|
+
A `<clip-path>` changes the **position, size, and visible extent** of every sibling path in its
|
|
167
|
+
group. Dropping it does not degrade the icon slightly; it renders a different shape. Two forms
|
|
168
|
+
both occur in real icon sets and **both must be preserved**:
|
|
169
|
+
|
|
170
|
+
| Form | Example | Effect if dropped |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| Full-viewport clip | `M0 0hWvHz` where `W`/`H` equal `viewportWidth`/`viewportHeight` | Visually equivalent, but do not use "it looks full-size" as licence to skip clips in general — verify the numbers first |
|
|
173
|
+
| **Sub-region clip** | clip origin or extent differs from the viewport | Geometry breaks. Two sub-cases are especially damaging: (a) the artwork occupies only part of the viewport, so the icon lands at the wrong offset and scale; (b) the path deliberately **floods the whole viewport** and relies on the clip to cut the visible figure out of it — dropping the clip yields a solid filled rectangle |
|
|
174
|
+
|
|
175
|
+
Case (b) is worth calling out because the path data alone looks like a plain background
|
|
176
|
+
rectangle; nothing about the `<path>` element hints that a clip is load-bearing. Never decide a
|
|
177
|
+
clip is redundant by reading the path — compare the clip's bounds against the viewport.
|
|
178
|
+
|
|
179
|
+
**Self-check after emitting each `<vector>`-derived SVG** (do not defer this to a later step):
|
|
180
|
+
|
|
181
|
+
1. count of `<clip-path>` in the Android source == count of `<clipPath>` in the SVG
|
|
182
|
+
2. every path that sat inside a clipped `<group>` carries `clip-path="url(#…)"`
|
|
183
|
+
3. each `clipPath` `id` referenced actually exists in `<defs>`
|
|
184
|
+
|
|
185
|
+
If any check fails, fix the SVG before moving to the next file.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Vector Fidelity Checklist
|
|
190
|
+
|
|
191
|
+
Run this per `<vector>`-derived SVG before considering the file converted. It is the `<vector>`
|
|
192
|
+
counterpart to the ShapeDrawable rules below, and it exists because **a file that exists and
|
|
193
|
+
parses can still be visually wrong** — existence checks cannot catch any of the items here.
|
|
194
|
+
|
|
195
|
+
| # | Check | Failure signature |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| 1 | `clip-path` count preserved; clipped paths reference their `clipPath` | Icon offset/scaled wrong, or a solid rectangle where a figure was expected |
|
|
198
|
+
| 2 | `fill` follows the three-branch rule (`fill="none"` for stroke-only paths) | Outline icon renders as a solid blob |
|
|
199
|
+
| 3 | All `stroke*` attributes carried over (`stroke`, `stroke-width`, `-linecap`, `-linejoin`, `-miterlimit`, `-opacity`) | Outline too thin/thick, or corners/caps visibly wrong |
|
|
200
|
+
| 4 | `viewportWidth`/`viewportHeight` == `viewBox="0 0 W H"`; `width`/`height` carried from `android:width/height` | Whole icon scaled or cropped |
|
|
201
|
+
| 5 | `<group>` transforms emitted in order translate → scale → rotate, with pivot preserved | Artwork rotated about the wrong point or displaced |
|
|
202
|
+
| 6 | `fillType="evenOdd"` → `fill-rule="evenodd"` | Holes in the shape fill in (donuts become discs) |
|
|
203
|
+
| 7 | `#AARRGGBB` split into color + `fill-opacity`/`stroke-opacity` | Opaque where it should be translucent |
|
|
204
|
+
| 8 | Inline `<aapt:attr name="android:fillColor"><gradient>` → `<defs><linearGradient\|radialGradient>` + `fill="url(#id)"` | Gradient collapses to a flat color — and because the `<path>` carries no `fillColor` *attribute*, a converter that ignores `aapt:attr` typically falls back to the black default, so the shape turns solid black |
|
|
205
|
+
|
|
206
|
+
Item 8 deserves attention because the gradient lives in a **child element**, not an attribute. Any
|
|
207
|
+
converter that only reads `<path>` attributes sees a path with no `fillColor` and takes the default
|
|
208
|
+
branch. Treat `<aapt:attr>` children as a fill source on equal footing with the attribute form.
|
|
209
|
+
|
|
210
|
+
Item 8 has a second form that only shows up in a **compiled** APK, which is exactly what this skill
|
|
211
|
+
converts. At build time aapt hoists the inline `<gradient>` out into its own drawable resource and
|
|
212
|
+
leaves behind an ordinary attribute: `android:fillColor="@drawable/$icon__0"`, decoding to a file
|
|
213
|
+
`res/drawable*/_icon__0.xml` whose root is `<gradient>` (`$` is written as `_`). Here the attribute
|
|
214
|
+
*is* present, so the `aapt:attr` branch never fires; the reference simply fails to resolve as a color
|
|
215
|
+
and the path falls back to black. Follow the reference into the drawable and build the gradient from
|
|
216
|
+
it — search the unqualified `drawable/` first, then any `drawable-*` directory. When the hoisted
|
|
217
|
+
gradient's own stops are unresolvable (framework dynamic colors such as `@android:0106xxxx`), the
|
|
218
|
+
black fallback is correct, but it must still be recorded as an unresolved paint.
|
|
219
|
+
|
|
220
|
+
Record any file that fails a check in the conversion report rather than silently re-emitting it,
|
|
221
|
+
so the same defect is not reintroduced on the next run.
|
|
222
|
+
|
|
164
223
|
---
|
|
165
224
|
|
|
166
225
|
## ShapeDrawable Conversion
|
|
@@ -488,10 +547,68 @@ Convert the gradient to an SVG `<linearGradient>` in `<defs>` and reference via
|
|
|
488
547
|
### Default Values
|
|
489
548
|
|
|
490
549
|
If an Android drawable doesn't specify certain attributes, use these defaults:
|
|
491
|
-
- `fillColor`: `#000000` (black) for paths in `<vector>`, transparent for shapes without `<solid>`
|
|
492
550
|
- `strokeWidth`: `0` (no stroke)
|
|
493
551
|
- `alpha`: `1.0`
|
|
494
552
|
- `fillType`: `nonZero`
|
|
553
|
+
- `fillColor`: see the three-branch rule below — **never blanket-default it to black**
|
|
554
|
+
|
|
555
|
+
#### `fill` on `<path>`: three branches, decided per path
|
|
556
|
+
|
|
557
|
+
An omitted `android:fillColor` does **not** always mean black. Because SVG's own `fill`
|
|
558
|
+
default *is* black, writing `fill="#000000"` where Android meant "no fill" silently turns a
|
|
559
|
+
stroke-only outline into a solid black blob. Decide per `<path>`:
|
|
560
|
+
|
|
561
|
+
| Android `<path>` has | Emit | Why |
|
|
562
|
+
|---|---|---|
|
|
563
|
+
| `fillColor` (with or without `strokeColor`) | `fill="<converted color>"` | Explicit fill; convert per the color-format table |
|
|
564
|
+
| **no `fillColor`, but has `strokeColor`** | **`fill="none"`** (required, explicit) | Stroke-only geometry. Omitting `fill` — or worse, emitting `fill="#000000"` — fills the outline solid |
|
|
565
|
+
| neither `fillColor` nor `strokeColor` | `fill="#000000"` | Matches the Android platform default for an otherwise unpainted path |
|
|
566
|
+
|
|
567
|
+
Rule of thumb: in SVG, *omitting* `fill` and *writing black* are equivalent, so `fill="none"`
|
|
568
|
+
must be emitted **explicitly** for stroke-only paths. This applies per path, not per file — one
|
|
569
|
+
`<vector>` may legitimately mix filled and stroke-only paths.
|
|
570
|
+
|
|
571
|
+
Icons built as "white/colored outline on transparent background" are the common case that this
|
|
572
|
+
branch protects; they are widespread in Material-style icon sets, so a blanket black default
|
|
573
|
+
tends to corrupt many files at once rather than just one.
|
|
574
|
+
|
|
575
|
+
**MANDATORY STROKE-ONLY DETECTION (runtime, not static check):**
|
|
576
|
+
|
|
577
|
+
After converting each `<vector>` to SVG, run this detection **before writing the file**:
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
function checkStrokeOnlyPaths(svgContent, filename) {
|
|
581
|
+
// Parse all <path> elements
|
|
582
|
+
const pathRegex = /<path[^>]*>/g;
|
|
583
|
+
const paths = svgContent.match(pathRegex) || [];
|
|
584
|
+
|
|
585
|
+
for (const pathTag of paths) {
|
|
586
|
+
const hasStroke = /stroke=["'](?!none)[^"']+/.test(pathTag);
|
|
587
|
+
const hasFill = /fill=["'](?!none)[^"']+/.test(pathTag);
|
|
588
|
+
|
|
589
|
+
// ERROR: stroke-only path without explicit fill="none"
|
|
590
|
+
if (hasStroke && !hasFill) {
|
|
591
|
+
return {
|
|
592
|
+
severity: 'error',
|
|
593
|
+
file: filename,
|
|
594
|
+
issue: 'STROKE_ONLY_WITHOUT_FILL_NONE',
|
|
595
|
+
evidence: `Path has stroke="${pathTag.match(/stroke=["']([^"']+)/)?.[1]}" but no fill attribute`,
|
|
596
|
+
fix: 'Add fill="none" explicitly to this <path> element'
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return null; // Pass
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
**Why this check is mandatory:**
|
|
606
|
+
- A stroke-only path *without* `fill="none"` inherits SVG's default `fill="black"`, rendering as a solid blob instead of an outline
|
|
607
|
+
- This defect is invisible to existence checks (the file exists and parses correctly)
|
|
608
|
+
- It only surfaces at runtime when the icon renders incorrectly
|
|
609
|
+
- The three-branch rule above states the requirement; this check enforces it mechanically
|
|
610
|
+
|
|
611
|
+
**Integration point:** This check must run in the same loop that generates the SVG files, not as a separate pass. If it returns an error object, DO NOT write that SVG file — fix the conversion and re-check until it returns `null`.
|
|
495
612
|
|
|
496
613
|
---
|
|
497
614
|
|