@willwade/aac-processors 0.1.10 → 0.1.12

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 (52) hide show
  1. package/dist/browser/processors/gridset/resolver.js +10 -0
  2. package/dist/browser/processors/gridsetProcessor.js +128 -6
  3. package/dist/processors/gridset/resolver.js +10 -0
  4. package/dist/processors/gridsetProcessor.js +128 -6
  5. package/package.json +1 -3
  6. package/examples/.coverage +0 -0
  7. package/examples/.keep +0 -1
  8. package/examples/README.md +0 -55
  9. package/examples/browser-test.html +0 -331
  10. package/examples/communikate.dot +0 -2637
  11. package/examples/demo.js +0 -143
  12. package/examples/example-images.gridset +0 -0
  13. package/examples/example.ce +0 -0
  14. package/examples/example.dot +0 -14
  15. package/examples/example.grd +0 -1
  16. package/examples/example.gridset +0 -0
  17. package/examples/example.obf +0 -27
  18. package/examples/example.obz +0 -0
  19. package/examples/example.opml +0 -18
  20. package/examples/example.spb +0 -0
  21. package/examples/example.sps +0 -0
  22. package/examples/example2.grd +0 -1
  23. package/examples/obf/aboutme.json +0 -376
  24. package/examples/obf/array.json +0 -6
  25. package/examples/obf/hash.json +0 -4
  26. package/examples/obf/links.obz +0 -0
  27. package/examples/obf/simple.obf +0 -53
  28. package/examples/package-lock.json +0 -1326
  29. package/examples/package.json +0 -10
  30. package/examples/styled-output/converted-snap-to-touchchat.ce +0 -0
  31. package/examples/styled-output/styled-example.ce +0 -0
  32. package/examples/styled-output/styled-example.gridset +0 -0
  33. package/examples/styled-output/styled-example.obf +0 -37
  34. package/examples/styled-output/styled-example.spb +0 -0
  35. package/examples/styling-example.ts +0 -316
  36. package/examples/translate.js +0 -39
  37. package/examples/translate_demo.js +0 -254
  38. package/examples/typescript-demo.ts +0 -251
  39. package/examples/vitedemo/README.md +0 -164
  40. package/examples/vitedemo/index.html +0 -580
  41. package/examples/vitedemo/package-lock.json +0 -1751
  42. package/examples/vitedemo/package.json +0 -24
  43. package/examples/vitedemo/src/main.ts +0 -1001
  44. package/examples/vitedemo/src/vite-env.d.ts +0 -1
  45. package/examples/vitedemo/test-files/example.dot +0 -14
  46. package/examples/vitedemo/test-files/example.grd +0 -1
  47. package/examples/vitedemo/test-files/example.gridset +0 -0
  48. package/examples/vitedemo/test-files/example.obz +0 -0
  49. package/examples/vitedemo/test-files/example.opml +0 -18
  50. package/examples/vitedemo/test-files/simple.obf +0 -53
  51. package/examples/vitedemo/tsconfig.json +0 -24
  52. package/examples/vitedemo/vite.config.ts +0 -57
@@ -42,6 +42,13 @@ export function resolveGrid3CellImage(zip, args, zipEntries) {
42
42
  const y = args.y;
43
43
  const entries = new Set(listZipEntries(zip, zipEntries));
44
44
  const has = (p) => entries.has(normalizeZipPathLocal(p));
45
+ // Debug logging for cells that fail to resolve
46
+ const shouldDebug = imageName?.startsWith('-') && x !== undefined && y !== undefined;
47
+ const debugLog = (msg) => {
48
+ if (shouldDebug) {
49
+ console.log(`[Resolver] ${baseDir} (${x},${y}) "${imageName}": ${msg}`);
50
+ }
51
+ };
45
52
  // Built-in resource like [grid3x]... (old format, not symbol library)
46
53
  // Check this BEFORE general symbol references to avoid misclassification
47
54
  if (imageName && imageName.startsWith('[')) {
@@ -72,6 +79,7 @@ export function resolveGrid3CellImage(zip, args, zipEntries) {
72
79
  // to be prefixed with the cell coordinates
73
80
  if (imageName.startsWith('-') && x != null && y != null) {
74
81
  const coordPrefixed = joinBaseDir(baseDir, `${x}-${y}${imageName}`);
82
+ debugLog(`trying coord-prefixed: ${coordPrefixed}, found: ${has(coordPrefixed)}`);
75
83
  if (has(coordPrefixed))
76
84
  return coordPrefixed;
77
85
  }
@@ -114,12 +122,14 @@ export function resolveGrid3CellImage(zip, args, zipEntries) {
114
122
  `${x}-${y}.jpg`,
115
123
  `${x}-${y}.png`,
116
124
  ].map((n) => joinBaseDir(baseDir, n));
125
+ debugLog(`trying candidates: ${candidates.filter(has).join(', ') || 'none found'}`);
117
126
  for (const c of candidates) {
118
127
  if (has(c))
119
128
  return c;
120
129
  }
121
130
  }
122
131
  }
132
+ debugLog(`NOT FOUND - returning null`);
123
133
  return null;
124
134
  }
