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:
commit
938add1bb0
3 changed files with 151 additions and 29 deletions
|
@ -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])}`
|
||||
);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue