-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpsviewer.html
116 lines (111 loc) · 4.91 KB
/
psviewer.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PostScript Viewer</title>
<style>
canvas { border: 1px solid black; }
</style>
</head>
<body>
<div>
<textarea id="input" rows="10" cols="50" placeholder="Enter your code here..."></textarea><br />
<button onclick="interpret()">Interpret</button>
</div>
<canvas id="canvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// different coordinate systems in canvas vs. postscript
// so rotate and flip horisontally
ctx.translate(canvas.width, canvas.height)
ctx.rotate(Math.PI);
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
// or .. vertically
// ctx.translate(0, canvas.height);
// ctx.scale(1, -1);
// stack to hold graphics states
const graphicsStateStack = [];
function interpretPostScript(script) {
const stack = [];
const commands = script.split('\n').flatMap(line => line.trim().split(/\s+/));
commands.forEach(command => {
if (!isNaN(command)) {
stack.push(parseFloat(command)); // push number
} else {
switch (command) {
case 'newpath':
ctx.beginPath(); // new path
break;
case 'moveto':
const yMove = stack.pop();
const xMove = stack.pop();
ctx.moveTo(xMove, yMove); // move to new point
break;
case 'lineto':
const yLine = stack.pop();
const xLine = stack.pop();
ctx.lineTo(xLine, yLine); // draw line to new point
break;
case 'arc':
const endAngle = stack.pop() * (Math.PI / 180); // ..to radians
const startAngle = stack.pop() * (Math.PI / 180); // ..to radians
const radi = stack.pop();
const yArc = stack.pop();
const xArc = stack.pop();
ctx.arc(xArc, yArc, radi, startAngle, endAngle); // draw arc
break;
case 'closepath':
ctx.closePath(); // close current path
break;
case 'gsave':
// save current state (transformations, line width, color)
graphicsStateStack.push({
lineWidth: ctx.lineWidth,
fillStyle: ctx.fillStyle,
strokeStyle: ctx.strokeStyle
});
break;
case 'grestore':
// restore most recent state
if (graphicsStateStack.length > 0) {
const state = graphicsStateStack.pop();
ctx.lineWidth = state.lineWidth;
ctx.fillStyle = state.fillStyle;
ctx.strokeStyle = state.strokeStyle;
}
break;
case 'setlinewidth':
const linewidth = stack.pop();
ctx.lineWidth = linewidth; // set line width
break;
case 'setgray':
const grayValue = stack.pop();
ctx.fillStyle = `rgba(${grayValue * 255}, ${grayValue * 255}, ${grayValue * 255}, 1)`;
ctx.strokeStyle = ctx.fillStyle; // set stroke color to same gray
break;
case 'fill':
ctx.fill(); // fill current path
break;
case 'stroke':
ctx.stroke(); // render path
break;
case 'showpage':
// no-op for this impl.
break;
case '#!PS': // maybe something needs here ...
// no-op for this
break;
}
}
});
}
function interpret() {
const postScript = document.getElementById('input').value;
interpretPostScript(postScript);
}
</script>
</body>
</html>