Public
Edited
Jul 17, 2023
Insert cell
Insert cell
import { example as pdf, PDF } from '@player1537/pdf-utilities';
Insert cell
pdf
Insert cell
{

const $missing = Symbol('missing');
const $undefined = Symbol('undefined');
const examples = {};
for (const op of PDF.OPS()) {
examples[op] = EXAMPLE(op);
}

return examples;

function EXAMPLE(op) {
const ch = PDF.op2ch(op);
const index = pdf.chs.indexOf(ch);
if (index < 0) return $missing;
for (let index=0;; ++index) {
index = pdf.chs.indexOf(ch, index);
if (index < 0) break;
const {
// fns: { [index]: fn },
args: { [index]: arg },
// ops: { [index]: op },
} = pdf;

if (arg !== undefined) return arg;
}
return $undefined;
}
}
Insert cell
PDF.OPS()
Insert cell
pdf.chs
Insert cell
JSON.stringify('km6cfe}ul'.split('').map((ch) => PDF.ch2op(ch)), true, 2)
Insert cell
BAKED = {
const ops = pdf.ops.slice();
const args = pdf.args.slice();

const ctx = DOM.context2d();

ctx.resetTransform();
for (let i=0, n=ops.length; i<n; ++i) {
const op = ops[i];
const arg = args[i];

if (false) {
} else if (op === 'transform') {
let [a, b, c, d, e, f] = arg;
ctx.transform(a, b, c, d, e, f);
EMIT(ctx.getTransform());
} else if (op === 'save') {
ctx.save();
EMIT(ctx.getTransform());
} else if (op === 'restore') {
ctx.restore();
EMIT(ctx.getTransform());
}

function EMIT({ a, b, c, d, e, f }) {
ops[i] = 'transform';
args[i] = [a, b, c, d, e, f];
}
}

const fns = ops.map((op) => PDF.op2fn(op));
const chs = ops.map((op) => PDF.op2ch(op)).join('');

return {
ops,
args,
fns,
chs,
};
}
Insert cell
PATHS = {
const pdf = BAKED;
const ops = [
// "transform",
"transform",
"setStrokeRGBColor",
"setLineWidth",
"setMiterLimit",
"setLineJoin",
"constructPath",
"stroke",
// "transform",
];

const chs = ops.map((op) => PDF.op2ch(op)).join('');

const paths = [];
let index = null;
for (let breakout=1024; breakout>0; --breakout) {
if (index == null) {
index = 0;
} else {
index = index + 1;
}
index = pdf.chs.indexOf(chs, index);
// return index;
if (index < 0) break;

const {
[ops.indexOf('transform')]: [sx, _skewX, _skewY, sy, tx, ty],
[ops.indexOf('setStrokeRGBColor')]: [r, g, b],
[ops.indexOf('constructPath')]: [fns, args, _minMax],
} = pdf.args.slice(index, index + chs.length);

if (_skewX != 0 || _skewY != 0) {
throw `x: ${_skewX} y: ${_skewY}`;
}

for (const path of PATHS({ sx, sy, tx, ty, fns, args })) {
EMIT({ path, r, g, b});
}

function EMIT({ path, r, g, b }) {
const { [paths.length-1]: prev } = paths;
if (prev && prev.r == r && prev.g == g && prev.b == b) {
prev.paths.push(path);

} else {
paths.push({ paths: [path], r, g, b });
}
}
}

return paths;

function PATHS({ sx, sy, tx, ty, fns, args }) {
fns = fns.slice();
args = args.slice();

const paths = [];
while (args.length > 0) {
// break;

const fn = fns.shift();
const op = PDF.fn2op(fn);
if (false) {
// for source code alignment
} else if (op === 'rectangle') {
const [x, y, width, height] = args.splice(0, 4);
continue; // skip
} else if (op === 'moveTo') {
const [x, y] = args.splice(0, 2);
EMITSTART({ x, y });
} else if (op === 'lineTo') {
const [x, y] = args.splice(0, 2);
EMIT({ x, y });
} else if (op === 'curveTo') {
const [cp1x, cp1y, cp2x, cp2y, x, y] = args.splice(0, 6);
EMIT({ x, y });
} else if (op === 'curveTo2') {
const [cp2x, cp2y, x, y] = args.splice(0, 4);
EMIT({ x, y });
} else if (op === 'curveTo3') {
const [cp1x, cp1y, x, y] = args.splice(0, 4);
EMIT({ x, y });
} else {
throw `Unexpected op: ${op}`;
}
}

return paths;

function EMITSTART({ x, y }) {
paths.push([]);
EMIT({ x, y });
}

function EMIT({ x, y }) {
x = sx * x + tx;
y = sy * y + ty;
const { [paths.length-1]: path } = paths;
path.push({ x, y });
}
}
}
Insert cell
{
const w = 1000;
const h = 1000;
const s = 2;
const ctx = DOM.context2d(w/s, h/s);

ctx.resetTransform();
ctx.translate(0, h); // applies "third"
ctx.scale(1.0, -1.0); // applies "second"
ctx.scale(1/s, 1/s); // applies "first"
return transform(ctx, {
x: 100,
y: 400,
});

function transform(ctx, { x, y }) {
// Thanks https://stackoverflow.com/a/62841369
//> const point = {x: 0, y: 0};
//> const matrix = ctx.getTransform();
//> const transformedPoint = {
//> x: matrix.a * point.x + matrix.c * point.y + matrix.e,
//> y: matrix.b * point.x + matrix.d * point.y + matrix.f,
//> };
const { a, b, c, d, e, f } = ctx.getTransform();
return {
x: a*x + c*y + e,
y: b*x + d*y + f,
};
}
}
Insert cell
{
const $pdf = await RENDER_PDF();
const w = +$pdf.width;
const h = +$pdf.height;
const s = 1;
const ctx = DOM.context2d(w/s, h/s);
ctx.resetTransform();
// ctx.translate(0, h);
// ctx.scale(1.0, -1.0);
ctx.translate(-w+500, 0);
ctx.scale(1/s, 1/s);

const $index = Inputs.range([0, PATHS.length-1], { value: 0, step: 1 });
yield htl.html`
<div>
${$index}
${ctx.canvas}
`;
for await (const index of Generators.input($index)) {
const { [index]: { paths, r, g, b } } = PATHS;

// ctx.clearRect(0, 0, w, h);
// ctx.save();
// ctx.resetTransform();
// ctx.scale(1/s, 1/s);
// ctx.translate(-500, 0);
ctx.globalCompositeOperation = 'copy';
ctx.drawImage($pdf, 0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
// ctx.restore();

for (const { lineWidth, strokeStyle } of [
{ lineWidth: 4, strokeStyle: 'black' },
{ lineWidth: 3, strokeStyle: `rgb(${r}, ${g}, ${b})` },
]) {
for (const path of paths) {
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
for (let i=1, n=path.length; i<n; ++i) {
ctx.lineTo(path[i].x, h-path[i].y);
}

Object.assign(ctx, {
lineWidth,
strokeStyle,
});
ctx.stroke();
}
}
}

async function RENDER_PDF() {
const viewport = pdf.page.getViewport({
scale: 1,
});
const ctx = DOM.context2d(viewport.width, viewport.height);
// ctx.scale(1/s, 1/s);
await pdf.page.render({
canvasContext: ctx,
viewport,
}).promise;
return ctx.canvas;
}
}
Insert cell
{
const $pdf = await RENDER_PDF();
const w = +$pdf.width;
const h = +$pdf.height;
const s = 2;
const ctx = DOM.context2d(w/s, h/s);
ctx.resetTransform();
// ctx.translate(0, h);
// ctx.scale(1.0, -1.0);
ctx.scale(1/s, 1/s);

const radius = 10;

const { [3]: { paths } } = PATHS;
const yes = [];
const no = paths.slice();
for await (const action of actions(yield ctx.canvas)) {
const { type } = action;
if (type === 'add' || type === 'remove') {
let { x, y } = action;
x = s * x;
y = h - s * y;

const { src, dst } = {
add: { src: no, dst: yes },
remove: { src: yes, dst: no },
}[type];

for (let i=0; i<src.length; ++i) {
const path = src[i];
for (const point of path) {
if (Math.abs(point.x - x) < radius && Math.abs(point.y - y) < radius) {
console.log(point);
dst.splice(-1, 0, ...src.splice(i, 1));
break;
}
}
}
}
// ctx.clearRect(0, 0, w, h);
ctx.save();
ctx.resetTransform();
ctx.scale(1/s, 1/s);
ctx.globalCompositeOperation = 'copy';
ctx.drawImage($pdf, 0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
ctx.restore();

const options = [
{ lineWidth: 4, strokeStyle: 'black', paths },
{ lineWidth: 3, strokeStyle: `black`, paths: yes },
{ lineWidth: 3, strokeStyle: `white`, paths: no },
];
for (const { lineWidth, strokeStyle, paths } of options) {
for (const path of paths) {
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
for (let i=1, n=path.length; i<n; ++i) {
ctx.lineTo(path[i].x, h-path[i].y);
}

Object.assign(ctx, {
lineWidth,
strokeStyle,
});
ctx.stroke();
}
}

const { x, y } = action;
ctx.beginPath();
ctx.arc(x*s, y*s, 10, 0.0, 2*Math.PI);
ctx.strokeStyle = 'red';
ctx.stroke();
}

async function RENDER_PDF() {
const viewport = pdf.page.getViewport({
scale: 1,
});
const ctx = DOM.context2d(viewport.width, viewport.height);
// ctx.scale(1/s, 1/s);
await pdf.page.render({
canvasContext: ctx,
viewport,
}).promise;
return ctx.canvas;
}

function actions(target) {
return Generators.observe((notify) => {
target.addEventListener('mousedown', onmouse);
target.addEventListener('mouseup', onmouse);

notify({
action: null,
x: null,
y: null,
});

return () => {
target.removeEventListener('mousedown', onmouse);
target.removeEventListener('mousemove', onmouse);
target.removeEventListener('mouseup', onmouse);
}
function onmouse(e) {
e.preventDefault();
const { type } = e;
if (type === 'mousedown') {
target.addEventListener('mousemove', onmouse);

} else if (type === 'mouseup') {
target.removeEventListener('mousemove', onmouse);
}
const { ctrlKey } = e;
const [x, y] = d3.pointer(e);

notify({
type: ctrlKey ? 'add' : 'remove',
x,
y,
});
}
});
}
}
Insert cell
CALIBRATION_POINTS = ({
sw: { x: 260.3723995361328, y: 431.95060156249997, lat: 35.473817, lng: -83.920740 },
ne: { x: 1382.9233995361328, y: 904.5586015625, lat: 35.763457, lng: -83.134908 },
})
Insert cell
{
const path = PATHS[3].paths[0];
const { sw, ne } = CALIBRATION_POINTS;
const x2lng = d3.scaleLinear([sw.x, ne.x], [sw.lng, ne.lng]);
const y2lat = d3.scaleLinear([sw.y, ne.y], [sw.lat, ne.lat]);

const ret = [];
for (const { x, y } of path) {
ret.push({
lng: x2lng(x),
lat: y2lat(y),
});
}

return htl.html`<textarea rows=24 cols=80>${JSON.stringify(ret, true, 2)}`;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more