Skip to content

Commit

Permalink
Work for surveyjs/survey-pdf#348: fix signaturepad's fromUrl method w…
Browse files Browse the repository at this point in the history
…orks incorrectly when question is not visible
  • Loading branch information
dk981234 committed Nov 11, 2024
1 parent ba076cd commit b5e36c3
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 11 deletions.
32 changes: 22 additions & 10 deletions packages/survey-core/src/question_signaturepad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SurveyModel } from "./survey";
import { ConsoleWarnings } from "./console-warnings";
import { ITheme } from "./themes";
import { dataUrl2File, FileLoader, QuestionFileModelBase } from "./question_file";
import { isBase64URL } from "./utils/utils";

var defaultWidth = 300;
var defaultHeight = 200;
Expand Down Expand Up @@ -47,6 +48,7 @@ export class QuestionSignaturePadModel extends QuestionFileModelBase {
}
protected updateValue() {
if (this.signaturePad) {

var data = this.signaturePad.toDataURL(this.getFormat());
this.valueIsUpdatingInternally = true;
this.value = data;
Expand Down Expand Up @@ -107,16 +109,26 @@ export class QuestionSignaturePadModel extends QuestionFileModelBase {

private fromUrl(url: string): void {
this.isFileLoading = true;
const img = new Image();
img.crossOrigin = "anonymous";
img.src = url;
img.onload = () => {
const ctx = this.canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var dataURL = this.canvas.toDataURL(this.getFormat());
this.fromDataUrl(dataURL);
if(isBase64URL(url)) {
this.fromDataUrl(url);
this.isFileLoading = false;
};
} else {
const img = new Image();
img.crossOrigin = "anonymous";
img.src = url;
img.onload = () => {
if(!!this.canvas) {
const ctx = this.canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var dataURL = this.canvas.toDataURL(this.getFormat());
this.fromDataUrl(dataURL);
}
this.isFileLoading = false;
};
img.onerror = () => {
this.isFileLoading = false;
};
}
}
private fromDataUrl(data: string) {
this._loadedData = data;
Expand Down Expand Up @@ -151,10 +163,10 @@ export class QuestionSignaturePadModel extends QuestionFileModelBase {
this._previewLoader = new FileLoader(this, (status, loaded) => {
if (status === "success" && loaded && loaded.length > 0 && loaded[0].content) {
this.fromDataUrl(loaded[0].content);
this.isFileLoading = false;
} else if (status === "skipped") {
this.fromUrl(newValue);
}
this.isFileLoading = false;
this._previewLoader.dispose();
this._previewLoader = undefined;
});
Expand Down
7 changes: 7 additions & 0 deletions packages/survey-core/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ function navigateToUrl(url: string): void {
function wrapUrlForBackgroundImage(url: string): string {
return !!url ? ["url(", url, ")"].join("") : "";
}
function isBase64URL(url: string): boolean {
if(typeof url == "string") {
return /^data:((?:\w+\/(?:(?!;).)+)?)((?:;[\w\W]*?[^;])*),/.test(url);

Check failure

Code scanning / CodeQL

Inefficient regular expression High

This part of the regular expression may cause exponential backtracking on strings starting with 'data:;' and containing many repetitions of ':;'.
}
return null;
}

// old-name: new-name
const renamedIcons:any = {
Expand Down Expand Up @@ -820,5 +826,6 @@ export {
findParentByClassNames,
getFirstVisibleChild,
chooseFiles,
isBase64URL,
renamedIcons
};
53 changes: 53 additions & 0 deletions packages/survey-core/tests/question_signaturepadtests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,59 @@ QUnit.test("Check isReady flag with onDownloadFile callback", (assert) => {
el.remove();
});

QUnit.test("Check storeDataAsText: false and base64 data", (assert) => {
const survey = new SurveyModel({
questions: [
{
type: "signaturepad",
name: "signature",
storeDataAsText: false,
}
],
});
const question = <QuestionSignaturePadModel>survey.getAllQuestions()[0];
assert.equal(question.isReady, true, "question is ready before data");
let log = "";
question.onReadyChanged.add((_, opt) => {
log += `->${opt.isReady}`;
});
const base64Url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII";
survey.data = {
signature: base64Url
};
assert.equal(question.loadedData, base64Url);
assert.equal(log, "->false->true", "isReady changed only one time");
});

QUnit.test("Check storeDataAsText: false and no download file callback and incorrect link passed", (assert) => {
const done = assert.async();
const survey = new SurveyModel({
questions: [
{
type: "signaturepad",
name: "signature",
storeDataAsText: false,
}
],
});
const question = <QuestionSignaturePadModel>survey.getAllQuestions()[0];
assert.equal(question.isReady, true, "question is ready before data");
let log = "";
question.onReadyChanged.add((_, opt) => {
log += `->${opt.isReady}`;
});
const url = "http://localhost:7777/image.jpg";
survey.data = {
signature: url
};
setTimeout(() => {
assert.equal(question.loadedData, undefined);
assert.equal(question.value, url);
assert.equal(log, "->false->true", "isReady changed only one time");
done();
}, 100);
});

QUnit.test("Check signature image cached in loadedData and loaded only once until value changed", (assert) => {
var el = document.createElement("div");
var canv = document.createElement("canvas");
Expand Down
10 changes: 9 additions & 1 deletion packages/survey-core/tests/utilstests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IAction } from "../src/actions/action";
import { defaultListCss } from "../src/list";
import { createSvg, doKey2ClickDown, doKey2ClickUp, sanitizeEditableContent, configConfirmDialog, getSafeUrl, compareArrays, setPropertiesOnElementForAnimation, cleanHtmlElementAfterAnimation } from "../src/utils/utils";
import { createSvg, doKey2ClickDown, doKey2ClickUp, sanitizeEditableContent, configConfirmDialog, getSafeUrl, compareArrays, setPropertiesOnElementForAnimation, cleanHtmlElementAfterAnimation, isBase64URL } from "../src/utils/utils";
import { mouseInfo, detectMouseSupport, MatchMediaMethod } from "../src/utils/devices";
import { PopupBaseViewModel } from "../src/popup-view-model";
import { PopupModel } from "../src/popup";
Expand Down Expand Up @@ -1069,4 +1069,12 @@ QUnit.test("animation helper functions", (assert) => {
assert.equal(el["__sv_created_properties"], undefined);
assert.equal(el.style.getPropertyValue("--animation-height"), "");
assert.equal(el.style.getPropertyValue("--animation-margin-top"), "");
});

QUnit.test("test isBase64", (assert) => {
assert.ok(isBase64URL("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"));
assert.ok(isBase64URL("data:image/jpg;base64,AAA"));
assert.notOk(isBase64URL("iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"));
assert.notOk(isBase64URL("image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"));
assert.notOk(isBase64URL("https://localhost:7777/image.jpg"));
});

0 comments on commit b5e36c3

Please sign in to comment.