@glassly/engine 0.3.7-dev.906 → 0.3.7-dev.910

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/build/generated/releaseMetadata.js +3 -3
  2. package/build/generated/releaseMetadata.js.map +1 -1
  3. package/build/services/AppRegistry.d.ts.map +1 -1
  4. package/build/services/AppRegistry.js +29 -6
  5. package/build/services/AppRegistry.js.map +1 -1
  6. package/build/services/DisplayProcessor.d.ts +2 -0
  7. package/build/services/DisplayProcessor.d.ts.map +1 -1
  8. package/build/services/DisplayProcessor.js +13 -1
  9. package/build/services/DisplayProcessor.js.map +1 -1
  10. package/build/services/JSCrashloopReportService.d.ts.map +1 -1
  11. package/build/services/JSCrashloopReportService.js +5 -0
  12. package/build/services/JSCrashloopReportService.js.map +1 -1
  13. package/build/services/JSRouter.d.ts.map +1 -1
  14. package/build/services/JSRouter.js +6 -1
  15. package/build/services/JSRouter.js.map +1 -1
  16. package/build/services/SceneRenderer.js +1 -1
  17. package/build/services/SceneRenderer.js.map +1 -1
  18. package/build/stores/apps.d.ts.map +1 -1
  19. package/build/stores/apps.js +11 -2
  20. package/build/stores/apps.js.map +1 -1
  21. package/build/stores/settings.d.ts.map +1 -1
  22. package/build/stores/settings.js +10 -0
  23. package/build/stores/settings.js.map +1 -1
  24. package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
  25. package/build/types/capabilities/even-realities-g1.js +3 -0
  26. package/build/types/capabilities/even-realities-g1.js.map +1 -1
  27. package/build/utils/display/index.d.ts +1 -1
  28. package/build/utils/display/index.d.ts.map +1 -1
  29. package/build/utils/display/index.js +2 -2
  30. package/build/utils/display/index.js.map +1 -1
  31. package/build/utils/display/profiles/index.d.ts +1 -1
  32. package/build/utils/display/profiles/index.d.ts.map +1 -1
  33. package/build/utils/display/profiles/index.js +2 -2
  34. package/build/utils/display/profiles/index.js.map +1 -1
  35. package/build/utils/display/profiles/rayneo-io.d.ts +1 -1
  36. package/build/utils/display/profiles/rayneo-io.d.ts.map +1 -1
  37. package/build/utils/display/profiles/rayneo-io.js +1 -1
  38. package/build/utils/display/profiles/rayneo-io.js.map +1 -1
  39. package/build/utils/display/scene/index.d.ts +1 -1
  40. package/build/utils/display/scene/index.d.ts.map +1 -1
  41. package/build/utils/display/scene/index.js +1 -1
  42. package/build/utils/display/scene/index.js.map +1 -1
  43. package/build/utils/display/scene/process.d.ts +6 -0
  44. package/build/utils/display/scene/process.d.ts.map +1 -1
  45. package/build/utils/display/scene/process.js +22 -7
  46. package/build/utils/display/scene/process.js.map +1 -1
  47. package/package.json +7 -7
  48. package/src/generated/releaseMetadata.ts +3 -3
  49. package/src/services/AppRegistry.ts +28 -6
  50. package/src/services/DisplayProcessor.ts +15 -0
  51. package/src/services/JSCrashloopReportService.ts +5 -0
  52. package/src/services/JSRouter.ts +6 -1
  53. package/src/services/SceneRenderer.ts +1 -1
  54. package/src/stores/apps.ts +11 -2
  55. package/src/stores/settings.ts +10 -0
  56. package/src/types/capabilities/even-realities-g1.ts +4 -0
  57. package/src/utils/display/index.ts +2 -2
  58. package/src/utils/display/profiles/index.ts +7 -2
  59. package/src/utils/display/profiles/rayneo-io.ts +6 -1
  60. package/src/utils/display/scene/index.ts +1 -1
  61. package/src/utils/display/scene/process.ts +22 -7
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/display/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @glassly/display-utils\n *\n * Glasses-agnostic, pixel-accurate text measurement and wrapping library\n * for smart glasses displays.\n *\n * Key features:\n * - Pixel-perfect measurement (no abstract units or averages)\n * - Multiple break modes (character, word, strict-word)\n * - Full script support (Latin, CJK, Korean, Cyrillic)\n * - Configurable display profiles for different glasses hardware\n *\n * @example\n * ```typescript\n * import {\n * TextMeasurer,\n * TextWrapper,\n * DisplayHelpers,\n * G1_PROFILE\n * } from '@glassly/display-utils'\n *\n * // Create measurer and wrapper for G1 glasses\n * const measurer = new TextMeasurer(G1_PROFILE)\n * const wrapper = new TextWrapper(measurer, { breakMode: 'character' })\n * const helpers = new DisplayHelpers(measurer, wrapper)\n *\n * // Wrap text for display\n * const result = wrapper.wrap(\"Hello, world! This is a long text.\")\n * console.log(result.lines)\n * // [\"Hello, world! This is a long text th-\", \"at needs wrapping.\"]\n * ```\n */\n\n// =============================================================================\n// Profiles - Hardware configurations for different glasses\n// =============================================================================\n\n// Import for factory functions\nimport {G1_PROFILE_LEGACY, G2_PROFILE, Z100_PROFILE, NEX_PROFILE} from \"./profiles\"\n\n// =============================================================================\n// Convenience factory functions\n// =============================================================================\n\nimport {TextMeasurer} from \"./measurer\"\nimport {TextWrapper} from \"./wrapper\"\nimport {DisplayHelpers} from \"./helpers\"\nimport {ColumnComposer} from \"./composer\"\nimport {G1_PROFILE} from \"./profiles\"\nimport type {DisplayProfile} from \"./profiles\"\nimport type {WrapOptions} from \"./wrapper\"\n\nexport type {\n DisplayProfile,\n FontMetrics,\n UniformScriptWidths,\n FallbackConfig,\n DisplayConstraints,\n ScriptType,\n} from \"./profiles\"\n\nexport {G1_PROFILE, G1_PROFILE_LEGACY, G1_HYPHEN_WIDTH_PX, G1_SPACE_WIDTH_PX} from \"./profiles\"\n\n// G2 Profile\nexport {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from \"./profiles\"\n\n// Z100 Profile\nexport {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from \"./profiles\"\n\n// Mentra Nex Profile (a.k.a. Mentra Display)\nexport {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from \"./profiles\"\n\n// RayNeo iO Profile (stock subtitle page)\nexport {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from \"./profiles\"\n\n// =============================================================================\n// Measurer - Pixel-accurate text measurement\n// =============================================================================\n\nexport {TextMeasurer} from \"./measurer\"\nexport type {CharMeasurement, TextMeasurement} from \"./measurer\"\n\n// Script detection utilities\nexport {\n detectScript,\n isCJKCharacter,\n isKoreanCharacter,\n isUniformWidthScript,\n isUnsupportedScript,\n needsHyphenForBreak,\n SCRIPT_RANGES,\n} from \"./measurer\"\n\n// =============================================================================\n// Wrapper - Text wrapping with multiple break modes\n// =============================================================================\n\nexport {TextWrapper} from \"./wrapper\"\nexport type {WrapOptions, WrapResult, LineMetrics, BreakMode} from \"./wrapper\"\nexport {DEFAULT_WRAP_OPTIONS} from \"./wrapper\"\n\n// =============================================================================\n// Helpers - Optional convenience utilities\n// =============================================================================\n\nexport {DisplayHelpers, ScrollView} from \"./helpers\"\nexport type {TruncateResult, Page, Chunk, ScrollPosition, ScrollViewport} from \"./helpers\"\n\n// =============================================================================\n// Composer - Multi-column layout composition\n// =============================================================================\n\nexport {ColumnComposer, createColumnComposer} from \"./composer\"\nexport type {ColumnConfig, ComposeOptions, ComposeResult} from \"./composer\"\n\n/**\n * Create a complete display toolkit for a given profile.\n *\n * @param profile - Display profile (defaults to G1)\n * @param wrapOptions - Default wrap options\n * @returns Object with measurer, wrapper, and helpers\n *\n * @example\n * ```typescript\n * const { measurer, wrapper, helpers } = createDisplayToolkit()\n * const lines = wrapper.wrapToLines(\"Hello, world!\")\n * ```\n */\nexport function createDisplayToolkit(\n profile: DisplayProfile = G1_PROFILE,\n wrapOptions?: WrapOptions,\n): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer, wrapOptions)\n const helpers = new DisplayHelpers(measurer, wrapper)\n const composer = new ColumnComposer(profile, wrapOptions?.breakMode || \"word\")\n\n return {\n measurer,\n wrapper,\n helpers,\n composer,\n profile,\n }\n}\n\n/**\n * Create a G1-configured display toolkit with character breaking.\n * This is the recommended setup for captions and similar high-utilization use cases.\n *\n * @returns Object with measurer, wrapper, and helpers configured for G1\n *\n * @example\n * ```typescript\n * const { wrapper, composer } = createG1Toolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * const columns = composer.composeDoubleTextWall(\"Left\", \"Right\")\n * ```\n */\nexport function createG1Toolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(G1_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a G1-configured display toolkit for LEGACY mobile clients.\n *\n * Use this when the mobile client has old wrapping logic that re-wraps\n * text received from the cloud. This profile uses a reduced display width\n * (~522px instead of 576px) to prevent double-wrapping overflow.\n *\n * @returns Object with measurer, wrapper, and helpers configured for legacy G1 clients\n *\n * @example\n * ```typescript\n * // For old mobile clients that double-wrap\n * const { wrapper } = createG1LegacyToolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * // Lines will be shorter to account for mobile re-wrapping\n * ```\n */\nexport function createG1LegacyToolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(G1_PROFILE_LEGACY, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a G2-configured display toolkit with character breaking.\n * G2 uses EvenHub protocol with the same display hardware as G1.\n *\n * @returns Object with measurer, wrapper, and helpers configured for Even G2\n *\n * @example\n * ```typescript\n * const { wrapper } = createG2Toolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * ```\n */\nexport function createG2Toolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(G2_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a Z100-configured display toolkit with character breaking.\n *\n * @returns Object with measurer, wrapper, and helpers configured for Vuzix Z100\n *\n * @example\n * ```typescript\n * const { wrapper } = createZ100Toolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * ```\n */\nexport function createZ100Toolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(Z100_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a Mentra Nex-configured display toolkit with character breaking.\n * Also known as \"Mentra Display\".\n *\n * @returns Object with measurer, wrapper, and helpers configured for Mentra Nex\n *\n * @example\n * ```typescript\n * const { wrapper } = createNexToolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * ```\n */\nexport function createNexToolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(NEX_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/display/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @glassly/display-utils\n *\n * Glasses-agnostic, pixel-accurate text measurement and wrapping library\n * for smart glasses displays.\n *\n * Key features:\n * - Pixel-perfect measurement (no abstract units or averages)\n * - Multiple break modes (character, word, strict-word)\n * - Full script support (Latin, CJK, Korean, Cyrillic)\n * - Configurable display profiles for different glasses hardware\n *\n * @example\n * ```typescript\n * import {\n * TextMeasurer,\n * TextWrapper,\n * DisplayHelpers,\n * G1_PROFILE\n * } from '@glassly/display-utils'\n *\n * // Create measurer and wrapper for G1 glasses\n * const measurer = new TextMeasurer(G1_PROFILE)\n * const wrapper = new TextWrapper(measurer, { breakMode: 'character' })\n * const helpers = new DisplayHelpers(measurer, wrapper)\n *\n * // Wrap text for display\n * const result = wrapper.wrap(\"Hello, world! This is a long text.\")\n * console.log(result.lines)\n * // [\"Hello, world! This is a long text th-\", \"at needs wrapping.\"]\n * ```\n */\n\n// =============================================================================\n// Profiles - Hardware configurations for different glasses\n// =============================================================================\n\n// Import for factory functions\nimport {G1_PROFILE_LEGACY, G2_PROFILE, Z100_PROFILE, NEX_PROFILE} from \"./profiles\"\n\n// =============================================================================\n// Convenience factory functions\n// =============================================================================\n\nimport {TextMeasurer} from \"./measurer\"\nimport {TextWrapper} from \"./wrapper\"\nimport {DisplayHelpers} from \"./helpers\"\nimport {ColumnComposer} from \"./composer\"\nimport {G1_PROFILE} from \"./profiles\"\nimport type {DisplayProfile} from \"./profiles\"\nimport type {WrapOptions} from \"./wrapper\"\n\nexport type {\n DisplayProfile,\n FontMetrics,\n UniformScriptWidths,\n FallbackConfig,\n DisplayConstraints,\n ScriptType,\n} from \"./profiles\"\n\nexport {G1_PROFILE, G1_PROFILE_LEGACY, G1_HYPHEN_WIDTH_PX, G1_SPACE_WIDTH_PX} from \"./profiles\"\n\n// G2 Profile\nexport {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from \"./profiles\"\n\n// Z100 Profile\nexport {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from \"./profiles\"\n\n// Mentra Nex Profile (a.k.a. Mentra Display)\nexport {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from \"./profiles\"\n\n// RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)\nexport {RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from \"./profiles\"\n\n// =============================================================================\n// Measurer - Pixel-accurate text measurement\n// =============================================================================\n\nexport {TextMeasurer} from \"./measurer\"\nexport type {CharMeasurement, TextMeasurement} from \"./measurer\"\n\n// Script detection utilities\nexport {\n detectScript,\n isCJKCharacter,\n isKoreanCharacter,\n isUniformWidthScript,\n isUnsupportedScript,\n needsHyphenForBreak,\n SCRIPT_RANGES,\n} from \"./measurer\"\n\n// =============================================================================\n// Wrapper - Text wrapping with multiple break modes\n// =============================================================================\n\nexport {TextWrapper} from \"./wrapper\"\nexport type {WrapOptions, WrapResult, LineMetrics, BreakMode} from \"./wrapper\"\nexport {DEFAULT_WRAP_OPTIONS} from \"./wrapper\"\n\n// =============================================================================\n// Helpers - Optional convenience utilities\n// =============================================================================\n\nexport {DisplayHelpers, ScrollView} from \"./helpers\"\nexport type {TruncateResult, Page, Chunk, ScrollPosition, ScrollViewport} from \"./helpers\"\n\n// =============================================================================\n// Composer - Multi-column layout composition\n// =============================================================================\n\nexport {ColumnComposer, createColumnComposer} from \"./composer\"\nexport type {ColumnConfig, ComposeOptions, ComposeResult} from \"./composer\"\n\n/**\n * Create a complete display toolkit for a given profile.\n *\n * @param profile - Display profile (defaults to G1)\n * @param wrapOptions - Default wrap options\n * @returns Object with measurer, wrapper, and helpers\n *\n * @example\n * ```typescript\n * const { measurer, wrapper, helpers } = createDisplayToolkit()\n * const lines = wrapper.wrapToLines(\"Hello, world!\")\n * ```\n */\nexport function createDisplayToolkit(\n profile: DisplayProfile = G1_PROFILE,\n wrapOptions?: WrapOptions,\n): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer, wrapOptions)\n const helpers = new DisplayHelpers(measurer, wrapper)\n const composer = new ColumnComposer(profile, wrapOptions?.breakMode || \"word\")\n\n return {\n measurer,\n wrapper,\n helpers,\n composer,\n profile,\n }\n}\n\n/**\n * Create a G1-configured display toolkit with character breaking.\n * This is the recommended setup for captions and similar high-utilization use cases.\n *\n * @returns Object with measurer, wrapper, and helpers configured for G1\n *\n * @example\n * ```typescript\n * const { wrapper, composer } = createG1Toolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * const columns = composer.composeDoubleTextWall(\"Left\", \"Right\")\n * ```\n */\nexport function createG1Toolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(G1_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a G1-configured display toolkit for LEGACY mobile clients.\n *\n * Use this when the mobile client has old wrapping logic that re-wraps\n * text received from the cloud. This profile uses a reduced display width\n * (~522px instead of 576px) to prevent double-wrapping overflow.\n *\n * @returns Object with measurer, wrapper, and helpers configured for legacy G1 clients\n *\n * @example\n * ```typescript\n * // For old mobile clients that double-wrap\n * const { wrapper } = createG1LegacyToolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * // Lines will be shorter to account for mobile re-wrapping\n * ```\n */\nexport function createG1LegacyToolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(G1_PROFILE_LEGACY, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a G2-configured display toolkit with character breaking.\n * G2 uses EvenHub protocol with the same display hardware as G1.\n *\n * @returns Object with measurer, wrapper, and helpers configured for Even G2\n *\n * @example\n * ```typescript\n * const { wrapper } = createG2Toolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * ```\n */\nexport function createG2Toolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(G2_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a Z100-configured display toolkit with character breaking.\n *\n * @returns Object with measurer, wrapper, and helpers configured for Vuzix Z100\n *\n * @example\n * ```typescript\n * const { wrapper } = createZ100Toolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * ```\n */\nexport function createZ100Toolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(Z100_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n\n/**\n * Create a Mentra Nex-configured display toolkit with character breaking.\n * Also known as \"Mentra Display\".\n *\n * @returns Object with measurer, wrapper, and helpers configured for Mentra Nex\n *\n * @example\n * ```typescript\n * const { wrapper } = createNexToolkit()\n * const result = wrapper.wrap(\"Your text here\")\n * ```\n */\nexport function createNexToolkit(): {\n measurer: TextMeasurer\n wrapper: TextWrapper\n helpers: DisplayHelpers\n composer: ColumnComposer\n profile: DisplayProfile\n} {\n return createDisplayToolkit(NEX_PROFILE, {\n breakMode: \"character-no-hyphen\",\n })\n}\n"]}
@@ -9,5 +9,5 @@ export { G1_PROFILE, G1_PROFILE_LEGACY, G1_HYPHEN_WIDTH_PX, G1_SPACE_WIDTH_PX }
9
9
  export { Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX } from "./z100";