125
135
  /**
@@ -646,15 +646,130 @@ class GridsetProcessor extends BaseProcessor {
646
646
  // Track WordList AutoContent cells and their positions for "more" button placement
647
647
  const wordListAutoContentCells = [];
648
648
  let wordListCellIndex = 0;
649
+ // Helper function to find next available position in grid (auto-flow)
650
+ // Returns {x, y} for next available slot that can accommodate the given span
651
+ const findNextAvailablePosition = (width, height, gridLayout) => {
652
+ for (let y = 0; y < maxRows; y++) {
653
+ for (let x = 0; x <= maxCols - width; x++) {
654
+ // Check if this position and the required span area are all free
655
+ let fits = true;
656
+ for (let dy = 0; dy < height && y + dy < maxRows; dy++) {
657
+ for (let dx = 0; dx < width && x + dx < maxCols; dx++) {
658
+ if (gridLayout[y + dy][x + dx] !== null) {
659
+ fits = false;
660
+ break;
661
+ }
662
+ }
663
+ if (!fits)
664
+ break;
665
+ }
666
+ if (fits) {
667
+ return { x, y };
668
+ }
669
+ }
670
+ }
671
+ // If no position found, return 0,0 (will be placed at first available)
672
+ return { x: 0, y: 0 };
673
+ };
674
+ // Helper function to find next available X position in a specific row
675
+ const findNextAvailableXInRow = (rowY, width, gridLayout) => {
676
+ for (let x = 0; x <= maxCols - width; x++) {
677
+ let fits = true;
678
+ for (let dx = 0; dx < width; dx++) {
679
+ if (gridLayout[rowY][x + dx] !== null) {
680
+ fits = false;
681
+ break;
682
+ }
683
+ }
684
+ if (fits)
685
+ return x;
686
+ }
687
+ return 0;
688
+ };
689
+ const cellsWithExplicitPosition = [];
690
+ const cellsWithYOnly = [];
691
+ const cellsWithXOnly = [];
692
+ const cellsWithAutoFlow = [];
649
693
  cellArr.forEach((cell, idx) => {
650
694
  if (!cell || !cell.Content)
651
695
  return;
652
- // Extract position information from cell attributes
653
- // Grid3 uses 1-based coordinates, convert to 0-based for internal use
654
- const cellX = Math.max(0, parseInt(String(cell['@_X'] || '1'), 10) - 1);
655
- const cellY = Math.max(0, parseInt(String(cell['@_Y'] || '1'), 10) - 1);
696
+ const hasX = cell['@_X'] !== undefined;
697
+ const hasY = cell['@_Y'] !== undefined;
698
+ if (hasX && hasY) {
699
+ cellsWithExplicitPosition.push({ cell, idx });
700
+ }
701
+ else if (hasY && !hasX) {
702
+ cellsWithYOnly.push({ cell, idx });
703
+ }
704
+ else if (!hasY && hasX) {
705
+ cellsWithXOnly.push({ cell, idx });
706
+ }
707
+ else {
708
+ cellsWithAutoFlow.push({ cell, idx });
709
+ }
710
+ });
711
+ // Process cells in order: explicit -> Y-only -> X-only -> auto-flow
712
+ const allCellsToProcess = [
713
+ ...cellsWithExplicitPosition,
714
+ ...cellsWithYOnly,
715
+ ...cellsWithXOnly,
716
+ ...cellsWithAutoFlow,
717
+ ];
718
+ allCellsToProcess.forEach(({ cell, idx }) => {
719
+ // Extract span information first
656
720
  const colSpan = parseInt(String(cell['@_ColumnSpan'] || '1'), 10);
657
721
  const rowSpan = parseInt(String(cell['@_RowSpan'] || '1'), 10);
722
+ // Determine position based on what attributes are present
723
+ const hasX = cell['@_X'] !== undefined;
724
+ const hasY = cell['@_Y'] !== undefined;
725
+ let cellX;
726
+ let cellY;
727
+ if (hasX && hasY) {
728
+ // Explicit position: both X and Y provided
729
+ // Grid 3 XML coordinates are already 0-based, use them directly
730
+ cellX = Math.max(0, parseInt(String(cell['@_X']), 10));
731
+ cellY = Math.max(0, parseInt(String(cell['@_Y']), 10));
732
+ }
733
+ else if (hasY && !hasX) {
734
+ // Y-only: auto-flow X in the specified row
735
+ // Grid 3 XML coordinates are already 0-based, use them directly
736
+ cellY = Math.max(0, parseInt(String(cell['@_Y']), 10));
737
+ cellX = findNextAvailableXInRow(cellY, colSpan, gridLayout);
738
+ }
739
+ else if (!hasY && hasX) {
740
+ // X-only: place at specified X in next available row
741
+ // Grid 3 XML coordinates are already 0-based, use them directly
742
+ cellX = Math.max(0, parseInt(String(cell['@_X']), 10));
743
+ // Find first row where this X position is available
744
+ cellY = 0;
745
+ let found = false;
746
+ for (let y = 0; y < maxRows; y++) {
747
+ let fits = true;
748
+ for (let dx = 0; dx < colSpan && cellX + dx < maxCols; dx++) {
749
+ if (gridLayout[y][cellX + dx] !== null) {
750
+ fits = false;
751
+ break;
752
+ }
753
+ }
754
+ if (fits) {
755
+ cellY = y;
756
+ found = true;
757
+ break;
758
+ }
759
+ }
760
+ if (!found) {
761
+ // No available row found, use auto-flow
762
+ const pos = findNextAvailablePosition(colSpan, rowSpan, gridLayout);
763
+ cellX = pos.x;
764
+ cellY = pos.y;
765
+ }
766
+ }
767
+ else {
768
+ // No position: auto-flow both X and Y
769
+ const pos = findNextAvailablePosition(colSpan, rowSpan, gridLayout);
770
+ cellX = pos.x;
771
+ cellY = pos.y;
772
+ }
658
773
  // Extract scan block number (1-8) for block scanning support
659
774
  const scanBlock = parseInt(String(cell['@_ScanBlock'] || '1'), 10);
660
775
  // Extract visibility from Grid 3's <Visibility> child element
@@ -793,10 +908,17 @@ class GridsetProcessor extends BaseProcessor {
793
908
  const resolvedImageEntry = resolveGrid3CellImage(null, {
794
909
  baseDir,
795
910
  imageName: declaredImageName,
796
- x: cellX + 1,
797
- y: cellY + 1,
911
+ x: cellX,
912
+ y: cellY,
798
913
  dynamicFiles,
799
914
  }, entries) || undefined;
915
+ // Debug: log resolution for cells with images
916
+ if (declaredImageName && resolvedImageEntry) {
917
+ console.log(`[GridsetProcessor] Cell (${cellX + 1},${cellY + 1}) [XML coords]: ${declaredImageName} -> ${resolvedImageEntry}`);
918
+ }
919
+ else if (declaredImageName && !resolvedImageEntry) {
920
+ console.log(`[GridsetProcessor] Cell (${cellX + 1},${cellY + 1}) [XML coords]: ${declaredImageName} -> NOT FOUND`);
921
+ }
800
922
  // Check if image is a symbol library reference
801
923
  let symbolLibraryRef = null;
802
924
  if (declaredImageName && isSymbolLibraryReference(declaredImageName)) {
@@ -47,6 +47,13 @@ function resolveGrid3CellImage(zip, args, zipEntries) {
47
47
  const y = args.y;
48
48
  const entries = new Set(listZipEntries(zip, zipEntries));
49
49
  const has = (p) => entries.has(normalizeZipPathLocal(p));
50
+ // Debug logging for cells that fail to resolve
51
+ const shouldDebug = imageName?.startsWith('-') && x !== undefined && y !== undefined;
52
+ const debugLog = (msg) => {
53
+ if (shouldDebug) {
54
+ console.log(`[Resolver] ${baseDir} (${x},${y}) "${imageName}": ${msg}`);
55
+ }
56
+ };
50
57
  // Built-in resource like [grid3x]... (old format, not symbol library)
51
58
  // Check this BEFORE general symbol references to avoid misclassification
52
59
  if (imageName && imageName.startsWith('[')) {
@@ -77,6 +84,7 @@ function resolveGrid3CellImage(zip, args, zipEntries) {
77
84
  // to be prefixed with the cell coordinates
78
85
  if (imageName.startsWith('-') && x != null && y != null) {
79
86
  const coordPrefixed = joinBaseDir(baseDir, `${x}-${y}${imageName}`);
87
+ debugLog(`trying coord-prefixed: ${coordPrefixed}, found: ${has(coordPrefixed)}`);
80
88
  if (has(coordPrefixed))
81
89
  return coordPrefixed;
82
90
  }
@@ -119,12 +127,14 @@ function resolveGrid3CellImage(zip, args, zipEntries) {
119
127
  `${x}-${y}.jpg`,
120
128
  `${x}-${y}.png`,
121
129
  ].map((n) => joinBaseDir(baseDir, n));
130
+ debugLog(`trying candidates: ${candidates.filter(has).join(', ') || 'none found'}`);
122
131
  for (const c of candidates) {
123
132
  if (has(c))
124
133
  return c;
125
134
  }
126
135
  }
127
136
  }
137
+ debugLog(`NOT FOUND - returning null`);
128
138
  return null;
129
139
  }
130
140
  /**
@@ -672,15 +672,130 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
672
672
  // Track WordList AutoContent cells and their positions for "more" button placement
673
673
  const wordListAutoContentCells = [];
674
674
  let wordListCellIndex = 0;
675
+ // Helper function to find next available position in grid (auto-flow)
676
+ // Returns {x, y} for next available slot that can accommodate the given span
677
+ const findNextAvailablePosition = (width, height, gridLayout) => {
678
+ for (let y = 0; y < maxRows; y++) {
679
+ for (let x = 0; x <= maxCols - width; x++) {
680
+ // Check if this position and the required span area are all free
681
+ let fits = true;
682
+ for (let dy = 0; dy < height && y + dy < maxRows; dy++) {
683
+ for (let dx = 0; dx < width && x + dx < maxCols; dx++) {
684
+ if (gridLayout[y + dy][x + dx] !== null) {
685
+ fits = false;
686
+ break;
687
+ }
688
+ }
689
+ if (!fits)
690
+ break;
691
+ }
692
+ if (fits) {
693
+ return { x, y };
694
+ }
695
+ }
696
+ }
697
+ // If no position found, return 0,0 (will be placed at first available)
698
+ return { x: 0, y: 0 };
699
+ };
700
+ // Helper function to find next available X position in a specific row
701
+ const findNextAvailableXInRow = (rowY, width, gridLayout) => {
702
+ for (let x = 0; x <= maxCols - width; x++) {
703
+ let fits = true;
704
+ for (let dx = 0; dx < width; dx++) {
705
+ if (gridLayout[rowY][x + dx] !== null) {
706
+ fits = false;
707
+ break;
708
+ }
709
+ }
710
+ if (fits)
711
+ return x;
712
+ }
713
+ return 0;
714
+ };
715
+ const cellsWithExplicitPosition = [];
716
+ const cellsWithYOnly = [];
717
+ const cellsWithXOnly = [];
718
+ const cellsWithAutoFlow = [];
675
719
  cellArr.forEach((cell, idx) => {
676
720
  if (!cell || !cell.Content)
677
721
  return;
678
- // Extract position information from cell attributes
679
- // Grid3 uses 1-based coordinates, convert to 0-based for internal use
680
- const cellX = Math.max(0, parseInt(String(cell['@_X'] || '1'), 10) - 1);
681
- const cellY = Math.max(0, parseInt(String(cell['@_Y'] || '1'), 10) - 1);
722
+ const hasX = cell['@_X'] !== undefined;
723
+ const hasY = cell['@_Y'] !== undefined;
724
+ if (hasX && hasY) {
725
+ cellsWithExplicitPosition.push({ cell, idx });
726
+ }
727
+ else if (hasY && !hasX) {
728
+ cellsWithYOnly.push({ cell, idx });
729
+ }
730
+ else if (!hasY && hasX) {
731
+ cellsWithXOnly.push({ cell, idx });
732
+ }
733
+ else {
734
+ cellsWithAutoFlow.push({ cell, idx });
735
+ }
736
+ });
737
+ // Process cells in order: explicit -> Y-only -> X-only -> auto-flow
738
+ const allCellsToProcess = [
739
+ ...cellsWithExplicitPosition,
740
+ ...cellsWithYOnly,
741
+ ...cellsWithXOnly,
742
+ ...cellsWithAutoFlow,
743
+ ];
744
+ allCellsToProcess.forEach(({ cell, idx }) => {
745
+ // Extract span information first
682
746
  const colSpan = parseInt(String(cell['@_ColumnSpan'] || '1'), 10);
683
747
  const rowSpan = parseInt(String(cell['@_RowSpan'] || '1'), 10);
748
+ // Determine position based on what attributes are present
749
+ const hasX = cell['@_X'] !== undefined;
750
+ const hasY = cell['@_Y'] !== undefined;
751
+ let cellX;
752
+ let cellY;
753
+ if (hasX && hasY) {
754
+ // Explicit position: both X and Y provided
755
+ // Grid 3 XML coordinates are already 0-based, use them directly
756
+ cellX = Math.max(0, parseInt(String(cell['@_X']), 10));
757
+ cellY = Math.max(0, parseInt(String(cell['@_Y']), 10));
758
+ }
759
+ else if (hasY && !hasX) {
760
+ // Y-only: auto-flow X in the specified row
761
+ // Grid 3 XML coordinates are already 0-based, use them directly
762
+ cellY = Math.max(0, parseInt(String(cell['@_Y']), 10));
763
+ cellX = findNextAvailableXInRow(cellY, colSpan, gridLayout);
764
+ }
765
+ else if (!hasY && hasX) {
766
+ // X-only: place at specified X in next available row
767
+ // Grid 3 XML coordinates are already 0-based, use them directly
768
+ cellX = Math.max(0, parseInt(String(cell['@_X']), 10));
769
+ // Find first row where this X position is available
770
+ cellY = 0;
771
+ let found = false;
772
+ for (let y = 0; y < maxRows; y++) {
773
+ let fits = true;
774
+ for (let dx = 0; dx < colSpan && cellX + dx < maxCols; dx++) {
775
+ if (gridLayout[y][cellX + dx] !== null) {
776
+ fits = false;
777
+ break;
778
+ }
779
+ }
780
+ if (fits) {
781
+ cellY = y;
782
+ found = true;
783
+ break;
784
+ }
785
+ }
786
+ if (!found) {
787
+ // No available row found, use auto-flow
788
+ const pos = findNextAvailablePosition(colSpan, rowSpan, gridLayout);
789
+ cellX = pos.x;
790
+ cellY = pos.y;
791
+ }
792
+ }
793
+ else {
794
+ // No position: auto-flow both X and Y
795
+ const pos = findNextAvailablePosition(colSpan, rowSpan, gridLayout);
796
+ cellX = pos.x;
797
+ cellY = pos.y;
798
+ }
684
799
  // Extract scan block number (1-8) for block scanning support
685
800
  const scanBlock = parseInt(String(cell['@_ScanBlock'] || '1'), 10);
686
801
  // Extract visibility from Grid 3's <Visibility> child element
@@ -819,10 +934,17 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
819
934
  const resolvedImageEntry = (0, resolver_1.resolveGrid3CellImage)(null, {
820
935
  baseDir,
821
936
  imageName: declaredImageName,
822
- x: cellX + 1,
823
- y: cellY + 1,
937
+ x: cellX,
938
+ y: cellY,
824
939
  dynamicFiles,
825
940
  }, entries) || undefined;
941
+ // Debug: log resolution for cells with images
942
+ if (declaredImageName && resolvedImageEntry) {
943
+ console.log(`[GridsetProcessor] Cell (${cellX + 1},${cellY + 1}) [XML coords]: ${declaredImageName} -> ${resolvedImageEntry}`);
944
+ }
945
+ else if (declaredImageName && !resolvedImageEntry) {
946
+ console.log(`[GridsetProcessor] Cell (${cellX + 1},${cellY + 1}) [XML coords]: ${declaredImageName} -> NOT FOUND`);
947
+ }
826
948
  // Check if image is a symbol library reference
827
949
  let symbolLibraryRef = null;
828
950
  if (declaredImageName && (0, resolver_2.isSymbolLibraryReference)(declaredImageName)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willwade/aac-processors",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
5
5
  "main": "dist/index.js",
6
6
  "browser": "dist/browser/index.browser.js",
@@ -80,13 +80,11 @@
80
80
  "files": [
81
81
  "dist/**/*",
