Files
Stirling-PDF/frontend/editor/public/vendor/jscanify/jscanify.js
T
0a50e765b7 Restructure/frontend editor (#6404)
## Move editor under `frontend/editor/`

Pure restructure: `frontend/` becomes the workspace, `frontend/editor/`
holds
  the PDF editor. 1775 file renames + 40 wiring edits. No logic changes.

  ### Why

`frontend/` is currently the editor — its `src/`, `public/`,
`src-tauri/`,
  config files all sit at the root. Promoting `frontend/` to a
workspace and putting the editor in a sibling folder leaves room for
future
apps to drop in alongside it, sharing one `package.json` /
`node_modules` /
  lint config / Storybook.

  ### What moves

  frontend/
  ├── editor/                ← NEW: everything editor-specific
  │   ├── src/               ← was frontend/src/
  │   ├── public/            ← was frontend/public/
  │   ├── src-tauri/         ← was frontend/src-tauri/
│ ├── index.html, vite.config.ts, vitest.config.ts, playwright.config.ts
  │   ├── tsconfig*.json, tailwind.config.js, postcss.config.js
  │   ├── scripts/
  │   ├── .env, .env.desktop, .env.saas
  │   └── DeveloperGuide.md
├── package.json, package-lock.json, node_modules/ ← workspace install
  ├── eslint.config.mjs, .prettierrc, .prettierignore ← shared tooling
  ├── .gitignore
  └── README.md

  ### Wiring edits (40 files)

  - `.taskfiles/frontend.yml`, `desktop.yml`, `e2e.yml`
  - `build.gradle`, `app/core/build.gradle`
- `eslint.config.mjs`, `frontend/package.json`, `.gitignore`,
`.prettierignore`
  - `docker/frontend/Dockerfile`
  - 8 `.github/workflows/*.yml`, plus `.github/dependabot.yml`,
    `.github/config/.files.yaml`, `.github/labeler-config-srvaroa.yml`
  - `scripts/translations/**`
- Docs: `AGENTS.md`, `CLAUDE.md`, `ADDING_TOOLS.md`,
`DeveloperGuide.md`,
`WINDOWS_SIGNING.md`, `devGuide/HowToAddNewLanguage.md`,
`frontend/README.md`,
    `frontend/editor/DeveloperGuide.md`

Plus 3 renamed + edited: `editor/vite.config.ts` (env path +
node_modules
  walk-up), `editor/scripts/setup-env.mts` (renamed from `.ts` for
`import.meta.url`), `editor/scripts/build-provisioner.mjs` (resolve
src-tauri
  relative to script).

  ### Verification

  | Check | Result |
  |---|---|
  | `task frontend:typecheck:all` (6 variants) | exit 0 |
  | `task frontend:lint` (eslint + dpdm) | exit 0 |
  | `task frontend:format:check` | exit 0 |
  | `task frontend:test` | 657 tests pass, 50 files |
| `task frontend:build:{core,proprietary,saas,desktop,prototypes}` | all
green |
| `task desktop:build` | full Tauri pipeline →
`Stirling-PDF_2.11.0_x64_en-US.msi` |
  | `playwright test --list --project=stubbed` | 172 tests discovered |

`task desktop:build` exercises the heaviest path — Rust + WiX + MSI
bundle
against the moved `editor/src-tauri/`. If anything in the restructure
was
  wrong it wouldn't have built.

  ### Test plan

  - [ ] `frontend-validation.yml` green
  - [ ] `e2e-stubbed.yml` green
  - [ ] `tauri-build.yml` green on at least one platform
  - [ ] `check_toml.yml` runs on a translation-touching PR

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-22 13:40:34 +01:00

264 lines
7.1 KiB
JavaScript

/*! jscanify v1.4.0 | (c) ColonelParrot and other contributors | MIT License */
(function (global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? (module.exports = factory())
: typeof define === "function" && define.amd
? define(factory)
: (global.jscanify = factory());
})(this, function () {
"use strict";
/**
* Calculates distance between two points. Each point must have `x` and `y` property
* @param {*} p1 point 1
* @param {*} p2 point 2
* @returns distance between two points
*/
function distance(p1, p2) {
return Math.hypot(p1.x - p2.x, p1.y - p2.y);
}
class jscanify {
constructor() { }
/**
* Finds the contour of the paper within the image
* @param {*} img image to process (cv.Mat)
* @returns the biggest contour inside the image
*/
findPaperContour(img) {
const imgGray = new cv.Mat();
cv.Canny(img, imgGray, 50, 200);
const imgBlur = new cv.Mat();
cv.GaussianBlur(
imgGray,
imgBlur,
new cv.Size(3, 3),
0,
0,
cv.BORDER_DEFAULT
);
const imgThresh = new cv.Mat();
cv.threshold(
imgBlur,
imgThresh,
0,
255,
cv.THRESH_OTSU
);
let contours = new cv.MatVector();
let hierarchy = new cv.Mat();
cv.findContours(
imgThresh,
contours,
hierarchy,
cv.RETR_CCOMP,
cv.CHAIN_APPROX_SIMPLE
);
let maxArea = 0;
let maxContourIndex = -1;
for (let i = 0; i < contours.size(); ++i) {
let contourArea = cv.contourArea(contours.get(i));
if (contourArea > maxArea) {
maxArea = contourArea;
maxContourIndex = i;
}
}
const maxContour =
maxContourIndex >= 0 ?
contours.get(maxContourIndex) :
null;
imgGray.delete();
imgBlur.delete();
imgThresh.delete();
contours.delete();
hierarchy.delete();
return maxContour;
}
/**
* Highlights the paper detected inside the image.
* @param {*} image image to process
* @param {*} options options for highlighting. Accepts `color` and `thickness` parameter
* @returns `HTMLCanvasElement` with original image and paper highlighted
*/
highlightPaper(image, options) {
options = options || {};
options.color = options.color || "orange";
options.thickness = options.thickness || 10;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = cv.imread(image);
const maxContour = this.findPaperContour(img);
cv.imshow(canvas, img);
if (maxContour) {
const {
topLeftCorner,
topRightCorner,
bottomLeftCorner,
bottomRightCorner,
} = this.getCornerPoints(maxContour, img);
if (
topLeftCorner &&
topRightCorner &&
bottomLeftCorner &&
bottomRightCorner
) {
ctx.strokeStyle = options.color;
ctx.lineWidth = options.thickness;
ctx.beginPath();
ctx.moveTo(...Object.values(topLeftCorner));
ctx.lineTo(...Object.values(topRightCorner));
ctx.lineTo(...Object.values(bottomRightCorner));
ctx.lineTo(...Object.values(bottomLeftCorner));
ctx.lineTo(...Object.values(topLeftCorner));
ctx.stroke();
}
}
img.delete();
return canvas;
}
/**
* Extracts and undistorts the image detected within the frame.
*
* Returns `null` if no paper is detected.
*
* @param {*} image image to process
* @param {*} resultWidth desired result paper width
* @param {*} resultHeight desired result paper height
* @param {*} cornerPoints optional custom corner points, in case automatic corner points are incorrect
* @returns `HTMLCanvasElement` containing undistorted image
*/
extractPaper(image, resultWidth, resultHeight, cornerPoints) {
const canvas = document.createElement("canvas");
const img = cv.imread(image);
const maxContour = cornerPoints ? null : this.findPaperContour(img);
if(maxContour == null && cornerPoints === undefined){
return null;
}
const {
topLeftCorner,
topRightCorner,
bottomLeftCorner,
bottomRightCorner,
} = cornerPoints || this.getCornerPoints(maxContour, img);
let warpedDst = new cv.Mat();
let dsize = new cv.Size(resultWidth, resultHeight);
let srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, [
topLeftCorner.x,
topLeftCorner.y,
topRightCorner.x,
topRightCorner.y,
bottomLeftCorner.x,
bottomLeftCorner.y,
bottomRightCorner.x,
bottomRightCorner.y,
]);
let dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, [
0,
0,
resultWidth,
0,
0,
resultHeight,
resultWidth,
resultHeight,
]);
let M = cv.getPerspectiveTransform(srcTri, dstTri);
cv.warpPerspective(
img,
warpedDst,
M,
dsize,
cv.INTER_LINEAR,
cv.BORDER_CONSTANT,
new cv.Scalar()
);
cv.imshow(canvas, warpedDst);
img.delete()
warpedDst.delete()
return canvas;
}
/**
* Calculates the corner points of a contour.
* @param {*} contour contour from {@link findPaperContour}
* @returns object with properties `topLeftCorner`, `topRightCorner`, `bottomLeftCorner`, `bottomRightCorner`, each with `x` and `y` property
*/
getCornerPoints(contour) {
let rect = cv.minAreaRect(contour);
const center = rect.center;
let topLeftCorner;
let topLeftCornerDist = 0;
let topRightCorner;
let topRightCornerDist = 0;
let bottomLeftCorner;
let bottomLeftCornerDist = 0;
let bottomRightCorner;
let bottomRightCornerDist = 0;
for (let i = 0; i < contour.data32S.length; i += 2) {
const point = { x: contour.data32S[i], y: contour.data32S[i + 1] };
const dist = distance(point, center);
if (point.x < center.x && point.y < center.y) {
// top left
if (dist > topLeftCornerDist) {
topLeftCorner = point;
topLeftCornerDist = dist;
}
} else if (point.x > center.x && point.y < center.y) {
// top right
if (dist > topRightCornerDist) {
topRightCorner = point;
topRightCornerDist = dist;
}
} else if (point.x < center.x && point.y > center.y) {
// bottom left
if (dist > bottomLeftCornerDist) {
bottomLeftCorner = point;
bottomLeftCornerDist = dist;
}
} else if (point.x > center.x && point.y > center.y) {
// bottom right
if (dist > bottomRightCornerDist) {
bottomRightCorner = point;
bottomRightCornerDist = dist;
}
}
}
return {
topLeftCorner,
topRightCorner,
bottomLeftCorner,
bottomRightCorner,
};
}
}
return jscanify;
});