10
10
  export { G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX } from "./g2";
11
11
  export { NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX } from "./nex";
12
- export { RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX } from "./rayneo-io";
12
+ export { RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX, } from "./rayneo-io";
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/profiles/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,UAAU,GACX,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAC,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAGzF,OAAO,EAAC,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAC,MAAM,QAAQ,CAAA;AAG9E,OAAO,EAAC,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAGtE,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,OAAO,CAAA;AAG1E,OAAO,EAAC,iBAAiB,EAAE,yBAAyB,EAAE,wBAAwB,EAAC,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/profiles/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,UAAU,GACX,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAC,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAGzF,OAAO,EAAC,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAC,MAAM,QAAQ,CAAA;AAG9E,OAAO,EAAC,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAGtE,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,OAAO,CAAA;AAG1E,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,aAAa,CAAA"}
@@ -12,6 +12,6 @@ export { Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX } from "./z100"
12
12
  export { G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX } from "./g2";
13
13
  // Mentra Nex Profile (a.k.a. Mentra Display)
14
14
  export { NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX } from "./nex";
15
- // RayNeo iO Profile (stock subtitle page)
16
- export { RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX } from "./rayneo-io";
15
+ // RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)
16
+ export { RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX, } from "./rayneo-io";
17
17
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/display/profiles/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,cAAc;AACd,OAAO,EAAC,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAEzF,eAAe;AACf,OAAO,EAAC,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAC,MAAM,QAAQ,CAAA;AAE9E,aAAa;AACb,OAAO,EAAC,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAEtE,6CAA6C;AAC7C,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,OAAO,CAAA;AAE1E,0CAA0C;AAC1C,OAAO,EAAC,iBAAiB,EAAE,yBAAyB,EAAE,wBAAwB,EAAC,MAAM,aAAa,CAAA","sourcesContent":["/**\n * Display Profiles\n *\n * Hardware-specific configurations for smart glasses displays.\n * Each profile defines display dimensions, font metrics, and constraints.\n */\n\n// Types\nexport type {\n DisplayProfile,\n FontMetrics,\n UniformScriptWidths,\n FallbackConfig,\n DisplayConstraints,\n ScriptType,\n} from \"./types\"\n\n// G1 Profiles\nexport {G1_PROFILE, G1_PROFILE_LEGACY, G1_HYPHEN_WIDTH_PX, G1_SPACE_WIDTH_PX} from \"./g1\"\n\n// Z100 Profile\nexport {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from \"./z100\"\n\n// G2 Profile\nexport {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from \"./g2\"\n\n// Mentra Nex Profile (a.k.a. Mentra Display)\nexport {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from \"./nex\"\n\n// RayNeo iO Profile (stock subtitle page)\nexport {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from \"./rayneo-io\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/display/profiles/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,cAAc;AACd,OAAO,EAAC,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAEzF,eAAe;AACf,OAAO,EAAC,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAC,MAAM,QAAQ,CAAA;AAE9E,aAAa;AACb,OAAO,EAAC,UAAU,EAAE,kBAAkB,EAAE,iBAAiB,EAAC,MAAM,MAAM,CAAA;AAEtE,6CAA6C;AAC7C,OAAO,EAAC,WAAW,EAAE,mBAAmB,EAAE,kBAAkB,EAAC,MAAM,OAAO,CAAA;AAE1E,mEAAmE;AACnE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,aAAa,CAAA","sourcesContent":["/**\n * Display Profiles\n *\n * Hardware-specific configurations for smart glasses displays.\n * Each profile defines display dimensions, font metrics, and constraints.\n */\n\n// Types\nexport type {\n DisplayProfile,\n FontMetrics,\n UniformScriptWidths,\n FallbackConfig,\n DisplayConstraints,\n ScriptType,\n} from \"./types\"\n\n// G1 Profiles\nexport {G1_PROFILE, G1_PROFILE_LEGACY, G1_HYPHEN_WIDTH_PX, G1_SPACE_WIDTH_PX} from \"./g1\"\n\n// Z100 Profile\nexport {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from \"./z100\"\n\n// G2 Profile\nexport {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from \"./g2\"\n\n// Mentra Nex Profile (a.k.a. Mentra Display)\nexport {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from \"./nex\"\n\n// RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)\nexport {\n RAYNEO_IO_PROFILE,\n RAYNEO_IO_CFW_PROFILE,\n RAYNEO_IO_HYPHEN_WIDTH_PX,\n RAYNEO_IO_SPACE_WIDTH_PX,\n} from \"./rayneo-io\"\n"]}
@@ -1,2 +1,2 @@
1
- export { RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX } from "@glassly/miniapp/display";
1
+ export { RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX, } from "@glassly/miniapp/display";
2
2
  //# sourceMappingURL=rayneo-io.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rayneo-io.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/profiles/rayneo-io.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAE,yBAAyB,EAAE,wBAAwB,EAAC,MAAM,0BAA0B,CAAA"}
