kritzel-stencil 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE.md +50 -0
  2. package/dist/cjs/index-Xav9JFHg.js +2 -2
  3. package/dist/cjs/index.cjs.js +7 -1
  4. package/dist/cjs/{kritzel-active-users_42.cjs.entry.js → kritzel-active-users_44.cjs.entry.js} +710 -145
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/cjs/{schema.constants-DJQTjcy7.js → schema.constants-DrHO_CYF.js} +1169 -171
  7. package/dist/cjs/stencil.cjs.js +1 -1
  8. package/dist/collection/classes/core/core.class.js +24 -0
  9. package/dist/collection/classes/handlers/context-menu.handler.js +24 -2
  10. package/dist/collection/classes/managers/license.manager.js +285 -0
  11. package/dist/collection/classes/managers/localization.manager.js +189 -0
  12. package/dist/collection/classes/objects/custom-element.class.js +2 -0
  13. package/dist/collection/classes/objects/group.class.js +7 -2
  14. package/dist/collection/classes/objects/image.class.js +10 -7
  15. package/dist/collection/classes/objects/line.class.js +3 -0
  16. package/dist/collection/classes/objects/path.class.js +13 -12
  17. package/dist/collection/classes/objects/selection-group.class.js +7 -2
  18. package/dist/collection/classes/objects/shape.class.js +3 -0
  19. package/dist/collection/classes/objects/text.class.js +4 -1
  20. package/dist/collection/classes/registries/icon-registry.class.js +1 -0
  21. package/dist/collection/classes/tools/brush-tool.class.js +1 -1
  22. package/dist/collection/collection-manifest.json +3 -1
  23. package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +16 -0
  24. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +462 -60
  25. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +446 -16
  26. package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.css +29 -0
  27. package/dist/collection/components/core/kritzel-watermark/kritzel-watermark.js +83 -0
  28. package/dist/collection/components/shared/kritzel-avatar/kritzel-avatar.js +3 -3
  29. package/dist/collection/components/shared/kritzel-button/kritzel-button.js +2 -2
  30. package/dist/collection/components/shared/kritzel-color/kritzel-color.js +2 -2
  31. package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +1 -1
  32. package/dist/collection/components/shared/kritzel-font/kritzel-font.js +1 -1
  33. package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +2 -1
  34. package/dist/collection/components/shared/kritzel-input/kritzel-input.js +1 -1
  35. package/dist/collection/components/shared/kritzel-master-detail/kritzel-master-detail.js +3 -3
  36. package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +1 -1
  37. package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +2 -2
  38. package/dist/collection/components/shared/kritzel-numeric-input/kritzel-numeric-input.js +1 -1
  39. package/dist/collection/components/shared/kritzel-opacity-slider/kritzel-opacity-slider.js +1 -1
  40. package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +1 -1
  41. package/dist/collection/components/shared/kritzel-slide-toggle/kritzel-slide-toggle.js +1 -1
  42. package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +1 -1
  43. package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +2 -1
  44. package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +2 -2
  45. package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  46. package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +41 -6
  47. package/dist/collection/components/ui/kritzel-current-user/kritzel-current-user.js +36 -1
  48. package/dist/collection/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.js +36 -1
  49. package/dist/collection/components/ui/kritzel-export/kritzel-export.js +44 -7
  50. package/dist/collection/components/ui/kritzel-login-dialog/kritzel-login-dialog.js +1 -1
  51. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +36 -1
  52. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +108 -14
  53. package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +38 -3
  54. package/dist/collection/components/ui/kritzel-tool-config/kritzel-tool-config.js +38 -3
  55. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +36 -1
  56. package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js +38 -3
  57. package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.css +72 -0
  58. package/dist/collection/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.js +173 -0
  59. package/dist/collection/constants/engine.constants.js +2 -0
  60. package/dist/collection/constants/license.constants.js +25 -0
  61. package/dist/collection/constants/version.js +1 -1
  62. package/dist/collection/helpers/localization.helper.js +25 -0
  63. package/dist/collection/helpers/math.helper.js +3 -0
  64. package/dist/collection/helpers/svg-export.helper.js +223 -26
  65. package/dist/collection/index.js +13 -0
  66. package/dist/collection/interfaces/localization.interface.js +1 -0
  67. package/dist/collection/locales/de-locale.js +119 -0
  68. package/dist/collection/locales/en-locale.js +120 -0
  69. package/dist/collection/locales/fr-locale.js +119 -0
  70. package/dist/collection/themes/dark-theme.js +18 -0
  71. package/dist/collection/themes/light-theme.js +18 -0
  72. package/dist/components/index.d.ts +4 -0
  73. package/dist/components/index.js +1 -1
  74. package/dist/components/kritzel-active-users.js +1 -1
  75. package/dist/components/kritzel-avatar.js +1 -1
  76. package/dist/components/kritzel-awareness-cursors.js +1 -1
  77. package/dist/components/kritzel-back-to-content.js +1 -1
  78. package/dist/components/kritzel-brush-style.js +1 -1
  79. package/dist/components/kritzel-button.js +1 -1
  80. package/dist/components/kritzel-color-palette.js +1 -1
  81. package/dist/components/kritzel-color.js +1 -1
  82. package/dist/components/kritzel-context-menu.js +1 -1
  83. package/dist/components/kritzel-controls.js +1 -1
  84. package/dist/components/kritzel-current-user-dialog.js +1 -1
  85. package/dist/components/kritzel-current-user.js +1 -1
  86. package/dist/components/kritzel-editor.js +1 -1
  87. package/dist/components/kritzel-engine.js +1 -1
  88. package/dist/components/kritzel-export.js +1 -1
  89. package/dist/components/kritzel-font-size.js +1 -1
  90. package/dist/components/kritzel-font.js +1 -1
  91. package/dist/components/kritzel-icon.js +1 -1
  92. package/dist/components/kritzel-input.js +1 -1
  93. package/dist/components/kritzel-login-dialog.js +1 -1
  94. package/dist/components/kritzel-master-detail.js +1 -1
  95. package/dist/components/kritzel-menu-item.js +1 -1
  96. package/dist/components/kritzel-menu.js +1 -1
  97. package/dist/components/kritzel-more-menu.js +1 -1
  98. package/dist/components/kritzel-numeric-input.js +1 -1
  99. package/dist/components/kritzel-opacity-slider.js +1 -1
  100. package/dist/components/kritzel-pill-tabs.js +1 -1
  101. package/dist/components/kritzel-portal.js +1 -1
  102. package/dist/components/kritzel-settings.js +1 -1
  103. package/dist/components/kritzel-share-dialog.js +1 -1
  104. package/dist/components/kritzel-slide-toggle.js +1 -1
  105. package/dist/components/kritzel-split-button.js +1 -1
  106. package/dist/components/kritzel-stroke-size.js +1 -1
  107. package/dist/components/kritzel-tool-config.js +1 -1
  108. package/dist/components/kritzel-tooltip.js +1 -1
  109. package/dist/components/kritzel-utility-panel.js +1 -1
  110. package/dist/components/kritzel-watermark.d.ts +11 -0
  111. package/dist/components/kritzel-watermark.js +1 -0
  112. package/dist/components/kritzel-workspace-manager.js +1 -1
  113. package/dist/components/kritzel-zoom-panel.d.ts +11 -0
  114. package/dist/components/kritzel-zoom-panel.js +1 -0
  115. package/dist/components/{p-B5xxfwKF.js → p-3HxnBrCM.js} +1 -1
  116. package/dist/components/p-6RjeGuvH.js +1 -0
  117. package/dist/components/p-7NsK0uHu.js +1 -0
  118. package/dist/components/{p-dcAernE1.js → p-BCNyR5Sw.js} +1 -1
  119. package/dist/components/{p-C2SX-XRr.js → p-BG6hOSrm.js} +1 -1
  120. package/dist/components/p-BKJSh8qQ.js +1 -0
  121. package/dist/components/{p-SptaSMno.js → p-BKvHg9cv.js} +1 -1
  122. package/dist/components/p-Bc55X65h.js +1 -0
  123. package/dist/components/p-BpnIvNvq.js +1 -0
  124. package/dist/components/p-BvRrA4hN.js +1 -0
  125. package/dist/components/{p-B2w8X7vn.js → p-BxpKq94F.js} +1 -1
  126. package/dist/components/{p-BFoK4W--.js → p-Bzv9Px8v.js} +1 -1
  127. package/dist/components/{p-COLHjboZ.js → p-C9HGoDHE.js} +1 -1
  128. package/dist/components/p-CEnEDaix.js +1 -0
  129. package/dist/components/p-CIcLzcfA.js +1 -0
  130. package/dist/components/p-CPtDfadX.js +1 -0
  131. package/dist/components/p-C_fKgKHu.js +9 -0
  132. package/dist/components/p-CdR76C4L.js +1 -0
  133. package/dist/components/p-Cu9KYyoq.js +1 -0
  134. package/dist/components/p-CyqRcqsO.js +1 -0
  135. package/dist/components/{p-UoPj5QjH.js → p-DDkmsPpV.js} +1 -1
  136. package/dist/components/{p-D-sRVAbQ.js → p-DI4vQRE3.js} +1 -1
  137. package/dist/components/{p-CJOhfMU5.js → p-DNdXJp8F.js} +1 -1
  138. package/dist/components/p-DX5K8xnh.js +1 -0
  139. package/dist/components/{p-DEy7zJCe.js → p-DZdgXCAx.js} +1 -1
  140. package/dist/components/p-DdH1cKED.js +1 -0
  141. package/dist/components/p-DgmtCdnL.js +1 -0
  142. package/dist/components/{p-BzYU3-MJ.js → p-DmWSRsjK.js} +1 -1
  143. package/dist/components/{p-Bj2laX89.js → p-Dz-Ti24X.js} +1 -1
  144. package/dist/components/{p-BiG1dxPS.js → p-F5_X4dZG.js} +1 -1
  145. package/dist/components/{p-x6doYeiI.js → p-IpoC5EEY.js} +1 -1
  146. package/dist/components/p-Jn6TNdfe.js +1 -0
  147. package/dist/components/{p-BfNHpqQ8.js → p-NuLP1xHe.js} +1 -1
  148. package/dist/components/{p-skWUIStn.js → p-SDZNC8GF.js} +1 -1
  149. package/dist/components/{p-BYmp9Ovv.js → p-U4oawa1x.js} +1 -1
  150. package/dist/components/{p-DM11KXUT.js → p-f8aW1ye7.js} +1 -1
  151. package/dist/components/p-mz3pUWW4.js +1 -0
  152. package/dist/components/p-v7dxxrL5.js +1 -0
  153. package/dist/components/p-vAeiXe6c.js +1 -0
  154. package/dist/esm/index-Dhio9uis.js +2 -2
  155. package/dist/esm/index.js +2 -2
  156. package/dist/esm/{kritzel-active-users_42.entry.js → kritzel-active-users_44.entry.js} +709 -146
  157. package/dist/esm/loader.js +1 -1
  158. package/dist/esm/{schema.constants-DiCnmIYK.js → schema.constants-DchTXG3V.js} +1163 -172
  159. package/dist/esm/stencil.js +1 -1
  160. package/dist/stencil/index.esm.js +1 -1
  161. package/dist/stencil/p-86348986.entry.js +9 -0
  162. package/dist/stencil/p-DchTXG3V.js +1 -0
  163. package/dist/stencil/stencil.esm.js +1 -1
  164. package/dist/types/classes/core/core.class.d.ts +16 -0
  165. package/dist/types/classes/handlers/context-menu.handler.d.ts +13 -0
  166. package/dist/types/classes/managers/license.manager.d.ts +141 -0
  167. package/dist/types/classes/managers/localization.manager.d.ts +121 -0
  168. package/dist/types/classes/objects/custom-element.class.d.ts +2 -0
  169. package/dist/types/classes/objects/group.class.d.ts +6 -1
  170. package/dist/types/classes/objects/image.class.d.ts +1 -1
  171. package/dist/types/classes/objects/path.class.d.ts +3 -2
  172. package/dist/types/classes/objects/selection-group.class.d.ts +6 -1
  173. package/dist/types/classes/objects/shape.class.d.ts +2 -0
  174. package/dist/types/classes/objects/text.class.d.ts +2 -1
  175. package/dist/types/classes/tools/brush-tool.class.d.ts +1 -1
  176. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +53 -1
  177. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +55 -3
  178. package/dist/types/components/core/kritzel-watermark/kritzel-watermark.d.ts +20 -0
  179. package/dist/types/components/ui/kritzel-controls/kritzel-controls.d.ts +3 -0
  180. package/dist/types/components/ui/kritzel-current-user/kritzel-current-user.d.ts +3 -0
  181. package/dist/types/components/ui/kritzel-current-user-dialog/kritzel-current-user-dialog.d.ts +3 -0
  182. package/dist/types/components/ui/kritzel-export/kritzel-export.d.ts +4 -1
  183. package/dist/types/components/ui/kritzel-more-menu/kritzel-more-menu.d.ts +3 -0
  184. package/dist/types/components/ui/kritzel-settings/kritzel-settings.d.ts +16 -0
  185. package/dist/types/components/ui/kritzel-share-dialog/kritzel-share-dialog.d.ts +3 -0
  186. package/dist/types/components/ui/kritzel-tool-config/kritzel-tool-config.d.ts +3 -0
  187. package/dist/types/components/ui/kritzel-utility-panel/kritzel-utility-panel.d.ts +3 -0
  188. package/dist/types/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.d.ts +3 -0
  189. package/dist/types/components/ui/kritzel-zoom-panel/kritzel-zoom-panel.d.ts +20 -0
  190. package/dist/types/components.d.ts +445 -26
  191. package/dist/types/constants/engine.constants.d.ts +2 -0
  192. package/dist/types/constants/license.constants.d.ts +25 -0
  193. package/dist/types/constants/version.d.ts +1 -1
  194. package/dist/types/helpers/localization.helper.d.ts +18 -0
  195. package/dist/types/helpers/math.helper.d.ts +1 -0
  196. package/dist/types/helpers/svg-export.helper.d.ts +81 -7
  197. package/dist/types/index.d.ts +13 -0
  198. package/dist/types/interfaces/context-menu-item.interface.d.ts +7 -1
  199. package/dist/types/interfaces/line-options.interface.d.ts +2 -0
  200. package/dist/types/interfaces/localization.interface.d.ts +143 -0
  201. package/dist/types/interfaces/path-options.interface.d.ts +2 -0
  202. package/dist/types/interfaces/settings.interface.d.ts +3 -0
  203. package/dist/types/interfaces/theme.interface.d.ts +27 -2
  204. package/dist/types/locales/de-locale.d.ts +5 -0
  205. package/dist/types/locales/en-locale.d.ts +6 -0
  206. package/dist/types/locales/fr-locale.d.ts +5 -0
  207. package/package.json +4 -7
  208. package/dist/components/p-2xYAGd0I.js +0 -1
  209. package/dist/components/p-B2Os1ya_.js +0 -1
  210. package/dist/components/p-BTEV1WwT.js +0 -1
  211. package/dist/components/p-BbactVA0.js +0 -1
  212. package/dist/components/p-BqwqGFQY.js +0 -1
  213. package/dist/components/p-C0TN5IAi.js +0 -1
  214. package/dist/components/p-CFgkUYoO.js +0 -1
  215. package/dist/components/p-COgo9OWy.js +0 -1
  216. package/dist/components/p-CUPYGT8c.js +0 -1
  217. package/dist/components/p-CcyIAi9S.js +0 -1
  218. package/dist/components/p-Cj78L1Kk.js +0 -1
  219. package/dist/components/p-CkAVEdDw.js +0 -9
  220. package/dist/components/p-CmuNn1Tc.js +0 -1
  221. package/dist/components/p-DDYoDSrm.js +0 -1
  222. package/dist/components/p-DbB730vO.js +0 -1
  223. package/dist/components/p-DlwYHzSj.js +0 -1
  224. package/dist/components/p-FK7b3BGt.js +0 -1
  225. package/dist/components/p-J9_SwObO.js +0 -1
  226. package/dist/stencil/p-67775031.entry.js +0 -9
  227. package/dist/stencil/p-DiCnmIYK.js +0 -1