82
82
  "docs/**/*",
83
- "examples/**/*",
84
83
  "README.md",
85
84
  "LICENSE"
86
85
  ],
87
86
  "directories": {
88
87
  "doc": "docs",
89
- "example": "examples",
90
88
  "test": "test"
91
89
  },
92
90
  "scripts": {
Binary file
package/examples/.keep DELETED
@@ -1 +0,0 @@
1
-
@@ -1,55 +0,0 @@
1
- # AAC Processors Example Pagesets
2
-
3
- This directory contains example AAC pagesets in various formats used for testing and demonstration purposes.
4
-
5
- ## Available Pagesets
6
-
7
- ### Grid3 Format (.gridset)
8
- - **example.gridset** - Main example pageset with multiple grids and wordlists
9
- - **example-images.gridset** - Example pageset with embedded images
10
-
11
- ### Snap Format (.spb, .sps)
12
- - **example.spb** - Snap pageset (binary format)
13
- - **example.sps** - Snap pageset (alternative format)
14
-
15
- ### TouchChat Format (.ce)
16
- - **example.ce** - TouchChat pageset
17
-
18
- ### OBF/OBZ Format (.obf, .obz)
19
- - **example.obf** - OBF pageset (JSON-based)
20
- - **example.obz** - OBZ pageset (compressed)
21
-
22
- **obf/** - Directory containing validation test samples from the obf-node project:
23
- - **simple.obf** - Simple, valid OBF file for basic validation tests
24
- - **aboutme.json** - Invalid OBF (missing locale field) for error testing
25
- - **hash.json** - Non-OBF JSON structure for format detection tests
26
- - **array.json** - JSON array (not object) for structure validation tests
27
- - **links.obz** - OBZ package with links for zip archive validation
28
-
29
- ### Asterics Grid Format (.grd)
30
- - **example.grd** - Asterics Grid pageset
31
- - **example2.grd** - Alternative Asterics Grid pageset
32
-
33
- ### DOT Format (.dot)
34
- - **example.dot** - Simple DOT format pageset
35
- - **communikate.dot** - Communikate DOT format pageset
36
-
37
- ### OPML Format (.opml)
38
- - **example.opml** - OPML pageset
39
-
40
- ### Styled Output
41
- - **styled-output/** - Directory containing example pagesets with styling applied
42
-
43
- ## Usage
44
-
45
- These pagesets are used by:
46
- - Unit tests in the main test suite
47
- - Demo scripts in the `scripts/` directory
48
- - Integration examples
49
-
50
- To run demo scripts that use these pagesets, see the [scripts/README.md](../scripts/README.md).
51
-
52
- ## Browser Demo (Vite)
53
-
54
- For browser testing, use the Vite demo in `examples/vitedemo`. It bundles the
55
- library and exercises real processors in a browser environment.