diff --git a/.changeset/thin-apes-juggle.md b/.changeset/thin-apes-juggle.md new file mode 100644 index 0000000..c1485b4 --- /dev/null +++ b/.changeset/thin-apes-juggle.md @@ -0,0 +1,8 @@ +--- +"taktische-zeichen-core": patch +"taktische-zeichen-cli": patch +"taktische-zeichen-react": patch +"taktische-zeichen-web-component": patch +--- + +Bugfix: Encoding von UTF-8 Text im Browser diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index 269fb33..e6134f2 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -8,6 +8,9 @@ "name": "taktische-zeichen-core", "version": "0.7.0", "license": "MIT", + "dependencies": { + "base64-js": "1.5.1" + }, "devDependencies": { "@types/jest": "^27.4.0", "@types/node": "^20.11.5", @@ -1268,6 +1271,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5215,6 +5237,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index 7635141..b3bf91d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,6 +32,9 @@ "format": "prettier --write src", "statistics": "ts-node -P tsconfig-scripts.json scripts/statistics.ts" }, + "dependencies": { + "base64-js": "^1.5.1" + }, "devDependencies": { "@types/jest": "^27.4.0", "@types/node": "^20.11.5", diff --git a/packages/core/src/taktisches-zeichen.spec.ts b/packages/core/src/taktisches-zeichen.spec.ts new file mode 100644 index 0000000..2cbd34b --- /dev/null +++ b/packages/core/src/taktisches-zeichen.spec.ts @@ -0,0 +1,82 @@ +import { erzeugeTaktischesZeichen } from "./taktisches-zeichen"; + +describe("taktisches-zeichen", () => { + it("should render an SVG", () => { + const tz = erzeugeTaktischesZeichen({ + grundzeichen: "taktische-formation", + organisation: "feuerwehr", + skipFontRegistration: true, + }); + + expect(tz.toString()).toMatchInlineSnapshot(` + " + " + `); + }); + + it("should render a data URL", () => { + const tz = erzeugeTaktischesZeichen({ + grundzeichen: "taktische-formation", + organisation: "feuerwehr", + skipFontRegistration: true, + }); + + expect(tz.dataUrl).toMatchInlineSnapshot( + `"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjb2xvcj0iI2ZmZmZmZiIgZmlsbD0idHJhbnNwYXJlbnQiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIgdmlld0JveD0iMCAwIDc1IDQ1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxjbGlwUGF0aCBpZD0idHpfdGFrdGlzY2hlLWZvcm1hdGlvbiI+PHBhdGggZD0iTTEsMSBINzQgVjQ0IEgxIFoiIC8+PC9jbGlwUGF0aD48L2RlZnM+PHBhdGggZD0iTTEsMSBINzQgVjQ0IEgxIFoiIGZpbGw9IiNjYzAwMDAiIC8+PC9zdmc+"` + ); + }); + + it("should render an SVG with text", () => { + const tz = erzeugeTaktischesZeichen({ + grundzeichen: "taktische-formation", + organisation: "feuerwehr", + name: "Test", + skipFontRegistration: true, + }); + + expect(tz.toString()).toMatchInlineSnapshot(` + " + Test" + `); + }); + + it("should render a data URL with text", () => { + const tz = erzeugeTaktischesZeichen({ + grundzeichen: "taktische-formation", + organisation: "feuerwehr", + name: "Test", + skipFontRegistration: true, + }); + + expect(tz.dataUrl).toMatchInlineSnapshot( + `"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjb2xvcj0iI2ZmZmZmZiIgZmlsbD0idHJhbnNwYXJlbnQiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIgdmlld0JveD0iMCAwIDc1IDQ1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxjbGlwUGF0aCBpZD0idHpfdGFrdGlzY2hlLWZvcm1hdGlvbiI+PHBhdGggZD0iTTEsMSBINzQgVjQ0IEgxIFoiIC8+PC9jbGlwUGF0aD48L2RlZnM+PHBhdGggZD0iTTEsMSBINzQgVjQ0IEgxIFoiIGZpbGw9IiNjYzAwMDAiIC8+PGcgY2xpcC1wYXRoPSJ1cmwoI3R6X3Rha3Rpc2NoZS1mb3JtYXRpb24pIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzLDMpIHNjYWxlKDAuMjc5MDY5NzY3NDQxODYwNDYpIj48dGV4dCBmaWxsPSJjdXJyZW50Q29sb3IiIHN0cm9rZT0ibm9uZSIgc3R5bGU9ImZvbnQtZmFtaWx5OlJvYm90byBTbGFiO2ZvbnQtc2l6ZTozMHB4O2ZvbnQtd2VpZ2h0OmJvbGQ7bGV0dGVyLXNwYWNpbmc6LTFweCIgeD0iMCIgeT0iMjEuNSI+PCFbQ0RBVEFbVGVzdF1dPjwvdGV4dD48L2c+PC9nPjwvc3ZnPg=="` + ); + }); + + it("should render an SVG with UTF-8 text", () => { + const tz = erzeugeTaktischesZeichen({ + grundzeichen: "taktische-formation", + organisation: "feuerwehr", + name: "Täst", + skipFontRegistration: true, + }); + + expect(tz.toString()).toMatchInlineSnapshot(` + " + Täst" + `); + }); + + it("should render a data URL with UTF-8 text", () => { + const tz = erzeugeTaktischesZeichen({ + grundzeichen: "taktische-formation", + organisation: "feuerwehr", + name: "Täst", + skipFontRegistration: true, + }); + + expect(tz.dataUrl).toMatchInlineSnapshot( + `"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjb2xvcj0iI2ZmZmZmZiIgZmlsbD0idHJhbnNwYXJlbnQiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIgdmlld0JveD0iMCAwIDc1IDQ1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxkZWZzPjxjbGlwUGF0aCBpZD0idHpfdGFrdGlzY2hlLWZvcm1hdGlvbiI+PHBhdGggZD0iTTEsMSBINzQgVjQ0IEgxIFoiIC8+PC9jbGlwUGF0aD48L2RlZnM+PHBhdGggZD0iTTEsMSBINzQgVjQ0IEgxIFoiIGZpbGw9IiNjYzAwMDAiIC8+PGcgY2xpcC1wYXRoPSJ1cmwoI3R6X3Rha3Rpc2NoZS1mb3JtYXRpb24pIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzLDMpIHNjYWxlKDAuMjc5MDY5NzY3NDQxODYwNDYpIj48dGV4dCBmaWxsPSJjdXJyZW50Q29sb3IiIHN0cm9rZT0ibm9uZSIgc3R5bGU9ImZvbnQtZmFtaWx5OlJvYm90byBTbGFiO2ZvbnQtc2l6ZTozMHB4O2ZvbnQtd2VpZ2h0OmJvbGQ7bGV0dGVyLXNwYWNpbmc6LTFweCIgeD0iMCIgeT0iMjEuNSI+PCFbQ0RBVEFbVMOkc3RdXT48L3RleHQ+PC9nPjwvZz48L3N2Zz4="` + ); + }); +}); diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 58f6d62..bc3029d 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,4 +1,5 @@ -import { SVG, Element } from "./svg"; +import { fromByteArray } from "base64-js"; +import { Element, SVG } from "./svg"; import type { Image, Padding, Point, Rect, Renderable } from "./types"; export type Parent = { @@ -26,11 +27,7 @@ export class ImageImpl implements Image { constructor(public readonly svg: SVG, public readonly size: Point) {} get dataUrl() { - const data = - typeof window !== "undefined" - ? btoa(this.toString()) - : Buffer.from(this.toString()).toString("base64"); - return `data:image/svg+xml;base64,${data}`; + return `data:image/svg+xml;base64,${toBase64(this.toString())}`; } toString() { @@ -38,6 +35,16 @@ export class ImageImpl implements Image { } } +function toBase64(string: string): string { + if (typeof Buffer !== "undefined") { + console.log("using Buffer to encode base64:", string); + return Buffer.from(string).toString("base64"); + } + + console.log("using Array to encode base64:", string); + return fromByteArray(new TextEncoder().encode(string)); +} + export type Alignment = "center" | "start" | "end"; export type PlacedComponent = {