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(`
+ "
+ "
+ `);
+ });
+
+ 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(`
+ "
+ "
+ `);
+ });
+
+ 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 = {