@@ -41,7 +41,7 @@ export class KritzelSvgExportHelper {
41
41
  * @returns SVG document string
42
42
  */
43
43
  static generateSvg(objects, options = {}) {
44
- const { theme, padding = 0, includeXmlDeclaration = true } = options;
44
+ const { theme, padding = 0, includeXmlDeclaration = true, rasterSafeForCanvas = false, imageDataUrls } = options;
45
45
  if (!objects || objects.length === 0) {
46
46
  return '';
47
47
  }
@@ -61,7 +61,7 @@ export class KritzelSvgExportHelper {
61
61
  // Generate SVG elements for each object
62
62
  const elements = objects
63
63
  .sort((a, b) => a.zIndex - b.zIndex)
64
- .map(obj => this.objectToSvgElement(obj, theme))
64
+ .map(obj => this.objectToSvgElement(obj, theme, imageDataUrls, rasterSafeForCanvas))
65
65
  .filter(Boolean)
66
66
  .join('\n ');
67
67
  const xmlDecl = includeXmlDeclaration ? '<?xml version="1.0" encoding="UTF-8"?>\n' : '';
@@ -87,6 +87,102 @@ export class KritzelSvgExportHelper {
87
87
  }
88
88
  return result;
89
89
  }
90
+ /**
91
+ * Resolves every image referenced by the given objects (including those
92
+ * nested inside groups) to an inlined `data:` URL, keyed by object id.
93
+ *
94
+ * This is required for export flows that rasterize the SVG (e.g. PNG
95
+ * export): an SVG loaded into an `<img>` element will NOT load external
96
+ * or `blob:` resources, so a `<image href="blob:...">` renders as a
97
+ * broken-image placeholder. Embedding the bytes as a base64 `data:` URL
98
+ * makes the image self-contained and survive rasterization.
99
+ *
100
+ * Resolution is best-effort: images that fail to resolve are simply
101
+ * omitted from the map, and the SVG falls back to whatever `href` the
102
+ * synchronous path would have produced.
103
+ * @param objects - Objects to inline images for
104
+ * @param assetResolver - Resolver used to fetch asset bytes; optional
105
+ * @returns Map of image object id -> inlined `data:` URL
106
+ */
107
+ static async resolveImageDataUrls(objects, assetResolver) {
108
+ const dataUrls = new Map();
109
+ if (!objects || objects.length === 0) {
110
+ return dataUrls;
111
+ }
112
+ const images = this.flattenObjects(objects).filter((obj) => KritzelClassHelper.isInstanceOf(obj, 'KritzelImage'));
113
+ await Promise.all(images.map(async (image) => {
114
+ // Legacy inline data URL is already embeddable as-is.
115
+ if (image.src?.startsWith('data:')) {
116
+ dataUrls.set(image.id, image.src);
117
+ return;
118
+ }
119
+ // A resolvedSrc that is itself a data URL can be embedded directly.
120
+ if (image.resolvedSrc?.startsWith('data:')) {
121
+ dataUrls.set(image.id, image.resolvedSrc);
122
+ return;
123
+ }
124
+ if (!image.assetId || !assetResolver) {
125
+ return;
126
+ }
127
+ try {
128
+ const blob = await assetResolver.fetchBlob(image.assetId);
129
+ dataUrls.set(image.id, await this.blobToDataUrl(blob));
130
+ }
131
+ catch (err) {
132
+ console.warn(`[KritzelSvgExportHelper] Failed to inline image asset ${image.assetId} for export:`, err);
133
+ }
134
+ }));
135
+ return dataUrls;
136
+ }
137
+ /**
138
+ * Reads a Blob into a base64 `data:` URL.
139
+ * @param blob - The blob to encode
140
+ * @returns Promise resolving to the data URL string
141
+ */
142
+ static async blobToDataUrl(blob) {
143
+ // Prefer FileReader in browser-like runtimes for efficiency.
144
+ const hasFileReader = typeof FileReader !== 'undefined';
145
+ if (hasFileReader) {
146
+ try {
147
+ return await new Promise((resolve, reject) => {
148
+ const reader = new FileReader();
149
+ reader.onload = () => {
150
+ if (typeof reader.result === 'string') {
151
+ resolve(reader.result);
152
+ return;
153
+ }
154
+ reject(new Error('FileReader result is not a string'));
155
+ };
156
+ reader.onerror = () => reject(reader.error ?? new Error('Failed to read blob as data URL'));
157
+ reader.readAsDataURL(blob);
158
+ });
159
+ }
160
+ catch {
161
+ // Fall through to the ArrayBuffer-based encoder.
162
+ }
163
+ }
164
+ const bytes = new Uint8Array(await blob.arrayBuffer());
165
+ const mimeType = blob.type || 'application/octet-stream';
166
+ return `data:${mimeType};base64,${this.uint8ToBase64(bytes)}`;
167
+ }
168
+ /**
169
+ * Encodes a byte array to base64 across browser and Node-like runtimes.
170
+ */
171
+ static uint8ToBase64(bytes) {
172
+ if (typeof Buffer !== 'undefined') {
173
+ return Buffer.from(bytes).toString('base64');
174
+ }
175
+ let binary = '';
176
+ const chunkSize = 0x8000;
177
+ for (let i = 0; i < bytes.length; i += chunkSize) {
178
+ const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));
179
+ binary += String.fromCharCode(...chunk);
180
+ }
181
+ if (typeof btoa === 'function') {
182
+ return btoa(binary);
183
+ }
184
+ throw new Error('No base64 encoder available in this runtime');
185
+ }
90
186
  /**
91
187
  * Collects SVG defs (markers, patterns, etc.) needed by the objects.
92
188
  * @param objects - Objects to collect defs for
@@ -131,9 +227,10 @@ export class KritzelSvgExportHelper {
131
227
  * Converts a Kritzel object to its SVG element representation.
132
228
  * @param object - The object to convert
133
229
  * @param theme - Theme for color resolution
230
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
134
231
  * @returns SVG element string, or empty string if object type is not supported
135
232
  */