1
+ {"version":3,"file":"rayneo-io.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/profiles/rayneo-io.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,0BAA0B,CAAA"}
@@ -1,3 +1,3 @@
1
1
  // Moved into the SDK (@glassly/miniapp/display) so miniapps lay text out the way the host measures it; this path re-exports it for the engine.
2
- export { RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX } from "@glassly/miniapp/display";
2
+ export { RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX, } from "@glassly/miniapp/display";
3
3
  //# sourceMappingURL=rayneo-io.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rayneo-io.js","sourceRoot":"","sources":["../../../../src/utils/display/profiles/rayneo-io.ts"],"names":[],"mappings":"AAAA,+IAA+I;AAC/I,OAAO,EAAC,iBAAiB,EAAE,yBAAyB,EAAE,wBAAwB,EAAC,MAAM,0BAA0B,CAAA","sourcesContent":["// Moved into the SDK (@glassly/miniapp/display) so miniapps lay text out the way the host measures it; this path re-exports it for the engine.\nexport {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from \"@glassly/miniapp/display\"\n"]}
1
+ {"version":3,"file":"rayneo-io.js","sourceRoot":"","sources":["../../../../src/utils/display/profiles/rayneo-io.ts"],"names":[],"mappings":"AAAA,+IAA+I;AAC/I,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,0BAA0B,CAAA","sourcesContent":["// Moved into the SDK (@glassly/miniapp/display) so miniapps lay text out the way the host measures it; this path re-exports it for the engine.\nexport {\n RAYNEO_IO_PROFILE,\n RAYNEO_IO_CFW_PROFILE,\n RAYNEO_IO_HYPHEN_WIDTH_PX,\n RAYNEO_IO_SPACE_WIDTH_PX,\n} from \"@glassly/miniapp/display\"\n"]}
@@ -4,7 +4,7 @@ export { resolveAnchors } from "./anchor";
4
4
  export type { DiffResult, DiffableElement } from "./differ";
5
5
  export { diffScene } from "./differ";
6
6
  export type { ProcessedScene } from "./process";
7
- export { processScene, profileLineHeightPx } from "./process";
7
+ export { processScene, profileLineHeightPx, wrapsOnDevice } from "./process";
8
8
  export type { DegradedScene } from "./degrade";
9
9
  export { degradeScene } from "./degrade";
10
10
  export { SceneStore } from "./store";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAA;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AACvC,YAAY,EAAC,UAAU,EAAE,eAAe,EAAC,MAAM,UAAU,CAAA;AACzD,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAA;AAClC,YAAY,EAAC,cAAc,EAAC,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAC,MAAM,WAAW,CAAA;AAC3D,YAAY,EAAC,aAAa,EAAC,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAA;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AACvC,YAAY,EAAC,UAAU,EAAE,eAAe,EAAC,MAAM,UAAU,CAAA;AACzD,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAA;AAClC,YAAY,EAAC,cAAc,EAAC,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAC,MAAM,WAAW,CAAA;AAC1E,YAAY,EAAC,aAAa,EAAC,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA"}
@@ -1,7 +1,7 @@
1
1
  export { boxesEqual, contentHash, elementContentHash } from "./types";
2
2
  export { resolveAnchors } from "./anchor";
3
3
  export { diffScene } from "./differ";
4
- export { processScene, profileLineHeightPx } from "./process";
4
+ export { processScene, profileLineHeightPx, wrapsOnDevice } from "./process";
5
5
  export { degradeScene } from "./degrade";
6
6
  export { SceneStore } from "./store";
7
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAA;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AAEvC,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAA;AAElC,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAC,MAAM,WAAW,CAAA;AAE3D,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA","sourcesContent":["export type {\n FrameElement,\n SceneAnchor,\n SceneBox,\n SceneBoxInput,\n SceneBreakMode,\n SceneChange,\n SceneDisplayCapabilities,\n SceneElementInput,\n SceneElementType,\n SceneFrame,\n SceneImageStyle,\n ScenePoint,\n SceneProcessResult,\n SceneRectStyle,\n SceneShapeStyle,\n SceneStrokeStyle,\n SceneTextStyle,\n SceneTransition,\n SceneTransitionEdge,\n} from \"./types\"\nexport {boxesEqual, contentHash, elementContentHash} from \"./types\"\nexport {resolveAnchors} from \"./anchor\"\nexport type {DiffResult, DiffableElement} from \"./differ\"\nexport {diffScene} from \"./differ\"\nexport type {ProcessedScene} from \"./process\"\nexport {processScene, profileLineHeightPx} from \"./process\"\nexport type {DegradedScene} from \"./degrade\"\nexport {degradeScene} from \"./degrade\"\nexport {SceneStore} from \"./store\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAA;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AAEvC,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAA;AAElC,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAC,MAAM,WAAW,CAAA;AAE1E,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA","sourcesContent":["export type {\n FrameElement,\n SceneAnchor,\n SceneBox,\n SceneBoxInput,\n SceneBreakMode,\n SceneChange,\n SceneDisplayCapabilities,\n SceneElementInput,\n SceneElementType,\n SceneFrame,\n SceneImageStyle,\n ScenePoint,\n SceneProcessResult,\n SceneRectStyle,\n SceneShapeStyle,\n SceneStrokeStyle,\n SceneTextStyle,\n SceneTransition,\n SceneTransitionEdge,\n} from \"./types\"\nexport {boxesEqual, contentHash, elementContentHash} from \"./types\"\nexport {resolveAnchors} from \"./anchor\"\nexport type {DiffResult, DiffableElement} from \"./differ\"\nexport {diffScene} from \"./differ\"\nexport type {ProcessedScene} from \"./process\"\nexport {processScene, profileLineHeightPx, wrapsOnDevice} from \"./process\"\nexport type {DegradedScene} from \"./degrade\"\nexport {degradeScene} from \"./degrade\"\nexport {SceneStore} from \"./store\"\n"]}
@@ -15,6 +15,12 @@ export interface ProcessedScene {
15
15
  degraded: boolean;
16
16
  dropped: string[];
17
17
  }
18
+ /**
19
+ * Text the host cannot wrap: a script the measurer has no widths or break
20
+ * rules for (Thai has no spaces between words, for one). It is passed through
21
+ * unwrapped and the device's text engine lays it out in the box.
22
+ */
23
+ export declare function wrapsOnDevice(text: string): boolean;
18
24
  /**
19
25
  * Line height for box-height→line-count math — ONLY when the profile declares
20
26
  * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,iBAAiB,EAKlB,MAAM,SAAS,CAAA;AAGhB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAmED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAE,cAAc,GACtB,cAAc,CAkThB"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,iBAAiB,EAKlB,MAAM,SAAS,CAAA;AAGhB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAkBD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKnD;AAmDD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAE,cAAc,GACtB,cAAc,CAoThB"}
@@ -8,6 +8,7 @@
8
8
  * design doc's image "reject" to per-element drop).
9
9
  */
10
10
  import { compileSvgPath } from "./svgPath";
11
+ import { isUnsupportedScript } from "../measurer/script-detection";
11
12
  import { TextMeasurer } from "../measurer/TextMeasurer";
12
13
  import { TextWrapper } from "../wrapper/TextWrapper";
13
14
  import { MAX_SCENE_DEPTH, POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash } from "./types";
@@ -25,6 +26,18 @@ function clampBox(box, width, height) {
25
26
  return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
26
27
  }
27
28
  const isFinite = (n) => typeof n === "number" && Number.isFinite(n);
29
+ /**
30
+ * Text the host cannot wrap: a script the measurer has no widths or break
31
+ * rules for (Thai has no spaces between words, for one). It is passed through
32
+ * unwrapped and the device's text engine lays it out in the box.
33
+ */
34
+ export function wrapsOnDevice(text) {
35
+ for (const char of text) {
36
+ if (isUnsupportedScript(char))
37
+ return true;
38
+ }
39
+ return false;
40
+ }
28
41
  /** Integer points, or null when the count/values are invalid for the type. */
