diff --git a/.vscodeignore b/.vscodeignore
index 8839ade..0d23969 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -21,6 +21,8 @@ demo/**
.prettierignore
.prettierrc
.yarnrc
+.github/**
+.vscode-test/**
CODE_OF_CONDUCT.md
CONTRIBUTING.md
generateEmojiShortcodeMap.js
@@ -30,3 +32,9 @@ tsconfig.webviews.json
tsconfig.tsbuildinfo
webpack.config.js
yarn.lock
+node_modules/pdfjs-dist/legacy/**
+node_modules/pdfjs-dist/types/**
+node_modules/pdfjs-dist/web/**
+src/test/**
+demo/**
+docs/**
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f2d5c8..a548f4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Change Log
+## [0.6.0] - 2025-12-24
+
+### Major Changes
+
+- **Internal Rendering**: Removed dependency on external tools (`Ghostscript`, `Poppler`, `pdfcairo`). The extension now renders PostScript files internally using WebAssembly (`postscript-wasm`) and `pdf.js`.
+- **Zero Config**: Removed all path configuration settings. The extension works out of the box without any setup.
+
## [0.5.4] - 2025-12-23
- Fixed an issue where previewing files with special characters in the filename (e.g., spaces, parentheses) would fail with a syntax error.
diff --git a/README.md b/README.md
index a4e6b91..61eccff 100644
--- a/README.md
+++ b/README.md
@@ -7,10 +7,6 @@
-
-
-
-
PostScript Preview for VS Code
@@ -41,69 +37,14 @@
This extension requires:
- **[PostScript Language](https://marketplace.visualstudio.com/items?itemName=mxschmitt.postscript)** extension for syntax highlighting
-- **GhostScript** (provides `ps2pdf`)
-- **Poppler** (provides `pdftocairo` and `pdfinfo`)
-
-### macOS
-
-```bash
-brew install ghostscript poppler
-```
-
-### Ubuntu / Debian
-
-```bash
-sudo apt-get install ghostscript poppler-utils -y
-```
-
-### Windows
-
-Install via [Chocolatey](https://chocolatey.org/install) (run as Administrator):
-
-```powershell
-choco install ghostscript --version 9.55.0 --force -y
-choco install poppler --version 0.89.0 -y --force
-```
-
-Add to PATH:
-
-```powershell
-[Environment]::SetEnvironmentVariable("Path",[Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) + ";C:\Program Files\gs\gs9.55.0\lib;C:\Program Files\gs\gs9.55.0\bin;C:\ProgramData\chocolatey\lib\poppler\tools",[EnvironmentVariableTarget]::Machine)
-```
-**Restart VS Code** after installation.
-
-
-Manual PATH setup
-
-If you have issues setting PATH, add these manually via System Properties → Environment Variables:
-
-```
-C:\Program Files\gs\gs9.55.0\lib
-C:\Program Files\gs\gs9.55.0\bin
-C:\ProgramData\chocolatey\lib\poppler\tools
-```
-
-
+**Note:** Pre-0.6.0 versions required external installations of Ghostscript and Poppler. As of v0.6.0, these are **no longer required**! The extension now handles rendering internally using WebAssembly.
## Configuration
-Configure custom executable paths in VS Code settings (useful for conda environments or non-standard installations):
+The extension is zero-config!
-| Setting | Description | Default |
-| ------------------------------------ | ----------------------------- | ------------ |
-| `postscript-preview.path.ps2pdf` | Path to ps2pdf executable | `ps2pdf` |
-| `postscript-preview.path.pdftocairo` | Path to pdftocairo executable | `pdftocairo` |
-| `postscript-preview.path.pdfinfo` | Path to pdfinfo executable | `pdfinfo` |
-
-Example `settings.json`:
-
-```json
-{
- "postscript-preview.path.ps2pdf": "/opt/ghostscript/bin/ps2pdf",
- "postscript-preview.path.pdftocairo": "/opt/poppler/bin/pdftocairo"
-}
-```
+Previous configuration settings (`postscript-preview.path.*`) have been deprecated and removed as they are no longer needed.
## Multi-Page Documents
diff --git a/package.json b/package.json
index 89eac22..c797323 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "postscript-preview",
"displayName": "PostScript Preview",
"description": "PostScript Preview is an extension that helps to preview EPS and PS files in Visual Studio Code.",
- "version": "0.5.4",
+ "version": "0.6.0",
"icon": "images/logo.png",
"publisher": "ahnafnafee",
"engines": {
@@ -45,26 +45,6 @@
"group": "navigation"
}
]
- },
- "configuration": {
- "title": "PostScript Preview",
- "properties": {
- "postscript-preview.path.ps2pdf": {
- "type": "string",
- "default": "ps2pdf",
- "description": "Path to ps2pdf executable (from GhostScript). Use this if ps2pdf is not in your system PATH."
- },
- "postscript-preview.path.pdftocairo": {
- "type": "string",
- "default": "pdftocairo",
- "description": "Path to pdftocairo executable (from Poppler). Use this if pdftocairo is not in your system PATH."
- },
- "postscript-preview.path.pdfinfo": {
- "type": "string",
- "default": "pdfinfo",
- "description": "Path to pdfinfo executable (from Poppler). Used for detecting page count in multi-page documents."
- }
- }
}
},
"scripts": {
@@ -88,8 +68,10 @@
"typescript": "^5.9.3"
},
"dependencies": {
+ "@jspawn/ghostscript-wasm": "^0.0.2",
"@types/temp": "^0.9.4",
"glob": "^7.2.3",
+ "pdfjs-dist": "^5.4.449",
"temp": "^0.9.1"
},
"repository": {
diff --git a/src/config.ts b/src/config.ts
index e06dd17..661c8c1 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,17 +1,13 @@
/**
- * Configuration management for PostScript Preview extension
+ * Configurations for PostScript Preview
*/
import * as vscode from "vscode";
-import { ExtensionConfig } from "./types";
-/**
- * Get configuration values for executable paths
- */
-export function getConfig(): ExtensionConfig {
- const config = vscode.workspace.getConfiguration("postscript-preview");
- return {
- ps2pdf: config.get("path.ps2pdf", "ps2pdf"),
- pdftocairo: config.get("path.pdftocairo", "pdftocairo"),
- pdfinfo: config.get("path.pdfinfo", "pdfinfo"),
- };
+export interface Config {
+ // No path configurations needed for internal rendering
+}
+
+export function getConfig(): Config {
+ // Return empty config or any future settings
+ return {};
}
diff --git a/src/extension.ts b/src/extension.ts
index b98b986..221205e 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,7 +7,6 @@ import * as vscode from "vscode";
import path = require("path");
import { PreviewState } from "./types";
import { generatePreview } from "./preview";
-import { showWhatsNew } from "./whats-new";
/**
* Called when the extension is activated
@@ -18,7 +17,6 @@ export function activate(context: vscode.ExtensionContext): void {
if (isWindows) {
console.log("PostScript Preview: Checking for updates (Windows)...");
- showWhatsNew(context);
}
const channel = vscode.window.createOutputChannel("PostScript-Preview");
@@ -49,64 +47,10 @@ export function activate(context: vscode.ExtensionContext): void {
const mainFilePath = document.fileName;
+ // Generate preview without awaiting (fire and forget)
generatePreview(mainFilePath, panel, channel);
channel.appendLine(`Watching ${filePath}`);
- // Handle messages from webview for page navigation
- panel.webview.onDidReceiveMessage(
- (message) => {
- const state = (panel as any).__previewState as
- | PreviewState
- | undefined;
- if (!state) {
- return;
- }
-
- switch (message.command) {
- case "prevPage":
- if (state.currentPage > 1) {
- state.currentPage--;
- generatePreview(
- state.filepath,
- panel,
- channel,
- state.currentPage,
- state.pdfPath
- );
- }
- break;
- case "nextPage":
- if (state.currentPage < state.totalPages) {
- state.currentPage++;
- generatePreview(
- state.filepath,
- panel,
- channel,
- state.currentPage,
- state.pdfPath
- );
- }
- break;
- case "goToPage": {
- const page = parseInt(message.page, 10);
- if (page >= 1 && page <= state.totalPages) {
- state.currentPage = page;
- generatePreview(
- state.filepath,
- panel,
- channel,
- state.currentPage,
- state.pdfPath
- );
- }
- break;
- }
- }
- },
- undefined,
- context.subscriptions
- );
-
// Watch for file changes
const watcher = vscode.workspace.createFileSystemWatcher(filePath);
watcher.onDidChange((_: vscode.Uri) => {
diff --git a/src/preview.ts b/src/preview.ts
index cd663db..7ea268b 100644
--- a/src/preview.ts
+++ b/src/preview.ts
@@ -1,165 +1,84 @@
/**
* Preview generation for PostScript files
*/
-// biome-ignore lint/style/useNodejsImportProtocol:
-import { execSync, spawnSync } from "child_process";
import * as vscode from "vscode";
-import temp = require("temp");
-// biome-ignore lint/style/useNodejsImportProtocol:
-import fs = require("fs");
-// biome-ignore lint/style/useNodejsImportProtocol:
-import path = require("path");
-import { getConfig } from "./config";
-import { PreviewState } from "./types";
+import * as path from "path";
+// @ts-ignore
+import ghostscript = require("@jspawn/ghostscript-wasm");
import { getWebviewContent } from "./webview";
+export interface PreviewResult {
+ data: Uint8Array;
+ mimeType: string;
+}
+
/**
- * Get page count from PDF using pdfinfo
+ * Generate PDF buffer from PostScript file using Ghostscript WASM
*/
-export function getPageCount(
- pdfPath: string,
+export async function generatePdfFromPs(
+ filepath: string,
channel: vscode.OutputChannel
-): number {
- const config = getConfig();
+): Promise {
try {
- const result = execSync(`"${config.pdfinfo}" "${pdfPath}"`, {
- encoding: "utf-8",
- });
- const match = result.match(/Pages:\s+(\d+)/);
- if (match) {
- return parseInt(match[1], 10);
+ const fs = require("fs");
+ const psContent = fs.readFileSync(filepath);
+
+ // Initialize Ghostscript WASM
+ const gs = await ghostscript();
+
+ // Write PS file to virtual filesystem
+ gs.FS.writeFile("/input.ps", psContent);
+
+ // Execute gs command to convert to PDF
+ // eq to: gs -sDEVICE=pdfwrite -o output.pdf input.ps
+ const exitCode = gs.callMain([
+ "-sDEVICE=pdfwrite",
+ "-o",
+ "/output.pdf",
+ "/input.ps",
+ ]);
+
+ if (exitCode !== 0) {
+ throw new Error(`Ghostscript exited with code ${exitCode}`);
}
- } catch (err) {
- channel.appendLine(
- `Warning: Could not get page count using pdfinfo: ${err}`
- );
+
+ // Read the result PDF
+ const pdfData = gs.FS.readFile("/output.pdf");
+
+ // Cleanup virtual file system if needed (optional for short lived instances)
+ // gs.FS.unlink("/input.ps");
+ // gs.FS.unlink("/output.pdf");
+
+ return pdfData;
+ } catch (err: any) {
+ channel.appendLine(`Error generating PDF: ${err.message}`);
+ channel.show(true);
+ throw err;
}
- return 1; // Default to 1 page
}
/**
* Generate preview for a PostScript file
*/
-export function generatePreview(
+export async function generatePreview(
filepath: string,
panel: vscode.WebviewPanel,
channel: vscode.OutputChannel,
- pageNumber: number = 1,
- existingPdfPath?: string
-): string | undefined {
- const config = getConfig();
- temp.track();
+ pageNumber: number = 1
+): Promise {
+ try {
+ const pdfData = await generatePdfFromPs(filepath, channel);
- // Helper function to generate SVG from existing PDF
- const generateSvgFromPdf = (pdfPath: string, totalPages: number) => {
- temp.open(
- { prefix: "postscript-preview-svg_", suffix: ".svg" },
- (svgErr, svgInfo) => {
- if (svgErr) {
- console.log(
- "Creating temporary file eps-preview-svg failed."
- );
- return;
- }
- try {
- execSync(
- `"${config.pdftocairo}" -svg -f ${pageNumber} -l ${pageNumber} "${pdfPath}" "${svgInfo.path}"`
- );
- } catch (err) {
- vscode.window.showInformationMessage(
- "Failed to execute pdftocairo. Report bug with postscript file to dev."
- );
- console.log("Error executing pdftocairo.");
- console.log(err);
- temp.cleanupSync();
- return;
- }
- try {
- const stat = fs.fstatSync(svgInfo.fd);
- const svgContent = Buffer.alloc(stat.size);
- fs.readSync(svgInfo.fd, svgContent, 0, stat.size, null);
- // Show SVG in the webview panel
- panel.webview.html = getWebviewContent(
- path.basename(filepath),
- svgContent,
- pageNumber,
- totalPages
- );
- } catch (err) {
- console.log("Error reading the final file.");
- console.log(err);
- }
- }
- );
- };
+ // Convert to base64 to pass to webview
+ const base64Pdf = Buffer.from(pdfData).toString("base64");
- // If we have an existing PDF (page navigation), use it directly
- if (existingPdfPath) {
- const totalPages = getPageCount(existingPdfPath, channel);
- generateSvgFromPdf(existingPdfPath, totalPages);
- return existingPdfPath;
+ // Update webview
+ panel.webview.html = getWebviewContent(
+ path.basename(filepath),
+ base64Pdf,
+ pageNumber
+ );
+ } catch (err) {
+ vscode.window.showErrorMessage("Failed to generate preview.");
}
-
- // Otherwise, generate new PDF from PS/EPS file
- let pdfPathResult: string | undefined;
- temp.open(
- { prefix: "postscript-preview-svg_", suffix: ".pdf" },
- (pdfErr, pdfInfo) => {
- if (pdfErr) {
- console.log("Creating temporary file eps-preview-pdf failed.");
- return;
- }
- // Transform EPS to PDF using ps2pdf
- // Capture stdout/stderr for console output display (Issue #7)
- try {
- const ps2pdfResult = spawnSync(
- config.ps2pdf,
- ["-dEPSCrop", filepath, pdfInfo.path],
- { encoding: "utf-8", shell: false }
- );
-
- // Display any console output from GhostScript
- if (ps2pdfResult.stdout && ps2pdfResult.stdout.trim()) {
- channel.appendLine("--- GhostScript Output ---");
- channel.appendLine(ps2pdfResult.stdout);
- channel.show(true); // Show output channel without taking focus
- }
- if (ps2pdfResult.stderr && ps2pdfResult.stderr.trim()) {
- channel.appendLine("--- GhostScript Errors/Warnings ---");
- channel.appendLine(ps2pdfResult.stderr);
- channel.show(true);
- }
-
- if (ps2pdfResult.status !== 0) {
- throw new Error(
- `ps2pdf exited with code ${ps2pdfResult.status}`
- );
- }
- } catch (err) {
- vscode.window.showInformationMessage(
- "Failed to execute ps2pdf. Report bug with postscript file to dev."
- );
- console.log("Error executing ps2pdf.");
- console.log(err);
- temp.cleanupSync();
- return;
- }
-
- // Get page count for multi-page navigation
- const totalPages = getPageCount(pdfInfo.path, channel);
- pdfPathResult = pdfInfo.path;
-
- // Store state in webview for page navigation
- (panel as any).__previewState = {
- currentPage: pageNumber,
- totalPages: totalPages,
- pdfPath: pdfInfo.path,
- filepath: filepath,
- } as PreviewState;
-
- generateSvgFromPdf(pdfInfo.path, totalPages);
- }
- );
-
- return pdfPathResult;
}
diff --git a/src/test/suite/config.test.ts b/src/test/suite/config.test.ts
deleted file mode 100644
index 61a398e..0000000
--- a/src/test/suite/config.test.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as assert from "assert";
-import * as vscode from "vscode";
-import { getConfig } from "../../config";
-
-suite("Configuration Test Suite", () => {
- vscode.window.showInformationMessage("Start config tests.");
-
- test("Default configuration values", () => {
- const config = getConfig();
- assert.strictEqual(config.ps2pdf, "ps2pdf");
- assert.strictEqual(config.pdftocairo, "pdftocairo");
- assert.strictEqual(config.pdfinfo, "pdfinfo");
- });
- test("Configuration should be readable", () => {
- const config = vscode.workspace.getConfiguration("postscript-preview");
- assert.ok(config.has("path.ps2pdf"));
- assert.ok(config.has("path.pdftocairo"));
- });
-});
diff --git a/src/webview.ts b/src/webview.ts
index 934a9aa..ae2dbf4 100644
--- a/src/webview.ts
+++ b/src/webview.ts
@@ -1,222 +1,319 @@
/**
- * Webview content generation for PostScript Preview
- * Uses VS Code theme variables for automatic light/dark mode support
+ * Generates HTML content for the webview
*/
+import * as vscode from "vscode";
-/**
- * Generate complete webview HTML content
- * Uses CSS variables from VS Code for theme support
- */
-// biome-ignore lint/suspicious/noExplicitAny: SVG content can be string or Buffer
export function getWebviewContent(
- fileName: string,
- svgContent: any,
- currentPage: number = 1,
- totalPages: number = 1
+ filename: string,
+ pdfData: string, // Base64 encoded PDF
+ pageNumber: number = 1
): string {
- const showNav = totalPages > 1;
-
return `
-
-
- PostScript Preview
-
-
-
-
+
+
+
+ PostScript Preview
+
+
+
+
+
+
-