Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 0 additions & 34 deletions .github/workflows/playwright-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,6 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: |
playwright-report/
test-results/
retention-days: 30

test-cursor:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Playwright Browsers
run: npx playwright install --with-deps

- name: Build extension
run: npm run compile

- name: Run Cursor-specific tests
run: npx playwright test cursor-test.spec.ts

- name: Upload Cursor test results
if: always()
uses: actions/upload-artifact@v4
with:
name: cursor-test-results
path: |
playwright-report/
test-results/
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules
out/
*.tif
# Keep the small TIFF fixtures used by the WASM decoder tests.
!test-samples/*.tif
*.vsix
yarn.lock
example/
Expand All @@ -12,7 +14,7 @@ test-workspace/
playwright-report/
test-resources/

package-lock.json
# package-lock.json is committed so CI's `npm ci` has a lockfile to install from.

# Webpack
*.webpack.config.js
Expand Down
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "npm: compile"
"preLaunchTask": "npm: compile:quick"
}
]
}
14 changes: 14 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@
"panel": "shared"
},
"problemMatcher": []
},
{
"type": "npm",
"script": "compile:quick",
"label": "npm: compile:quick",
"detail": "Build the extension with esbuild only (reuses the committed media/wasm/tiff-wasm.wasm; no Rust/wasm-pack required)",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared"
},
"problemMatcher": []
}
]
}
48 changes: 45 additions & 3 deletions media/modules/ppm-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,38 @@ export class PpmProcessor {
return token;
};

// Read a numeric header field (width/height/maxval). Unlike readToken,
// this stops at the first non-digit, so it never swallows binary raster
// bytes when the single whitespace before the data is missing or when a
// data byte merely happens not to be whitespace.
const readNumber = () => {
// Skip whitespace and comments (same rules as readToken).
while (offset < uint8Array.length) {
const char = uint8Array[offset];
if (char === 35) { // '#' - comment
while (offset < uint8Array.length && uint8Array[offset] !== 10) {
offset++;
}
if (offset < uint8Array.length) offset++; // Skip newline
} else if (char === 32 || char === 9 || char === 10 || char === 13) {
offset++;
} else {
break;
}
}
let token = '';
while (offset < uint8Array.length) {
const char = uint8Array[offset];
if (char >= 48 && char <= 57) { // '0'-'9'
token += String.fromCharCode(char);
offset++;
} else {
break;
}
}
return parseInt(token, 10);
};

// Read magic number
const magic = readToken();
if (!['P1', 'P2', 'P3', 'P4', 'P5', 'P6'].includes(magic)) {
Expand All @@ -125,10 +157,10 @@ export class PpmProcessor {
const isPbm = magic === 'P1' || magic === 'P4';

// Read dimensions
const width = parseInt(readToken(), 10);
const height = parseInt(readToken(), 10);
const width = readNumber();
const height = readNumber();
// PBM files don't have maxval, only PGM/PPM do
const maxval = isPbm ? 1 : parseInt(readToken(), 10);
const maxval = isPbm ? 1 : readNumber();

if (width <= 0 || height <= 0 || (!isPbm && maxval <= 0)) {
throw new Error('Invalid PPM/PGM/PBM dimensions or maxval');
Expand All @@ -155,6 +187,16 @@ export class PpmProcessor {
}
} else if (isPbm && !isAscii) {
// PBM binary format (P4) - packed bits
// PBM spec: exactly one whitespace separates the header from the
// raster data. Skip it (matching the P5/P6 branch below); tolerate
// its absence in slightly malformed files.
if (offset < uint8Array.length) {
const char = uint8Array[offset];
if (char === 32 || char === 9 || char === 10 || char === 13) {
offset++;
}
}

const bytesPerRow = Math.ceil(width / 8);
const expectedBytes = bytesPerRow * height;

Expand Down
7 changes: 5 additions & 2 deletions media/modules/tiff-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ export class TiffProcessor {
let workerTiffFailed = false;
/** @type {ArrayBuffer|null} */
let localBuffer = buffer;
if (!use24BitMode && this.decodeWorker?.canDecode('tiff')) {
// 24-bit grayscale is a post-decode reinterpretation (combine R/G/B
// into one value), handled later in renderTiff/ImageRenderer, so the
// Rust/WASM decoder can decode these images like any other RGB TIFF.
if (this.decodeWorker?.canDecode('tiff')) {
const workerStart = performance.now();
const workerResponse = await this.decodeWorker.decode('tiff', buffer);
if (loadSignal?.aborted) { throw new DOMException('Load superseded', 'AbortError'); }
Expand All @@ -157,7 +160,7 @@ export class TiffProcessor {
}
}

const useWasm = !wasmResult && !workerTiffFailed && this._wasmAvailable && !use24BitMode;
const useWasm = !wasmResult && !workerTiffFailed && this._wasmAvailable;
console.log(`[TiffProcessor] Decode decision: worker=${!!wasmResult}, wasmAvailable=${this._wasmAvailable}, 24BitMode=${use24BitMode}, willUseWasm=${useWasm}`);

// Local WASM decoding when the worker isn't available
Expand Down
Binary file modified media/wasm/tiff-wasm.wasm
Binary file not shown.
Loading
Loading