1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 22:58:07 +02:00

Merge pull request #19396 from calixteman/update_signature_draw

[Editor] Add some functions in order to extract contours from text and to generate a drawing from a drawn signature
This commit is contained in:
calixteman 2025-01-31 15:44:00 +01:00 committed by GitHub
commit 938add1bb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 151 additions and 29 deletions

View file

@ -344,7 +344,7 @@ class InkDrawOutline extends Outline {
buffer.push("Z");
continue;
}
if (line.length === 12) {
if (line.length === 12 && isNaN(line[6])) {
buffer.push(
`L${Outline.svgRound(line[10])} ${Outline.svgRound(line[11])}`
);

View file

@ -14,6 +14,7 @@
*/
import { ContourDrawOutline } from "./contour.js";
import { InkDrawOutline } from "./inkdraw.js";
import { Outline } from "./outline.js";
/**
@ -343,6 +344,14 @@ class SignatureExtractor {
return [out, histogram];
}
static #getHistogram(buf) {
const histogram = new Uint32Array(256);
for (const g of buf) {
histogram[g]++;
}
return histogram;
}
static #toUint8(buf) {
// We have a RGBA buffer, containing a grayscale image.
// We want to convert it into a basic G buffer.
@ -479,9 +488,85 @@ class SignatureExtractor {
return [uint8Buf, newWidth, newHeight];
}
static extractContoursFromText(
text,
{ fontFamily, fontStyle, fontWeight },
pageWidth,
pageHeight,
rotation,
innerMargin
) {
let canvas = new OffscreenCanvas(1, 1);
let ctx = canvas.getContext("2d", { alpha: false });
const fontSize = 200;
const font =
(ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`);
const {
actualBoundingBoxLeft,
actualBoundingBoxRight,
actualBoundingBoxAscent,
actualBoundingBoxDescent,
fontBoundingBoxAscent,
fontBoundingBoxDescent,
width,
} = ctx.measureText(text);
// We rescale the canvas to make "sure" the text fits.
const SCALE = 1.5;
const canvasWidth = Math.ceil(
Math.max(
Math.abs(actualBoundingBoxLeft) + Math.abs(actualBoundingBoxRight) || 0,
width
) * SCALE
);
const canvasHeight = Math.ceil(
Math.max(
Math.abs(actualBoundingBoxAscent) +
Math.abs(actualBoundingBoxDescent) || fontSize,
Math.abs(fontBoundingBoxAscent) + Math.abs(fontBoundingBoxDescent) ||
fontSize
) * SCALE
);
canvas = new OffscreenCanvas(canvasWidth, canvasHeight);
ctx = canvas.getContext("2d", { alpha: true, willReadFrequently: true });
ctx.font = font;
ctx.filter = "grayscale(1)";
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.fillStyle = "black";
ctx.fillText(
text,
(canvasWidth * (SCALE - 1)) / 2,
(canvasHeight * (3 - SCALE)) / 2
);
const uint8Buf = this.#toUint8(
ctx.getImageData(0, 0, canvasWidth, canvasHeight).data
);
const histogram = this.#getHistogram(uint8Buf);
const threshold = this.#guessThreshold(histogram);
const contourList = this.#findContours(
uint8Buf,
canvasWidth,
canvasHeight,
threshold
);
return this.processDrawnLines({
lines: { curves: contourList, width: canvasWidth, height: canvasHeight },
pageWidth,
pageHeight,
rotation,
innerMargin,
mustSmooth: true,
areContours: true,
});
}
static process(bitmap, pageWidth, pageHeight, rotation, innerMargin) {
const [uint8Buf, width, height] = this.#getGrayPixels(bitmap);
const [uint8Filtered, histogram] = this.#bilateralFilter(
const [buffer, histogram] = this.#bilateralFilter(
uint8Buf,
width,
height,
@ -491,32 +576,55 @@ class SignatureExtractor {
);
const threshold = this.#guessThreshold(histogram);
const contourList = this.#findContours(
uint8Filtered,
width,
height,
threshold
);
const linesAndPoints = [];
const contourList = this.#findContours(buffer, width, height, threshold);
return this.processDrawnLines({
lines: { curves: contourList, width, height },
pageWidth,
pageHeight,
rotation,
innerMargin,
mustSmooth: true,
areContours: true,
});
}
static processDrawnLines({
lines,
pageWidth,
pageHeight,
rotation,
innerMargin,
mustSmooth,
areContours,
}) {
if (rotation % 180 !== 0) {
[pageWidth, pageHeight] = [pageHeight, pageWidth];
}
// The points need to be converted into page coordinates.
const ratio = 0.5 * Math.min(pageWidth / width, pageHeight / height);
const { curves, thickness, width, height } = lines;
const linesAndPoints = [];
const ratio = Math.min(pageWidth / width, pageHeight / height);
const xScale = ratio / pageWidth;
const yScale = ratio / pageHeight;
for (const { points } of contourList) {
const reducedPoints = this.#douglasPeucker(points);
for (const { points } of curves) {
const reducedPoints = mustSmooth ? this.#douglasPeucker(points) : points;
if (!reducedPoints) {
continue;
}
const len = reducedPoints.length;
const newPoints = new Float32Array(len);
const line = new Float32Array(3 * (len - 2));
const line = new Float32Array(3 * (len === 2 ? 2 : len - 2));
linesAndPoints.push({ line, points: newPoints });
if (len === 2) {
newPoints[0] = reducedPoints[0] * xScale;
newPoints[1] = reducedPoints[1] * yScale;
line.set([NaN, NaN, NaN, NaN, newPoints[0], newPoints[1]], 0);
continue;
}
let [x1, y1, x2, y2] = reducedPoints;
x1 *= xScale;
@ -532,17 +640,23 @@ class SignatureExtractor {
line.set(Outline.createBezierPoints(x1, y1, x2, y2, x, y), (i - 2) * 3);
[x1, y1, x2, y2] = [x2, y2, x, y];
}
linesAndPoints.push({ line, points: newPoints });
}
const outline = new ContourDrawOutline();
if (linesAndPoints.length === 0) {
return null;
}
const outline = areContours
? new ContourDrawOutline()
: new InkDrawOutline();
outline.build(
linesAndPoints,
pageWidth,
pageHeight,
1,
rotation,
0,
areContours ? 0 : thickness,
innerMargin
);

View file

@ -131,21 +131,29 @@ class SignatureEditor extends DrawingEditor {
input.click();
const bitmap = await promise;
if (!bitmap?.bitmap) {
this.remove();
return;
}
const {
rawDims: { pageWidth, pageHeight },
rotation,
} = this.parent.viewport;
const drawOutlines = SignatureExtractor.process(
bitmap.bitmap,
pageWidth,
pageHeight,
rotation,
SignatureEditor._INNER_MARGIN
);
let drawOutlines;
if (bitmap?.bitmap) {
drawOutlines = SignatureExtractor.process(
bitmap.bitmap,
pageWidth,
pageHeight,
rotation,
SignatureEditor._INNER_MARGIN
);
} else {
drawOutlines = SignatureExtractor.extractContoursFromText(
"Hello PDF.js' World !!",
{ fontStyle: "italic", fontWeight: "400", fontFamily: "cursive" },
pageWidth,
pageHeight,
rotation,
SignatureEditor._INNER_MARGIN
);
}
this._addOutlines({
drawOutlines,
drawingOptions: SignatureEditor.getDefaultDrawingOptions(),