136
- static objectToSvgElement(object, theme) {
233
+ static objectToSvgElement(object, theme, imageDataUrls, rasterSafeForCanvas = false) {
137
234
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelPath')) {
138
235
  return this.pathToSvg(object, theme);
139
236
  }
@@ -144,13 +241,13 @@ export class KritzelSvgExportHelper {
144
241
  return this.shapeToSvg(object, theme);
145
242
  }
146
243
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelText')) {
147
- return this.textToSvg(object, theme);
244
+ return this.textToSvg(object, theme, rasterSafeForCanvas);
148
245
  }
149
246
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelImage')) {
150
- return this.imageToSvg(object);
247
+ return this.imageToSvg(object, imageDataUrls);
151
248
  }
152
249
  if (KritzelClassHelper.isInstanceOf(object, 'KritzelGroup')) {
153
- return this.groupToSvg(object, theme);
250
+ return this.groupToSvg(object, theme, imageDataUrls, rasterSafeForCanvas);
154
251
  }
155
252
  return '';
156
253
  }
@@ -165,9 +262,10 @@ export class KritzelSvgExportHelper {
165
262
  const fill = KritzelColorHelper.resolveThemeColor(path.fill, theme);
166
263
  const stroke = KritzelColorHelper.resolveThemeColor(path.stroke, theme);
167
264
  const opacity = path.opacity !== 1 ? ` opacity="${path.opacity}"` : '';
265
+ const scale = this.getObjectScale(path);
168
266
  // Path needs to be wrapped in a g with translation since path.d uses local coordinates
169
267
  return `<g transform="${transform}"${opacity}>
170
- <svg viewBox="${path.viewBox}" width="${path.totalWidth / path.scale}" height="${path.totalHeight / path.scale}" overflow="visible">
268
+ <svg viewBox="${path.viewBox}" width="${path.totalWidth / scale}" height="${path.totalHeight / scale}" overflow="visible">
171
269
  <path d="${path.d}" fill="${fill || 'none'}" stroke="${stroke || 'none'}"${path.strokeWidth ? ` stroke-width="${path.strokeWidth}"` : ''}/>
172
270
  </svg>
173
271
  </g>`;
@@ -182,10 +280,11 @@ export class KritzelSvgExportHelper {
182
280
  const transform = this.buildTransform(line);
183
281
  const stroke = KritzelColorHelper.resolveThemeColor(line.stroke, theme);
184
282
  const opacity = line.opacity !== 1 ? ` opacity="${line.opacity}"` : '';
283
+ const scale = this.getObjectScale(line);
185
284
  const markerStart = line.hasStartArrow ? ` marker-start="url(#${line.startMarkerId})"` : '';
186
285
  const markerEnd = line.hasEndArrow ? ` marker-end="url(#${line.endMarkerId})"` : '';
187
286
  return `<g transform="${transform}"${opacity}>
188
- <svg viewBox="${line.viewBox}" width="${line.totalWidth / line.scale}" height="${line.totalHeight / line.scale}" overflow="visible">
287
+ <svg viewBox="${line.viewBox}" width="${line.totalWidth / scale}" height="${line.totalHeight / scale}" overflow="visible">
189
288
  <path d="${line.d}" fill="none" stroke="${stroke}" stroke-width="${line.strokeWidth}" stroke-linecap="round"${markerStart}${markerEnd}/>
190
289
  </svg>
191
290
  </g>`;
@@ -201,6 +300,7 @@ export class KritzelSvgExportHelper {
201
300
  const fill = KritzelColorHelper.resolveThemeColor(shape.fillColor, theme);
202
301
  const stroke = KritzelColorHelper.resolveThemeColor(shape.strokeColor, theme);
203
302
  const opacity = shape.opacity !== 1 ? ` opacity="${shape.opacity}"` : '';
303
+ const scale = this.getObjectScale(shape);
204
304
  // Get the SVG path for the shape
205
305
  const pathD = shape.getSvgPath();
206
306
  // Handle text content if present
@@ -221,7 +321,7 @@ export class KritzelSvgExportHelper {
221
321
  }
222
322
  }
223
323
  return `<g transform="${transform}"${opacity}>
224
- <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / shape.scale}" height="${shape.totalHeight / shape.scale}" overflow="visible" preserveAspectRatio="none">
324
+ <svg viewBox="${shape.viewBox}" width="${shape.totalWidth / scale}" height="${shape.totalHeight / scale}" overflow="visible" preserveAspectRatio="none">
225
325
  <path d="${pathD}" fill="${fill || 'transparent'}" stroke="${stroke}" stroke-width="${shape.strokeWidth}"/>${textContent}
226
326
  </svg>
227
327
  </g>`;
@@ -232,45 +332,85 @@ export class KritzelSvgExportHelper {
232
332
  * @param theme - Theme for color resolution
233
333
  * @returns SVG element string
234
334
  */
235
- static textToSvg(text, theme) {
335
+ static textToSvg(text, theme, rasterSafeForCanvas = false) {
336
+ if (rasterSafeForCanvas) {
337
+ return this.textToSvgRasterSafe(text, theme);
338
+ }
236
339
  const transform = this.buildTransform(text);
237
340
  const opacity = text.opacity !== 1 ? ` opacity="${text.opacity}"` : '';
238
341
  const fontColor = KritzelColorHelper.resolveThemeColor(text.fontColor, theme);
239
342
  const bgColor = KritzelColorHelper.resolveThemeColor(text.backgroundColor, theme);
343
+ const scale = this.getObjectScale(text);
240
344
  // Convert ProseMirror content to HTML
241
345
  const htmlContent = this.prosemirrorToHtml(text.content, text, theme);
242
346
  // Calculate dimensions accounting for scale
243
- const width = text.totalWidth / text.scale;
244
- const height = text.totalHeight / text.scale;
347
+ const width = text.totalWidth / scale;
348
+ const height = text.totalHeight / scale;
245
349
  return `<g transform="${transform}"${opacity}>
246
350
  <foreignObject x="0" y="0" width="${width}" height="${height}">
247
351
  <div xmlns="http://www.w3.org/1999/xhtml" style="font-family: ${text.fontFamily}; font-size: ${text.fontSize}pt; color: ${fontColor}; background-color: ${bgColor || 'transparent'}; transform: scale(${text.scaleFactor}); transform-origin: top left; white-space: pre-wrap; word-wrap: break-word;">
248
352
  ${htmlContent}
249
353
  </div>
250
354
  </foreignObject>
355
+ </g>`;
356
+ }
357
+ /**
358
+ * Converts a KritzelText to SVG using pure SVG text primitives.
359
+ * This path avoids `<foreignObject>`, which taints canvases when the
360
+ * generated SVG is rasterized through `<img>` and `drawImage`.
361
+ */
362
+ static textToSvgRasterSafe(text, theme) {
363
+ const transform = this.buildTransform(text);
364
+ const opacity = text.opacity !== 1 ? ` opacity="${text.opacity}"` : '';
365
+ const fontColor = KritzelColorHelper.resolveThemeColor(text.fontColor, theme);
366
+ const bgColor = KritzelColorHelper.resolveThemeColor(text.backgroundColor, theme);
367
+ const scale = this.getObjectScale(text);
368
+ const width = text.totalWidth / scale;
369
+ const height = text.totalHeight / scale;
370
+ const lines = this.prosemirrorToPlainTextLines(text.content);
371
+ const resolvedLines = lines.length > 0 ? lines : [''];
372
+ const resolvedFontSize = Math.max(1, text.fontSize * (text.scaleFactor || 1));
373
+ const lineHeight = resolvedFontSize * 1.2;
374
+ const startY = resolvedFontSize;
375
+ const bgRect = bgColor && bgColor !== 'transparent'
376
+ ? `<rect x="0" y="0" width="${width}" height="${height}" fill="${bgColor}"/>`
377
+ : '';
378
+ const tspans = resolvedLines
379
+ .map((line, index) => {
380
+ const y = startY + index * lineHeight;
381
+ return `<tspan x="0" y="${y}" xml:space="preserve">${this.escapeHtml(line)}</tspan>`;
382
+ })
383
+ .join('');
384
+ return `<g transform="${transform}"${opacity}>
385
+ ${bgRect}
386
+ <text font-family="${this.escapeHtml(text.fontFamily)}" font-size="${resolvedFontSize}" fill="${fontColor}">${tspans}</text>
251
387
  </g>`;
252
388
  }
253
389
  /**
254
390
  * Converts a KritzelImage to SVG.
255
391
  *
256
- * Uses `resolvedSrc` when available (populated by the renderer via the
257
- * asset resolver), falling back to the legacy inline `src` field for
392
+ * Prefers an inlined `data:` URL from `imageDataUrls` (keyed by object
393
+ * id) when provided, so the embedded bytes survive rasterization to
394
+ * PNG. Falls back to `resolvedSrc` (populated by the renderer via the
395
+ * asset resolver) and finally the legacy inline `src` field for
258
396
  * documents persisted before the asset layer existed.
259
397
  *
260
- * Note: SVG export is synchronous and cannot await asset bytes from a
261
- * remote provider. Callers that need the fully rasterized image bytes
262
- * embedded in the exported SVG should pre-resolve assets via
263
- * `core.assetResolver.fetchBlob` and inline them as data URLs.
398
+ * Note: a `blob:` `resolvedSrc` renders correctly on-canvas but becomes
399
+ * a broken-image placeholder once the SVG is loaded into an `<img>` and
400
+ * drawn to a canvas. Callers that rasterize the SVG must pass
401
+ * `imageDataUrls` (see {@link KritzelSvgExportHelper.resolveImageDataUrls}).
264
402
  * @param image - The image object
403
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
265
404
  * @returns SVG element string
266
405
  */
267
- static imageToSvg(image) {
406
+ static imageToSvg(image, imageDataUrls) {
268
407
  const transform = this.buildTransform(image);
269
408
  const opacity = image.opacity !== 1 ? ` opacity="${image.opacity}"` : '';
409
+ const scale = this.getObjectScale(image);
270
410
  // Calculate dimensions
271
- const width = image.totalWidth / image.scale;
272
- const height = image.totalHeight / image.scale;
273
- const href = image.resolvedSrc || image.src || '';
411
+ const width = image.totalWidth / scale;
412
+ const height = image.totalHeight / scale;
413
+ const href = imageDataUrls?.get(image.id) || image.resolvedSrc || image.src || '';
274
414
  return `<g transform="${transform}"${opacity}>
275
415
  <image href="${href}" x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="xMidYMid meet"/>
276
416
  </g>`;
@@ -279,12 +419,13 @@ export class KritzelSvgExportHelper {
279
419
  * Converts a KritzelGroup to SVG by recursively converting children.
280
420
  * @param group - The group object
281
421
  * @param theme - Theme for color resolution
422
+ * @param imageDataUrls - Optional map of image id -> inlined data URL
282
423
  * @returns SVG element string
283
424
  */
284
- static groupToSvg(group, theme) {
425
+ static groupToSvg(group, theme, imageDataUrls, rasterSafeForCanvas = false) {
285
426
  const children = group.children
286
427
  .sort((a, b) => a.zIndex - b.zIndex)
287
- .map(child => this.objectToSvgElement(child, theme))
428
+ .map(child => this.objectToSvgElement(child, theme, imageDataUrls, rasterSafeForCanvas))
288
429
  .filter(Boolean)
289
430
  .join('\n ');
290
431
  if (!children) {
@@ -303,17 +444,25 @@ export class KritzelSvgExportHelper {
303
444
  */
304
445
  static buildTransform(object) {
305
446
  const transforms = [];
447
+ const scale = this.getObjectScale(object);
306
448
  // Translation
307
449
  transforms.push(`translate(${object.translateX}, ${object.translateY})`);
308
450
  // Rotation around center
309
451
  if (object.rotation !== 0) {
310
- const centerX = object.totalWidth / 2 / object.scale;
311
- const centerY = object.totalHeight / 2 / object.scale;
452
+ const centerX = object.totalWidth / 2 / scale;
453
+ const centerY = object.totalHeight / 2 / scale;
312
454
  const degrees = object.rotation * (180 / Math.PI);
313
455
  transforms.push(`rotate(${degrees}, ${centerX}, ${centerY})`);
314
456
  }
315
457
  return transforms.join(' ');
316
458
  }
459
+ /**
460
+ * Returns a safe non-zero scale for export geometry calculations.
461
+ */
462
+ static getObjectScale(object) {
463
+ const scale = object.scale ?? 1;
464
+ return scale === 0 ? 1 : scale;
465
+ }
317
466
  /**
318
467
  * Converts ProseMirror JSON content to HTML string.
319
468
  * @param content - ProseMirror document JSON
@@ -369,6 +518,54 @@ export class KritzelSvgExportHelper {
369
518
  return '';
370
519
  }
371
520
  }
521
+ /**
522
+ * Converts ProseMirror JSON content to plain-text lines.
523
+ * Used by raster-safe SVG export, which cannot depend on HTML/foreignObject.
524
+ */
525
+ static prosemirrorToPlainTextLines(content) {
526
+ if (!content || !Array.isArray(content.content)) {
527
+ return [];
528
+ }
529
+ const plainText = content.content
530
+ .map((node) => this.nodeToPlainText(node))
531
+ .join('')
532
+ .replace(/\r\n/g, '\n')
533
+ .replace(/\n+$/, '');
534
+ if (!plainText) {
535
+ return [];
536
+ }
537
+ return plainText.split('\n');
538
+ }
539
+ /**
540
+ * Converts a ProseMirror node tree into plain text.
541
+ */
542
+ static nodeToPlainText(node) {
543
+ if (!node) {
544
+ return '';
545
+ }
546
+ switch (node.type) {
547
+ case 'text':
548
+ return node.text || '';
549
+ case 'hard_break':
550
+ return '\n';
551
+ case 'paragraph': {
552
+ const paragraph = Array.isArray(node.content)
553
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
554
+ : '';
555
+ return `${paragraph}\n`;
556
+ }
557
+ case 'list_item': {
558
+ const item = Array.isArray(node.content)
559
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
560
+ : '';
561
+ return `- ${item.trim()}\n`;
562
+ }
563
+ default:
564
+ return Array.isArray(node.content)
565
+ ? node.content.map((child) => this.nodeToPlainText(child)).join('')
566
+ : '';
567
+ }
568
+ }
372
569
  /**
373
570
  * Applies a ProseMirror mark to text.
374
571
  * @param text - The text to wrap
@@ -14,6 +14,7 @@ export * from './classes/objects/image.class';
14
14
  export * from './classes/objects/line.class';
15
15
  export * from './classes/objects/group.class';
16
16
  export * from './classes/objects/shape.class';
17
+ export * from './classes/tools/base-tool.class';
17
18
  export * from './classes/tools/brush-tool.class';
18
19
  export * from './classes/tools/line-tool.class';
19
20
  export * from './classes/tools/eraser-tool.class';
@@ -34,6 +35,8 @@ export * from './classes/providers/assets/presigned-asset-provider.class';
34
35
  export * from './classes/core/workspace.class';
35
36
  export * from './classes/managers/anchor.manager';
36
37
  export * from './classes/managers/theme.manager';
38
+ export * from './classes/managers/localization.manager';
39
+ export * from './classes/managers/license.manager';
37
40
  export * from './interfaces/toolbar-control.interface';
38
41
  export * from './interfaces/menu-item.interface';
39
42
  export * from './interfaces/object-change-event.interface';
@@ -48,13 +51,23 @@ export * from './interfaces/anchor.interface';
48
51
  export * from './interfaces/viewport-state.interface';
49
52
  export * from './interfaces/user.interface';
50
53
  export * from './interfaces/theme.interface';
54
+ export * from './interfaces/localization.interface';
51
55
  export * from './interfaces/shortcut.interface';
56
+ export * from './interfaces/undo-state.interface';
57
+ export * from './interfaces/share.interface';
58
+ export * from './interfaces/engine-state.interface';
59
+ export * from './interfaces/debug-info.interface';
60
+ export * from './interfaces/bounding-box.interface';
61
+ export * from './interfaces/polygon.interface';
52
62
  export * from './configs/default-brush-tool.config';
53
63
  export * from './configs/default-text-tool.config';
54
64
  export * from './configs/default-line-tool.config';
55
65
  export * from './configs/default-asset-storage.config';
56
66
  export * from './themes/light-theme';
57
67
  export * from './themes/dark-theme';
68
+ export * from './locales/en-locale';
69
+ export * from './locales/de-locale';
70
+ export * from './locales/fr-locale';
58
71
  export * from './enums/alignment.enum';
59
72
  export * from './enums/shape-type.enum';
60
73
  export * from './interfaces/migration.interface';
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Built-in German locale.
3
+ */
4
+ export const DE_LOCALE = {
5
+ code: 'de',
6
+ label: 'Deutsch',
7
+ terms: {
8
+ // Context menu
9
+ 'menu.copy': 'Kopieren',
10
+ 'menu.cut': 'Ausschneiden',
11
+ 'menu.paste': 'Einfügen',
12
+ 'menu.selectAll': 'Alles auswählen',
13
+ 'menu.order': 'Anordnen',
14
+ 'menu.bringToFront': 'In den Vordergrund',
15
+ 'menu.sendToBack': 'In den Hintergrund',
16
+ 'menu.moveUp': 'Nach vorne',
17
+ 'menu.moveDown': 'Nach hinten',
18
+ 'menu.align': 'Ausrichten',
19
+ 'menu.alignLeft': 'Linksbündig ausrichten',
20
+ 'menu.alignCenterHorizontal': 'Horizontal zentrieren',
21
+ 'menu.alignRight': 'Rechtsbündig ausrichten',
22
+ 'menu.alignTop': 'Oben ausrichten',
23
+ 'menu.alignCenterVertical': 'Vertikal zentrieren',
24
+ 'menu.alignBottom': 'Unten ausrichten',
25
+ 'menu.group': 'Gruppieren',
26
+ 'menu.ungroup': 'Gruppierung aufheben',
27
+ 'menu.export': 'Exportieren',
28
+ 'menu.exportAsSvg': 'Als SVG exportieren',
29
+ 'menu.exportAsPng': 'Als PNG exportieren',
30
+ 'menu.delete': 'Löschen',
31
+ // More menu
32
+ 'menu.share': 'Teilen',
33
+ 'menu.import': 'Importieren',
34
+ 'menu.settings': 'Einstellungen',
35
+ 'menu.logout': 'Abmelden',
36
+ // Settings dialog
37
+ 'settings.dialogTitle': 'Einstellungen',
38
+ 'settings.categories.general': 'Allgemein',
39
+ 'settings.categories.viewport': 'Ansichtsfenster',
40
+ 'settings.categories.shortcuts': 'Tastenkürzel',
41
+ 'settings.categories.developer': 'Entwickleroptionen',
42
+ 'settings.categories.about': 'Über',
43
+ 'settings.general.title': 'Allgemeine Einstellungen',
44
+ 'settings.general.theme.label': 'Design',
45
+ 'settings.general.theme.description': 'Wählen Sie ein registriertes Farbdesign für die Editor-Oberfläche.',
46
+ 'settings.general.language.label': 'Sprache',
47
+ 'settings.general.language.description': 'Wählen Sie die Anzeigesprache für die Editor-Oberfläche.',
48
+ 'settings.general.lockDrawingScale.label': 'Zeichenskalierung sperren',
49
+ 'settings.general.lockDrawingScale.description': 'Wenn aktiviert, behalten gezeichnete Objekte unabhängig von der aktuellen Zoomstufe eine feste visuelle Größe.',
50
+ 'settings.viewport.title': 'Ansichtsfenster-Einstellungen',
51
+ 'settings.viewport.minZoom.label': 'Minimale Zoomstufe',
52
+ 'settings.viewport.minZoom.description': 'Legt die minimale Zoomstufe fest. Niedrigere Werte ermöglichen weiteres Herauszoomen, um mehr von der Zeichenfläche zu sehen.',
53
+ 'settings.viewport.maxZoom.label': 'Maximale Zoomstufe',
54
+ 'settings.viewport.maxZoom.description': 'Legt die maximale Zoomstufe fest. Höhere Werte ermöglichen näheres Heranzoomen für Detailarbeit.',
55
+ 'settings.viewport.boundaryLeft.label': 'Ansichtsfenster-Grenze links',
56
+ 'settings.viewport.boundaryLeft.description': 'Linke Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach links verschoben werden kann.',
57
+ 'settings.viewport.boundaryRight.label': 'Ansichtsfenster-Grenze rechts',
58
+ 'settings.viewport.boundaryRight.description': 'Rechte Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach rechts verschoben werden kann.',
59
+ 'settings.viewport.boundaryTop.label': 'Ansichtsfenster-Grenze oben',
60
+ 'settings.viewport.boundaryTop.description': 'Obere Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach oben verschoben werden kann.',
61
+ 'settings.viewport.boundaryBottom.label': 'Ansichtsfenster-Grenze unten',
62
+ 'settings.viewport.boundaryBottom.description': 'Untere Grenze in Weltkoordinaten. Legen Sie fest, wie weit das Ansichtsfenster nach unten verschoben werden kann.',
63
+ 'settings.viewport.boundaryPlaceholder': 'Unendlich',
64
+ 'settings.shortcuts.title': 'Tastenkürzel',
65
+ 'settings.developer.title': 'Entwickleroptionen',
66
+ 'settings.developer.showViewportInfo.label': 'Ansichtsfenster-Infos anzeigen',
67
+ 'settings.developer.showViewportInfo.description': 'Zeigt Debug-Informationen zum Ansichtsfenster an, z. B. Position, Zoomstufe und Grenzen.',
68
+ 'settings.developer.showObjectInfo.label': 'Objekt-Infos anzeigen',
69
+ 'settings.developer.showObjectInfo.description': 'Zeigt Debug-Informationen zu Objekten auf der Zeichenfläche an.',
70
+ 'settings.developer.showSyncProviderInfo.label': 'Sync-Provider-Infos anzeigen',
71
+ 'settings.developer.showSyncProviderInfo.description': 'Zeigt Debug-Informationen zum Verbindungsstatus des Sync-Providers an.',
72
+ 'settings.developer.showMigrationInfo.label': 'Migrations-Infos anzeigen',
73
+ 'settings.developer.showMigrationInfo.description': 'Zeigt Debug-Informationen zu Datenmigrationen an.',
74
+ 'settings.about.title': 'Über',
75
+ 'settings.about.description': 'Kritzel – Eine Zeichenanwendung',
76
+ // Export dialog
77
+ 'export.dialogTitle': 'Exportieren',
78
+ 'export.tabs.viewport': 'Ansichtsfenster exportieren',
79
+ 'export.tabs.workspace': 'Arbeitsbereich exportieren',
80
+ 'export.format.label': 'Format',
81
+ 'export.filename.label': 'Dateiname',
82
+ 'export.filename.placeholder': 'Dateiname eingeben',
83
+ 'export.exportButton': 'Exportieren',
84
+ // Workspace manager
85
+ 'workspace.sharedTooltip': 'Geteilter Arbeitsbereich',
86
+ 'workspace.rename': 'Umbenennen',
87
+ 'workspace.delete': 'Löschen',
88
+ // Zoom panel
89
+ 'zoom.zoomIn': 'Vergrößern',
90
+ 'zoom.zoomOut': 'Verkleinern',
91
+ // Utility panel
92
+ 'utility.undo': 'Rückgängig',
93
+ 'utility.redo': 'Wiederholen',
94
+ 'utility.delete': 'Ausgewählte Elemente löschen',
95
+ // Share dialog
96
+ 'share.dialogTitle': 'Arbeitsbereich teilen',
97
+ 'share.linkSharing.label': 'Link-Freigabe',
98
+ 'share.linkSharing.enabledDescription': 'Jeder mit dem Link kann auf diesen Arbeitsbereich zugreifen.',
99
+ 'share.linkSharing.disabledDescription': 'Die Link-Freigabe ist deaktiviert. Nur Sie können auf diesen Arbeitsbereich zugreifen.',
100
+ 'share.linkSharing.toggleLabel': 'Link-Freigabe aktivieren',
101
+ 'share.copyLink.title': 'Link kopieren',
102
+ 'share.copyLink.copied': 'Kopiert!',
103
+ // Login dialog
104
+ 'login.dialogTitle': 'Anmelden',
105
+ // Current user dialog
106
+ 'currentUser.dialogTitle': 'Konto',
107
+ // Back to content
108
+ 'backToContent.label': 'Zurück zum Inhalt',
109
+ // Tool config
110
+ 'toolConfig.collapse': 'Einklappen',
111
+ 'toolConfig.expand': 'Ausklappen',
112
+ // More menu button
113
+ 'moreMenu.ariaLabel': 'Weitere Optionen',
114
+ // Engine
115
+ 'engine.loading': 'Wird geladen...',
116
+ // Watermark
117
+ 'watermark.poweredBy': 'Bereitgestellt von Kritzel',
118
+ },
119
+ };