29
42
  function validPoints(el) {
30
43
  const raw = el.points;
@@ -337,10 +350,11 @@ export function processScene(input, caps, profile) {
337
350
  // Height clipping only applies with a CALIBRATED line height — otherwise
338
351
  // the firmware clips vertically in-box (legacy behavior).
339
352
  const style = el.style ?? {};
340
- if (style.font === "mono" || style.font === "builtin") {
341
- // Grid text is laid out by the app (cols × rows) and builtin text by the
342
- // glasses' own font engine; the host's proportional wrap metrics don't
343
- // apply to either. Pass lines through and let the device clip.
353
+ if (style.font === "mono" || style.font === "builtin" || wrapsOnDevice(el.text ?? "")) {
354
+ // Grid text is laid out by the app (cols × rows), builtin text by the
355
+ // glasses' own font engine and unsupported scripts by the device's text
356
+ // engine; the host's proportional wrap metrics don't apply to any of
357
+ // them. Pass lines through and let the device wrap and clip.
344
358
  const monoText = el.text ?? "";
345
359
  out.push({
346
360
  id: el.id,
@@ -355,8 +369,9 @@ export function processScene(input, caps, profile) {
355
369
  continue;
356
370
  }
357
371
  const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines;
372
+ const maxWidthPx = Math.max(1, clamped.w - 2 * (profile.textInsetPx ?? 0));
358
373
  const result = wrapper.wrap(el.text ?? "", {
359
- maxWidthPx: clamped.w,
374
+ maxWidthPx,
360
375
  maxLines,
361
376
  ...(style.breakMode ? { breakMode: style.breakMode } : {}),
362
377
  });
@@ -367,12 +382,12 @@ export function processScene(input, caps, profile) {
367
382
  // Trim the last line until it fits WITH the ellipsis appended —
368
383
  // otherwise the extra glyph can overflow the clamped box width.
369
384
  let last = lines[lines.length - 1];
370
- while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {
385
+ while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, maxWidthPx)) {
371
386
  last = last.slice(0, -1);
372
387
  }
373
388
  // A box narrower than the ellipsis glyph itself gets an empty line —
374
389
  // never emit a line wider than the clamped box.
375
- const ellipsized = last.length > 0 || measurer.fitsInWidth("…", clamped.w) ? `${last}…` : "";
390
+ const ellipsized = last.length > 0 || measurer.fitsInWidth("…", maxWidthPx) ? `${last}…` : "";
376
391
  lines = [...lines.slice(0, -1), ellipsized];
377
392
  }
378
393
  }
@@ -1 +1 @@
1
- {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene processing — validate → clamp → budget → wrap. Everything here is\n * generic; device variation enters only through the capabilities block and the\n * display profile (data, not code). Spec §5.\n *\n * Never rejects a frame: offending elements are dropped per-element and\n * reported via `dropped` / `degraded` (design doc §3.4.6; spec §4 refines the\n * design doc's image \"reject\" to per-element drop).\n */\n\nimport {compileSvgPath} from \"./svgPath\"\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {DisplayProfile} from \"../profiles/types\"\nimport type {DiffableElement} from \"./differ\"\nimport type {\n SceneBox,\n SceneBoxInput,\n SceneDisplayCapabilities,\n SceneElementInput,\n ScenePoint,\n SceneTextStyle,\n SceneTransition,\n SceneTransitionEdge,\n} from \"./types\"\nimport {MAX_SCENE_DEPTH, POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash} from \"./types\"\n\nexport interface ProcessedScene {\n elements: DiffableElement[]\n degraded: boolean\n dropped: string[]\n}\n\n/** Reporting id for an element the app may not have named. */\nfunction reportId(el: SceneElementInput, index: number): string {\n return el.id ?? `${el.type}[${index}]`\n}\n\nfunction clampBox(box: SceneBox, width: number, height: number): SceneBox | null {\n const x1 = Math.max(0, Math.floor(box.x))\n const y1 = Math.max(0, Math.floor(box.y))\n const x2 = Math.min(width, Math.floor(box.x) + Math.max(0, Math.floor(box.w)))\n const y2 = Math.min(height, Math.floor(box.y) + Math.max(0, Math.floor(box.h)))\n if (x2 <= x1 || y2 <= y1) return null\n return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}\n}\n\nconst isFinite = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n)\n\n/** Integer points, or null when the count/values are invalid for the type. */\nfunction validPoints(el: SceneElementInput): ScenePoint[] | null {\n const raw = (el as {points?: unknown}).points\n if (!Array.isArray(raw) || !(POINT_SHAPE_ARITY[el.type] ?? []).includes(raw.length)) return null\n const points: ScenePoint[] = []\n for (const p of raw) {\n if (!p || typeof p !== \"object\" || !isFinite((p as ScenePoint).x) || !isFinite((p as ScenePoint).y)) return null\n points.push({x: Math.floor((p as ScenePoint).x), y: Math.floor((p as ScenePoint).y)})\n }\n return points\n}\n\nfunction pointsBox(points: ScenePoint[]): SceneBox {\n const xs = points.map((p) => p.x)\n const ys = points.map((p) => p.y)\n const x = Math.min(...xs)\n const y = Math.min(...ys)\n return {x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1}\n}\n\nfunction validEdge(e: unknown): SceneTransitionEdge | undefined {\n if (!e || typeof e !== \"object\") return undefined\n const dx = Math.round(Number((e as SceneTransitionEdge).dx ?? 0))\n const dy = Math.round(Number((e as SceneTransitionEdge).dy ?? 0))\n if (!isFinite(dx) || !isFinite(dy) || Math.abs(dx) > 1024 || Math.abs(dy) > 1024) return undefined\n return {dx, dy}\n}\n\nfunction validTransition(t: unknown): SceneTransition | undefined {\n if (!t || typeof t !== \"object\") return undefined\n const {durationMs, easing} = t as SceneTransition\n if (!isFinite(durationMs) || durationMs <= 0) return undefined\n const out: SceneTransition = easing ? {durationMs, easing} : {durationMs}\n const enter = validEdge((t as SceneTransition).enter)\n const exit = validEdge((t as SceneTransition).exit)\n if (enter) out.enter = enter\n if (exit) out.exit = exit\n return out\n}\n\nfunction boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {\n return (\n clamped.x !== Math.floor(orig.x) ||\n clamped.y !== Math.floor(orig.y) ||\n clamped.w !== Math.floor(orig.w) ||\n clamped.h !== Math.floor(orig.h)\n )\n}\n\n/**\n * Line height for box-height→line-count math — ONLY when the profile declares\n * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved\n * wrong on hardware (288/8=36px clipped the G2 nav instruction to one line;\n * the real container line height is smaller). Without calibration we return\n * null and the wrap step skips host-side height clipping entirely — text wraps\n * to the box WIDTH and the firmware clips vertically in-box, which is exactly\n * the legacy behavior.\n */\nexport function profileLineHeightPx(profile: DisplayProfile, _canvasHeight: number): number | null {\n return profile.lineHeightPx ?? null\n}\n\n/**\n * Process a raw scene against a device's capabilities + profile. Output is\n * diff-ready (text pre-wrapped, boxes clamped, content hashed) and reflects\n * exactly what will be sent — the diff baseline is post-processed by design\n * (spec §4).\n */\nexport function processScene(\n input: readonly SceneElementInput[],\n caps: SceneDisplayCapabilities,\n profile: DisplayProfile,\n): ProcessedScene {\n const dropped: string[] = []\n let degraded = false\n\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer)\n const lineHeight = profileLineHeightPx(profile, caps.height)\n\n // Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).\n const seenIds = new Set<string>()\n const valid: {el: SceneElementInput; index: number; box: SceneBoxInput; points?: ScenePoint[]}[] = []\n input.forEach((raw, index) => {\n if (!raw || typeof raw !== \"object\") {\n dropped.push(reportId(raw ?? ({type: \"text\"} as SceneElementInput), index))\n degraded = true\n return\n }\n let points: ScenePoint[] | undefined\n if (POINT_SHAPE_TYPES.includes(raw.type)) {\n const valid = validPoints(raw)\n if (!valid) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n points = valid\n raw = {...raw, box: pointsBox(valid)} as SceneElementInput\n }\n if (!raw.box) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Non-finite box numbers (NaN/±Infinity/non-number) would survive the\n // Math.* clamp below and poison the frame all the way to native ints.\n const b = raw.box\n if (![b.x, b.y, b.w, b.h].every(isFinite)) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n if ((raw.type === \"arc\" || raw.type === \"pie\") && !(isFinite(raw.startDeg) && isFinite(raw.endDeg))) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Leading \"~\" is the differ's synthetic-id namespace — an app id there\n // would never match across frames (re-created every push). Normalize by\n // stripping; the rewrite is deterministic, so diffing stays stable.\n const el = raw.id?.startsWith(\"~\") ? {...raw, id: raw.id.replace(/^~+/, \"\") || undefined} : raw\n if (el.id) {\n const key = `${el.type}:${el.id}`\n if (seenIds.has(key)) {\n dropped.push(reportId(el, index))\n degraded = true\n return\n }\n seenIds.add(key)\n }\n valid.push({el, index, box: el.box as SceneBoxInput, points})\n })\n\n // Clamp + per-type limits, then budget in array order.\n let textBudget = caps.maxTextElements\n let imageBudget = caps.maxImageElements\n const out: DiffableElement[] = []\n let pathEdges = 0\n\n for (const {el, index, box, points} of valid) {\n const transition = validTransition((el as {transition?: unknown}).transition)\n // Leaving elements only exist to tween away; a device that jumps never shows them.\n if (transition?.exit && !caps.animation) continue\n const transform: {rotation?: number; pivot?: ScenePoint} = {}\n if (el.rotation !== undefined || el.pivot !== undefined) {\n const angle = el.rotation ?? 0\n const pivot = el.pivot ?? {x: box.x + box.w / 2, y: box.y + box.h / 2}\n if (\n !isFinite(angle) ||\n Math.abs(angle) > 36000 ||\n !isFinite(pivot.x) ||\n !isFinite(pivot.y) ||\n Math.abs(pivot.x) > 32767 ||\n Math.abs(pivot.y) > 32767 ||\n !caps.rotation ||\n el.type === \"image\" ||\n el.type === \"text\"\n ) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n transform.rotation = Math.round(angle * 256) / 256\n transform.pivot = {x: Math.round(pivot.x), y: Math.round(pivot.y)}\n if (transition) transition.durationMs = Math.min(65535, transition.durationMs)\n }\n const transformHash =\n transform.rotation === undefined ? undefined : `${transform.rotation}:${transform.pivot?.x}:${transform.pivot?.y}`\n // Depth is a lens disparity, not content: devices without it draw the\n // element flat instead of dropping it. A non-number is still a dev error.\n let depth: number | undefined\n if (el.depth !== undefined) {\n if (!isFinite(el.depth)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const d = Math.max(-MAX_SCENE_DEPTH, Math.min(MAX_SCENE_DEPTH, Math.round(el.depth)))\n if (caps.depth && d !== 0) depth = d\n }\n const plane = depth === undefined ? {} : {depth}\n const clamped = clampBox(box, caps.width, caps.height)\n if (!clamped) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const shrunk = boxShrunk(box, clamped)\n if (shrunk) degraded = true\n\n if (el.type === \"svg\") {\n try {\n if (!caps.shapes.includes(\"svg\") || textBudget <= 0 || shrunk)\n throw new Error(\"SVG unavailable or out of bounds\")\n const scale = el.scale ?? 1\n if (\n !isFinite(scale) ||\n scale < 0 ||\n scale > 8 ||\n (el.fillRule !== undefined && el.fillRule !== \"evenodd\" && el.fillRule !== \"nonzero\")\n )\n throw new Error(\"Invalid SVG scale or fill rule\")\n const compiled = compileSvgPath(el.d)\n if (pathEdges + compiled.edges > 4096) throw new Error(\"SVG scene exceeds the edge budget\")\n pathEdges += compiled.edges\n textBudget--\n out.push({\n id: el.id,\n type: \"svg\",\n box: clamped,\n d: el.d,\n path: compiled.commands,\n pathScale: Math.round(scale * 256),\n fillRule: el.fillRule ?? \"nonzero\",\n style: el.style,\n ...transform,\n ...plane,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({\n type: \"svg\",\n data: `${el.d}:${scale}:${el.fillRule}:${transformHash}`,\n style: el.style,\n depth,\n }),\n })\n } catch {\n dropped.push(reportId(el, index))\n degraded = true\n }\n continue\n }\n\n if (el.type !== \"text\" && el.type !== \"image\" && el.type !== \"rect\") {\n // Vector shapes: only devices that draw them get them; the device clips\n // point shapes itself, so an off-canvas point is not a drop.\n if (!caps.shapes.includes(el.type) || textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n const angles = el.type === \"arc\" || el.type === \"pie\" ? {startDeg: el.startDeg, endDeg: el.endDeg} : {}\n out.push({\n id: el.id,\n type: el.type,\n box: clamped,\n style: el.style,\n ...(points ? {points} : {}),\n ...angles,\n ...(transition ? {transition} : {}),\n ...transform,\n ...plane,\n contentHash: elementContentHash({\n type: el.type,\n style: el.style,\n points,\n ...angles,\n data: transformHash,\n depth,\n }),\n })\n continue\n }\n\n if (el.type === \"image\") {\n // Images: a shrunk box can't be honored (we don't crop pixels host-side)\n // and per-image device limits are box-level (the firmware allocates the\n // component from the box). SGCs scale pixels to the box (phone-side\n // scaling — never on glasses).\n if (shrunk) {\n dropped.push(reportId(el, index))\n continue\n }\n if (caps.maxImagePx && (clamped.w > caps.maxImagePx.width || clamped.h > caps.maxImagePx.height)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n if (imageBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n imageBudget--\n out.push({\n id: el.id,\n type: \"image\",\n box: clamped,\n data: el.data,\n ...(el.style ? {style: el.style} : {}),\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"image\", data: el.data, style: el.style, depth}),\n })\n continue\n }\n\n // text + rect share the text-container budget (design doc §3.4.6).\n if (textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n\n if (el.type === \"rect\") {\n out.push({\n id: el.id,\n type: \"rect\",\n box: clamped,\n style: el.style,\n ...(transition ? {transition} : {}),\n ...transform,\n ...plane,\n contentHash: elementContentHash({type: \"rect\", style: el.style, data: transformHash, depth}),\n })\n continue\n }\n\n // Text: wrap on the phone into the (clamped) box. The box then carries\n // pre-wrapped text; firmware in-box wrap is a fallback, not the mechanism.\n // Height clipping only applies with a CALIBRATED line height — otherwise\n // the firmware clips vertically in-box (legacy behavior).\n const style: SceneTextStyle = el.style ?? {}\n if (style.font === \"mono\" || style.font === \"builtin\") {\n // Grid text is laid out by the app (cols × rows) and builtin text by the\n // glasses' own font engine; the host's proportional wrap metrics don't\n // apply to either. Pass lines through and let the device clip.\n const monoText = el.text ?? \"\"\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: monoText,\n style: el.style,\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style, depth}),\n })\n continue\n }\n const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines\n const result = wrapper.wrap(el.text ?? \"\", {\n maxWidthPx: clamped.w,\n maxLines,\n ...(style.breakMode ? {breakMode: style.breakMode} : {}),\n })\n let lines = result.lines\n if (result.truncated) {\n degraded = true\n if (style.overflow === \"ellipsis\" && lines.length > 0) {\n // Trim the last line until it fits WITH the ellipsis appended —\n // otherwise the extra glyph can overflow the clamped box width.\n let last = lines[lines.length - 1]\n while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {\n last = last.slice(0, -1)\n }\n // A box narrower than the ellipsis glyph itself gets an empty line —\n // never emit a line wider than the clamped box.\n const ellipsized = last.length > 0 || measurer.fitsInWidth(\"…\", clamped.w) ? `${last}…` : \"\"\n lines = [...lines.slice(0, -1), ellipsized]\n }\n }\n const wrappedText = lines.join(\"\\n\")\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: wrappedText,\n style: el.style,\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style, depth}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
1
+ {"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene processing — validate → clamp → budget → wrap. Everything here is\n * generic; device variation enters only through the capabilities block and the\n * display profile (data, not code). Spec §5.\n *\n * Never rejects a frame: offending elements are dropped per-element and\n * reported via `dropped` / `degraded` (design doc §3.4.6; spec §4 refines the\n * design doc's image \"reject\" to per-element drop).\n */\n\nimport {compileSvgPath} from \"./svgPath\"\nimport {isUnsupportedScript} from \"../measurer/script-detection\"\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {DisplayProfile} from \"../profiles/types\"\nimport type {DiffableElement} from \"./differ\"\nimport type {\n SceneBox,\n SceneBoxInput,\n SceneDisplayCapabilities,\n SceneElementInput,\n ScenePoint,\n SceneTextStyle,\n SceneTransition,\n SceneTransitionEdge,\n} from \"./types\"\nimport {MAX_SCENE_DEPTH, POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash} from \"./types\"\n\nexport interface ProcessedScene {\n elements: DiffableElement[]\n degraded: boolean\n dropped: string[]\n}\n\n/** Reporting id for an element the app may not have named. */\nfunction reportId(el: SceneElementInput, index: number): string {\n return el.id ?? `${el.type}[${index}]`\n}\n\nfunction clampBox(box: SceneBox, width: number, height: number): SceneBox | null {\n const x1 = Math.max(0, Math.floor(box.x))\n const y1 = Math.max(0, Math.floor(box.y))\n const x2 = Math.min(width, Math.floor(box.x) + Math.max(0, Math.floor(box.w)))\n const y2 = Math.min(height, Math.floor(box.y) + Math.max(0, Math.floor(box.h)))\n if (x2 <= x1 || y2 <= y1) return null\n return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}\n}\n\nconst isFinite = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n)\n\n/**\n * Text the host cannot wrap: a script the measurer has no widths or break\n * rules for (Thai has no spaces between words, for one). It is passed through\n * unwrapped and the device's text engine lays it out in the box.\n */\nexport function wrapsOnDevice(text: string): boolean {\n for (const char of text) {\n if (isUnsupportedScript(char)) return true\n }\n return false\n}\n\n/** Integer points, or null when the count/values are invalid for the type. */\nfunction validPoints(el: SceneElementInput): ScenePoint[] | null {\n const raw = (el as {points?: unknown}).points\n if (!Array.isArray(raw) || !(POINT_SHAPE_ARITY[el.type] ?? []).includes(raw.length)) return null\n const points: ScenePoint[] = []\n for (const p of raw) {\n if (!p || typeof p !== \"object\" || !isFinite((p as ScenePoint).x) || !isFinite((p as ScenePoint).y)) return null\n points.push({x: Math.floor((p as ScenePoint).x), y: Math.floor((p as ScenePoint).y)})\n }\n return points\n}\n\nfunction pointsBox(points: ScenePoint[]): SceneBox {\n const xs = points.map((p) => p.x)\n const ys = points.map((p) => p.y)\n const x = Math.min(...xs)\n const y = Math.min(...ys)\n return {x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1}\n}\n\nfunction validEdge(e: unknown): SceneTransitionEdge | undefined {\n if (!e || typeof e !== \"object\") return undefined\n const dx = Math.round(Number((e as SceneTransitionEdge).dx ?? 0))\n const dy = Math.round(Number((e as SceneTransitionEdge).dy ?? 0))\n if (!isFinite(dx) || !isFinite(dy) || Math.abs(dx) > 1024 || Math.abs(dy) > 1024) return undefined\n return {dx, dy}\n}\n\nfunction validTransition(t: unknown): SceneTransition | undefined {\n if (!t || typeof t !== \"object\") return undefined\n const {durationMs, easing} = t as SceneTransition\n if (!isFinite(durationMs) || durationMs <= 0) return undefined\n const out: SceneTransition = easing ? {durationMs, easing} : {durationMs}\n const enter = validEdge((t as SceneTransition).enter)\n const exit = validEdge((t as SceneTransition).exit)\n if (enter) out.enter = enter\n if (exit) out.exit = exit\n return out\n}\n\nfunction boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {\n return (\n clamped.x !== Math.floor(orig.x) ||\n clamped.y !== Math.floor(orig.y) ||\n clamped.w !== Math.floor(orig.w) ||\n clamped.h !== Math.floor(orig.h)\n )\n}\n\n/**\n * Line height for box-height→line-count math — ONLY when the profile declares\n * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved\n * wrong on hardware (288/8=36px clipped the G2 nav instruction to one line;\n * the real container line height is smaller). Without calibration we return\n * null and the wrap step skips host-side height clipping entirely — text wraps\n * to the box WIDTH and the firmware clips vertically in-box, which is exactly\n * the legacy behavior.\n */\nexport function profileLineHeightPx(profile: DisplayProfile, _canvasHeight: number): number | null {\n return profile.lineHeightPx ?? null\n}\n\n/**\n * Process a raw scene against a device's capabilities + profile. Output is\n * diff-ready (text pre-wrapped, boxes clamped, content hashed) and reflects\n * exactly what will be sent — the diff baseline is post-processed by design\n * (spec §4).\n */\nexport function processScene(\n input: readonly SceneElementInput[],\n caps: SceneDisplayCapabilities,\n profile: DisplayProfile,\n): ProcessedScene {\n const dropped: string[] = []\n let degraded = false\n\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer)\n const lineHeight = profileLineHeightPx(profile, caps.height)\n\n // Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).\n const seenIds = new Set<string>()\n const valid: {el: SceneElementInput; index: number; box: SceneBoxInput; points?: ScenePoint[]}[] = []\n input.forEach((raw, index) => {\n if (!raw || typeof raw !== \"object\") {\n dropped.push(reportId(raw ?? ({type: \"text\"} as SceneElementInput), index))\n degraded = true\n return\n }\n let points: ScenePoint[] | undefined\n if (POINT_SHAPE_TYPES.includes(raw.type)) {\n const valid = validPoints(raw)\n if (!valid) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n points = valid\n raw = {...raw, box: pointsBox(valid)} as SceneElementInput\n }\n if (!raw.box) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Non-finite box numbers (NaN/±Infinity/non-number) would survive the\n // Math.* clamp below and poison the frame all the way to native ints.\n const b = raw.box\n if (![b.x, b.y, b.w, b.h].every(isFinite)) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n if ((raw.type === \"arc\" || raw.type === \"pie\") && !(isFinite(raw.startDeg) && isFinite(raw.endDeg))) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Leading \"~\" is the differ's synthetic-id namespace — an app id there\n // would never match across frames (re-created every push). Normalize by\n // stripping; the rewrite is deterministic, so diffing stays stable.\n const el = raw.id?.startsWith(\"~\") ? {...raw, id: raw.id.replace(/^~+/, \"\") || undefined} : raw\n if (el.id) {\n const key = `${el.type}:${el.id}`\n if (seenIds.has(key)) {\n dropped.push(reportId(el, index))\n degraded = true\n return\n }\n seenIds.add(key)\n }\n valid.push({el, index, box: el.box as SceneBoxInput, points})\n })\n\n // Clamp + per-type limits, then budget in array order.\n let textBudget = caps.maxTextElements\n let imageBudget = caps.maxImageElements\n const out: DiffableElement[] = []\n let pathEdges = 0\n\n for (const {el, index, box, points} of valid) {\n const transition = validTransition((el as {transition?: unknown}).transition)\n // Leaving elements only exist to tween away; a device that jumps never shows them.\n if (transition?.exit && !caps.animation) continue\n const transform: {rotation?: number; pivot?: ScenePoint} = {}\n if (el.rotation !== undefined || el.pivot !== undefined) {\n const angle = el.rotation ?? 0\n const pivot = el.pivot ?? {x: box.x + box.w / 2, y: box.y + box.h / 2}\n if (\n !isFinite(angle) ||\n Math.abs(angle) > 36000 ||\n !isFinite(pivot.x) ||\n !isFinite(pivot.y) ||\n Math.abs(pivot.x) > 32767 ||\n Math.abs(pivot.y) > 32767 ||\n !caps.rotation ||\n el.type === \"image\" ||\n el.type === \"text\"\n ) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n transform.rotation = Math.round(angle * 256) / 256\n transform.pivot = {x: Math.round(pivot.x), y: Math.round(pivot.y)}\n if (transition) transition.durationMs = Math.min(65535, transition.durationMs)\n }\n const transformHash =\n transform.rotation === undefined ? undefined : `${transform.rotation}:${transform.pivot?.x}:${transform.pivot?.y}`\n // Depth is a lens disparity, not content: devices without it draw the\n // element flat instead of dropping it. A non-number is still a dev error.\n let depth: number | undefined\n if (el.depth !== undefined) {\n if (!isFinite(el.depth)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const d = Math.max(-MAX_SCENE_DEPTH, Math.min(MAX_SCENE_DEPTH, Math.round(el.depth)))\n if (caps.depth && d !== 0) depth = d\n }\n const plane = depth === undefined ? {} : {depth}\n const clamped = clampBox(box, caps.width, caps.height)\n if (!clamped) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const shrunk = boxShrunk(box, clamped)\n if (shrunk) degraded = true\n\n if (el.type === \"svg\") {\n try {\n if (!caps.shapes.includes(\"svg\") || textBudget <= 0 || shrunk)\n throw new Error(\"SVG unavailable or out of bounds\")\n const scale = el.scale ?? 1\n if (\n !isFinite(scale) ||\n scale < 0 ||\n scale > 8 ||\n (el.fillRule !== undefined && el.fillRule !== \"evenodd\" && el.fillRule !== \"nonzero\")\n )\n throw new Error(\"Invalid SVG scale or fill rule\")\n const compiled = compileSvgPath(el.d)\n if (pathEdges + compiled.edges > 4096) throw new Error(\"SVG scene exceeds the edge budget\")\n pathEdges += compiled.edges\n textBudget--\n out.push({\n id: el.id,\n type: \"svg\",\n box: clamped,\n d: el.d,\n path: compiled.commands,\n pathScale: Math.round(scale * 256),\n fillRule: el.fillRule ?? \"nonzero\",\n style: el.style,\n ...transform,\n ...plane,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({\n type: \"svg\",\n data: `${el.d}:${scale}:${el.fillRule}:${transformHash}`,\n style: el.style,\n depth,\n }),\n })\n } catch {\n dropped.push(reportId(el, index))\n degraded = true\n }\n continue\n }\n\n if (el.type !== \"text\" && el.type !== \"image\" && el.type !== \"rect\") {\n // Vector shapes: only devices that draw them get them; the device clips\n // point shapes itself, so an off-canvas point is not a drop.\n if (!caps.shapes.includes(el.type) || textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n const angles = el.type === \"arc\" || el.type === \"pie\" ? {startDeg: el.startDeg, endDeg: el.endDeg} : {}\n out.push({\n id: el.id,\n type: el.type,\n box: clamped,\n style: el.style,\n ...(points ? {points} : {}),\n ...angles,\n ...(transition ? {transition} : {}),\n ...transform,\n ...plane,\n contentHash: elementContentHash({\n type: el.type,\n style: el.style,\n points,\n ...angles,\n data: transformHash,\n depth,\n }),\n })\n continue\n }\n\n if (el.type === \"image\") {\n // Images: a shrunk box can't be honored (we don't crop pixels host-side)\n // and per-image device limits are box-level (the firmware allocates the\n // component from the box). SGCs scale pixels to the box (phone-side\n // scaling — never on glasses).\n if (shrunk) {\n dropped.push(reportId(el, index))\n continue\n }\n if (caps.maxImagePx && (clamped.w > caps.maxImagePx.width || clamped.h > caps.maxImagePx.height)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n if (imageBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n imageBudget--\n out.push({\n id: el.id,\n type: \"image\",\n box: clamped,\n data: el.data,\n ...(el.style ? {style: el.style} : {}),\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"image\", data: el.data, style: el.style, depth}),\n })\n continue\n }\n\n // text + rect share the text-container budget (design doc §3.4.6).\n if (textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n\n if (el.type === \"rect\") {\n out.push({\n id: el.id,\n type: \"rect\",\n box: clamped,\n style: el.style,\n ...(transition ? {transition} : {}),\n ...transform,\n ...plane,\n contentHash: elementContentHash({type: \"rect\", style: el.style, data: transformHash, depth}),\n })\n continue\n }\n\n // Text: wrap on the phone into the (clamped) box. The box then carries\n // pre-wrapped text; firmware in-box wrap is a fallback, not the mechanism.\n // Height clipping only applies with a CALIBRATED line height — otherwise\n // the firmware clips vertically in-box (legacy behavior).\n const style: SceneTextStyle = el.style ?? {}\n if (style.font === \"mono\" || style.font === \"builtin\" || wrapsOnDevice(el.text ?? \"\")) {\n // Grid text is laid out by the app (cols × rows), builtin text by the\n // glasses' own font engine and unsupported scripts by the device's text\n // engine; the host's proportional wrap metrics don't apply to any of\n // them. Pass lines through and let the device wrap and clip.\n const monoText = el.text ?? \"\"\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: monoText,\n style: el.style,\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style, depth}),\n })\n continue\n }\n const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines\n const maxWidthPx = Math.max(1, clamped.w - 2 * (profile.textInsetPx ?? 0))\n const result = wrapper.wrap(el.text ?? \"\", {\n maxWidthPx,\n maxLines,\n ...(style.breakMode ? {breakMode: style.breakMode} : {}),\n })\n let lines = result.lines\n if (result.truncated) {\n degraded = true\n if (style.overflow === \"ellipsis\" && lines.length > 0) {\n // Trim the last line until it fits WITH the ellipsis appended —\n // otherwise the extra glyph can overflow the clamped box width.\n let last = lines[lines.length - 1]\n while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, maxWidthPx)) {\n last = last.slice(0, -1)\n }\n // A box narrower than the ellipsis glyph itself gets an empty line —\n // never emit a line wider than the clamped box.\n const ellipsized = last.length > 0 || measurer.fitsInWidth(\"…\", maxWidthPx) ? `${last}…` : \"\"\n lines = [...lines.slice(0, -1), ellipsized]\n }\n }\n const wrappedText = lines.join(\"\\n\")\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: wrappedText,\n style: el.style,\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style, depth}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.3.7-dev.906",
3
+ "version": "0.3.7-dev.910",
4
4
  "description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -97,17 +97,17 @@
97
97
  "registry": "https://registry.npmjs.org/"
98
98
  },
99
99
  "dependencies": {
100
- "@glassly/bluetooth-sdk": "0.3.7-dev.906",
101
- "@glassly/cloud-client": "0.3.7-dev.906",
102
- "@glassly/cloud-protocol": "0.3.7-dev.906",
103
- "@glassly/crust": "0.3.7-dev.906",
104
- "@glassly/miniapp": "0.3.7-dev.906",
100
+ "@glassly/bluetooth-sdk": "0.3.7-dev.910",
101
+ "@glassly/cloud-client": "0.3.7-dev.910",
102
+ "@glassly/cloud-protocol": "0.3.7-dev.910",
103
+ "@glassly/crust": "0.3.7-dev.910",
104
+ "@glassly/miniapp": "0.3.7-dev.910",
105
105
  "buffer": "^6.0.3",
106
106
  "events": "^3.3.0",
107
107
  "react-native-marked": "8.3.2",
108
108
  "semver": "^7.7.4",
109
109
  "typesafe-ts": "^1.7.1",
110
- "@glassly/llm-providers": "0.3.7-dev.906",
110
+ "@glassly/llm-providers": "0.3.7-dev.910",
111
111
  "@huggingface/jinja": "^0.5.9",
112
112
  "jsonrepair": "^3.12.0"
113
113
  },
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "0.3.7",
15
- "releaseIdentity": "0.3.7-dev.906",
16
- "releaseSetId": "glassly-0.3.7-dev.906",
17
- "sourceCommit": "6b17c03b828a30bb6de09e801493e5353477dc0c",
15
+ "releaseIdentity": "0.3.7-dev.910",
16
+ "releaseSetId": "glassly-0.3.7-dev.910",
17
+ "sourceCommit": "137a2ada68a7e7154f6e1557e6beca4659441ada",
18
18
  "otaManifestUrl": null,
19
19
  "otaManifestSha256": null
20
20
  })
@@ -730,7 +730,13 @@ class AppRegistry {
730
730
  const installed = diskApps.filter(
731
731
  (app) => !offlinePackages.has(app.packageName) && !devPackages.has(app.packageName),
732
732
  )
733
- return [...installed, ...dev, ...offline]
733
+ // The last switcher-card capture comes back from disk, so a relaunched
734
+ // app's card is not blank until it is minimized again.
735
+ const withScreenshot = (app: ClientApp): ClientApp => ({
736
+ ...app,
737
+ screenshot: app.screenshot ?? getLocalAppScreenshot(app.packageName),
738
+ })
739
+ return [...installed.map(withScreenshot), ...dev.map(withScreenshot), ...offline]
734
740
  }
735
741
 
736
742
  public async getInstalledMiniapps(): Promise<ClientApp[]> {
@@ -968,6 +974,19 @@ export function saveWidgetExpandChoice(packageName: string, widgetId: string, ex
968
974
  storage.save(`${packageName}_widget_expand_${widgetId}`, expandOnTap)
969
975
  }
970
976
 
977
+ /**
978
+ * Where a screenshot captured under an earlier install now lives: iOS gives
979
+ * every install a new container, so a stored absolute file URI goes stale
980
+ * while the file itself is still in the documents directory.
981
+ */
982
+ function rerootDocumentUri(uri: string): string | null {
983
+ const marker = "/Documents/"
984
+ const at = uri.indexOf(marker)
985
+ if (at < 0) return null
986
+ const rerooted = new File(Paths.document, uri.slice(at + marker.length)).uri
987
+ return rerooted === uri ? null : rerooted
988
+ }
989
+
971
990
  export function getLocalAppScreenshot(packageName: string): string | undefined {
972
991
  const key = `${packageName}_screenshot`
973
992
  const res = storage.load<string>(key)
@@ -976,14 +995,17 @@ export function getLocalAppScreenshot(packageName: string): string | undefined {
976
995
  const uri = res.value
977
996
  if (uri.startsWith("file://")) {
978
997
  try {
979
- if (!new File(uri).exists) {
980
- storage.remove(key)
981
- return undefined
998
+ if (new File(uri).exists) return uri
999
+ const rerooted = rerootDocumentUri(uri)
1000
+ if (rerooted && new File(rerooted).exists) {
1001
+ storage.save(key, rerooted)
1002
+ return rerooted
982
1003
  }
983
1004
  } catch {
984
- storage.remove(key)
985
- return undefined
1005
+ // fall through: the stored uri is unusable
986
1006
  }
1007
+ storage.remove(key)
1008
+ return undefined
987
1009
  }
988
1010
 
989
1011
  return uri
@@ -20,6 +20,7 @@ import {
20
20
  Z100_PROFILE,
21
21
  NEX_PROFILE,
22
22
  RAYNEO_IO_PROFILE,
23
+ RAYNEO_IO_CFW_PROFILE,
23
24
  TextMeasurer,
24
25
  TextWrapper,
25
26
  ColumnComposer,
@@ -210,6 +211,14 @@ const DEVICE_PROFILES: Partial<Record<DeviceTypes, DisplayProfile>> = {
210
211
  [DeviceTypes.RAYNEO_IO]: RAYNEO_IO_PROFILE,
211
212
  }
212
213
 
214
+ /**
215
+ * Scene text profiles for models whose Glassly CFW draws text with a font of
216
+ * its own, used while the CFW publishes a scene canvas.
217
+ */
218
+ const CFW_SCENE_PROFILES: Partial<Record<DeviceTypes, DisplayProfile>> = {
219
+ [DeviceTypes.RAYNEO_IO]: RAYNEO_IO_CFW_PROFILE,
220
+ }
221
+
213
222
  // =============================================================================
214
223
  // DisplayProcessor Class
215
224
  // =============================================================================
@@ -380,6 +389,12 @@ export class DisplayProcessor {
380
389
  return this.profile
381
390
  }
382
391
 
392
+ /** Profile scene text is wrapped with: the CFW's own while its scene page is up, else the model's. */
393
+ public getSceneProfile(): DisplayProfile {
394
+ const cfw = CFW_SCENE_PROFILES[this.deviceModel]
395
+ return cfw && (useGlassesStore.getState().sceneCanvasWidth ?? 0) > 0 ? cfw : this.profile
396
+ }
397
+
383
398
  // ===========================================================================
384
399
  // Display Event Processing
385
400
  // ===========================================================================
@@ -19,6 +19,11 @@ export async function submitJSCrashloopReport(params: {
19
19
  }): Promise<AutomaticReportSubmissionStatus> {
20
20
  const {packageName, reason, lastLogLines = []} = params
21
21
  const app = useAppStatusStore.getState().apps.find((a) => a.packageName === packageName)
22
+ // A dev miniapp's crash is its developer's to fix; JSRouter forwards it to their dev server.
23
+ if (app?.isMiniappDev) {
24
+ console.log(`[${LOG_TAG}] Not reporting a dev miniapp crashloop:`, packageName)
25
+ return {status: "skipped", reason: "dev miniapp"}
26
+ }
22
27
  const appName = app?.name ?? packageName
23
28
  const result = await submitAutomaticReport({
24
29
  kind: "automatic",
@@ -483,13 +483,18 @@ export class JSRouter {
483
483
  if (iface === "__error") {
484
484
  const payload = this.tryParseArgs(msg.argsJson)
485
485
  this.logger.error(`[${packageName}] ${method}`, payload)
486
+ // Crashes reach the `glassly dev` terminal too, not only console calls.
487
+ const summary = this.summarizeReason(payload, method)
488
+ const stack = (payload as {stack?: unknown} | null)?.stack
489
+ const errorArgs = typeof stack === "string" && stack ? [summary, stack] : [summary]
490
+ devServerBridge.forwardLog(packageName, "error", errorArgs, Date.now(), "background")
486
491
  if (this.crashController) {
487
492
  // Only treat "exception" + "unhandledRejection" + "uncaught" as
488
493
  // crash signals. `console.error` calls also flow through the
489
494
  // log path — those don't end the JSContext.
490
495
  const isCrash = method === "exception" || method === "unhandledRejection" || method === "uncaught"
491
496
  if (isCrash) {
492
- this.handleCrash(packageName, this.summarizeReason(payload, method))
497
+ this.handleCrash(packageName, summary)
493
498
  }
494
499
  }
495
500
  return
@@ -250,7 +250,7 @@ class SceneRenderer {
250
250
  return {kind: "legacy", layout, degraded, dropped}
251
251
  }
252
252
 
253
- const processed = processScene(elements, caps, displayProcessor.getProfile())
253
+ const processed = processScene(elements, caps, displayProcessor.getSceneProfile())
254
254
  // A stale baseline means the glasses were cleared since the last commit —
255
255
  // diff from empty so everything repaints (the retained frame stays valid
256
256
  // as the replay source only).