@pdfme/schemas 2.2.1 → 3.0.0-dev.1

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 (63) hide show
  1. package/dist/cjs/__tests__/renderUtils.test.js +96 -0
  2. package/dist/cjs/__tests__/renderUtils.test.js.map +1 -0
  3. package/dist/cjs/src/barcodes/pdfRender.js +3 -9
  4. package/dist/cjs/src/barcodes/pdfRender.js.map +1 -1
  5. package/dist/cjs/src/barcodes/propPanel.js +12 -12
  6. package/dist/cjs/src/barcodes/propPanel.js.map +1 -1
  7. package/dist/cjs/src/barcodes/uiRender.js +9 -7
  8. package/dist/cjs/src/barcodes/uiRender.js.map +1 -1
  9. package/dist/cjs/src/image/pdfRender.js +3 -9
  10. package/dist/cjs/src/image/pdfRender.js.map +1 -1
  11. package/dist/cjs/src/image/propPanel.js +10 -2
  12. package/dist/cjs/src/image/propPanel.js.map +1 -1
  13. package/dist/cjs/src/image/uiRender.js +7 -7
  14. package/dist/cjs/src/image/uiRender.js.map +1 -1
  15. package/dist/cjs/src/index.js +8 -3
  16. package/dist/cjs/src/index.js.map +1 -1
  17. package/dist/cjs/src/renderUtils.js +39 -55
  18. package/dist/cjs/src/renderUtils.js.map +1 -1
  19. package/dist/cjs/src/text/pdfRender.js +45 -5
  20. package/dist/cjs/src/text/pdfRender.js.map +1 -1
  21. package/dist/cjs/src/text/propPanel.js +13 -10
  22. package/dist/cjs/src/text/propPanel.js.map +1 -1
  23. package/dist/cjs/src/text/uiRender.js +17 -15
  24. package/dist/cjs/src/text/uiRender.js.map +1 -1
  25. package/dist/esm/__tests__/renderUtils.test.js +94 -0
  26. package/dist/esm/__tests__/renderUtils.test.js.map +1 -0
  27. package/dist/esm/src/barcodes/pdfRender.js +4 -10
  28. package/dist/esm/src/barcodes/pdfRender.js.map +1 -1
  29. package/dist/esm/src/barcodes/propPanel.js +12 -12
  30. package/dist/esm/src/barcodes/propPanel.js.map +1 -1
  31. package/dist/esm/src/barcodes/uiRender.js +9 -7
  32. package/dist/esm/src/barcodes/uiRender.js.map +1 -1
  33. package/dist/esm/src/image/pdfRender.js +4 -10
  34. package/dist/esm/src/image/pdfRender.js.map +1 -1
  35. package/dist/esm/src/image/propPanel.js +10 -2
  36. package/dist/esm/src/image/propPanel.js.map +1 -1
  37. package/dist/esm/src/image/uiRender.js +7 -7
  38. package/dist/esm/src/image/uiRender.js.map +1 -1
  39. package/dist/esm/src/index.js +6 -6
  40. package/dist/esm/src/index.js.map +1 -1
  41. package/dist/esm/src/renderUtils.js +37 -50
  42. package/dist/esm/src/renderUtils.js.map +1 -1
  43. package/dist/esm/src/text/pdfRender.js +46 -6
  44. package/dist/esm/src/text/pdfRender.js.map +1 -1
  45. package/dist/esm/src/text/propPanel.js +13 -10
  46. package/dist/esm/src/text/propPanel.js.map +1 -1
  47. package/dist/esm/src/text/uiRender.js +17 -15
  48. package/dist/esm/src/text/uiRender.js.map +1 -1
  49. package/dist/types/__tests__/renderUtils.test.d.ts +1 -0
  50. package/dist/types/src/index.d.ts +8 -3
  51. package/dist/types/src/renderUtils.d.ts +18 -9
  52. package/package.json +2 -2
  53. package/src/barcodes/pdfRender.ts +10 -10
  54. package/src/barcodes/propPanel.ts +26 -4
  55. package/src/barcodes/uiRender.ts +10 -7
  56. package/src/image/pdfRender.ts +11 -10
  57. package/src/image/propPanel.ts +10 -2
  58. package/src/image/uiRender.ts +8 -8
  59. package/src/index.ts +7 -6
  60. package/src/renderUtils.ts +51 -61
  61. package/src/text/pdfRender.ts +60 -15
  62. package/src/text/propPanel.ts +13 -10
  63. package/src/text/uiRender.ts +19 -12
@@ -13,7 +13,14 @@ const default40x20 = { width: 40, height: 20 };
13
13
  const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[] = [
14
14
  {
15
15
  defaultValue: 'https://pdfme.com/',
16
- defaultSchema: { type: 'qrcode', position, ...defaultColors, width: 30, height: 30 },
16
+ defaultSchema: {
17
+ type: 'qrcode',
18
+ position,
19
+ ...defaultColors,
20
+ width: 30,
21
+ height: 30,
22
+ rotate: 0,
23
+ },
17
24
  },
18
25
  {
19
26
  defaultValue: '6540123789-A-K-Z',
@@ -24,6 +31,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
24
31
  ...defaultTextColors,
25
32
  width: 80,
26
33
  height: 7.2,
34
+ rotate: 0,
27
35
  },
28
36
  },
29
37
  {
@@ -35,6 +43,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
35
43
  ...defaultTextColors,
36
44
  ...default40x20,
37
45
  height: 16,
46
+ rotate: 0,
38
47
  },
39
48
  },
40
49
  {
@@ -45,6 +54,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
45
54
  ...defaultColors,
46
55
  ...defaultTextColors,
47
56
  ...default40x20,
57
+ rotate: 0,
48
58
  },
49
59
  },
50
60
  {
@@ -65,6 +75,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
65
75
  ...defaultColors,
66
76
  ...defaultTextColors,
67
77
  ...default40x20,
78
+ rotate: 0,
68
79
  },
69
80
  },
70
81
  {
@@ -75,6 +86,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
75
86
  ...defaultColors,
76
87
  ...defaultTextColors,
77
88
  ...default40x20,
89
+ rotate: 0,
78
90
  },
79
91
  },
80
92
  {
@@ -86,6 +98,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
86
98
  ...defaultTextColors,
87
99
  ...default40x20,
88
100
  height: 12,
101
+ rotate: 0,
89
102
  },
90
103
  },
91
104
  {
@@ -97,6 +110,7 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
97
110
  ...defaultTextColors,
98
111
  ...default40x20,
99
112
  height: 16,
113
+ rotate: 0,
100
114
  },
101
115
  },
102
116
  {
@@ -107,11 +121,19 @@ const barcodeDefaults: { defaultValue: string; defaultSchema: BarcodeSchema }[]
107
121
  ...defaultColors,
108
122
  ...defaultTextColors,
109
123
  ...default40x20,
124
+ rotate: 0,
110
125
  },
111
126
  },
112
127
  {
113
128
  defaultValue: '(01)03453120000011(17)191125(10)ABCD1234',
114
- defaultSchema: { type: 'gs1datamatrix', position, ...defaultColors, width: 30, height: 30 },
129
+ defaultSchema: {
130
+ type: 'gs1datamatrix',
131
+ position,
132
+ ...defaultColors,
133
+ width: 30,
134
+ height: 30,
135
+ rotate: 0,
136
+ },
115
137
  },
116
138
  ];
117
139
 
@@ -127,7 +149,7 @@ export const getPropPanelByBarcodeType = (barcodeType: string): PropPanel<Barcod
127
149
  };
128
150
  const defaults = barcodeDefaults.find(({ defaultSchema }) => defaultSchema.type === barcodeType);
129
151
 
130
- if (!defaults) throw new Error(`No default for barcode type ${barcodeType}`);
152
+ if (!defaults) throw new Error(`[@pdfme/schemas] No default for barcode type ${barcodeType}`);
131
153
 
132
- return { propPanelSchema: schema, ...defaults };
154
+ return { schema, ...defaults };
133
155
  };
@@ -72,8 +72,8 @@ export const uiRender = async (arg: UIRenderProps<BarcodeSchema>) => {
72
72
  };
73
73
  Object.assign(container.style, containerStyle);
74
74
  rootElement.appendChild(container);
75
- const isForm = mode === 'form';
76
- if (isForm) {
75
+ const editable = mode === 'form' || mode === 'designer';
76
+ if (editable) {
77
77
  const input = document.createElement('input');
78
78
  const inputStyle: CSS.Properties = {
79
79
  ...fullSize,
@@ -81,7 +81,7 @@ export const uiRender = async (arg: UIRenderProps<BarcodeSchema>) => {
81
81
  textAlign: 'center',
82
82
  fontSize: '1rem',
83
83
  color: '#000',
84
- backgroundColor: isForm || value ? 'rgb(242 244 255 / 75%)' : 'none',
84
+ backgroundColor: editable || value ? 'rgb(242 244 255 / 75%)' : 'none',
85
85
  border: 'none',
86
86
  display: 'flex',
87
87
  alignItems: 'center',
@@ -99,17 +99,20 @@ export const uiRender = async (arg: UIRenderProps<BarcodeSchema>) => {
99
99
  stopEditing && stopEditing();
100
100
  });
101
101
  container.appendChild(input);
102
- input.setSelectionRange(value.length, value.length);
103
- input.focus();
102
+ if (mode === 'designer') {
103
+ input.setSelectionRange(value.length, value.length);
104
+ input.focus();
105
+ }
104
106
  }
105
107
 
106
108
  if (!value) return;
107
109
  try {
108
- if (!validateBarcodeInput(schema.type, value)) throw new Error('Invalid barcode input');
110
+ if (!validateBarcodeInput(schema.type, value))
111
+ throw new Error('[@pdfme/schemas] Invalid barcode input');
109
112
  const imgElm = await createBarcodeImageElm(schema, value);
110
113
  container.appendChild(imgElm);
111
114
  } catch (err) {
112
- console.error(err);
115
+ console.error(`[@pdfme/ui] ${err}`);
113
116
  const errorBarcodeElm = createErrorBarcodeElm();
114
117
  container.appendChild(errorBarcodeElm);
115
118
  }
@@ -1,18 +1,10 @@
1
1
  import type { PDFRenderProps } from '@pdfme/common';
2
2
  import type { ImageSchema } from './types';
3
- import { calcX, calcY, convertSchemaDimensionsToPt, getCacheKey } from '../renderUtils';
3
+ import { getCacheKey, convertForPdfLayoutProps } from '../renderUtils';
4
4
 
5
5
  export const pdfRender = async (arg: PDFRenderProps<ImageSchema>) => {
6
6
  const { value, schema, pdfDoc, page, _cache } = arg;
7
7
 
8
- const { width, height, rotate } = convertSchemaDimensionsToPt(schema);
9
- const opt = {
10
- x: calcX(schema.position.x, 'left', width, width),
11
- y: calcY(schema.position.y, page.getHeight(), height),
12
- rotate,
13
- width,
14
- height,
15
- };
16
8
  const inputImageCacheKey = getCacheKey(schema, value);
17
9
  let image = _cache.get(inputImageCacheKey);
18
10
  if (!image) {
@@ -20,5 +12,14 @@ export const pdfRender = async (arg: PDFRenderProps<ImageSchema>) => {
20
12
  image = await (isPng ? pdfDoc.embedPng(value) : pdfDoc.embedJpg(value));
21
13
  _cache.set(inputImageCacheKey, image);
22
14
  }
23
- page.drawImage(image, opt);
15
+
16
+ const pageHeight = page.getHeight();
17
+ const {
18
+ width,
19
+ height,
20
+ rotate,
21
+ position: { x, y },
22
+ } = convertForPdfLayoutProps({ schema, pageHeight });
23
+
24
+ page.drawImage(image, { x, y, rotate, width, height });
24
25
  };
@@ -1,8 +1,16 @@
1
1
  import { PropPanel } from '@pdfme/common';
2
2
  import type { ImageSchema } from './types';
3
3
  export const propPanel: PropPanel<ImageSchema> = {
4
- propPanelSchema: {},
4
+ schema: {},
5
5
  defaultValue:
6
6
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAu4AAALuAQMAAADL0wGJAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURbzAw+rv8fKruy0AAAPoSURBVHja7dwxbtwwEEBRCkKwRQodYftcYk+ROkcJz5NTsEuZK/AIKlIQAUEnke0VqQ0pA5zxWvFnZcD2s0CNuENxPOZBc0QDDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PD/+P8bkxvnTzzjTG0M2b5rh08rHNT518aPOnTt63+aGTd23edPJ2h//ax+/oO6Gzx6c78+cuPu7x01vmwx5/6uLnO/PjO+b/rGifvtWH3VnT9vmh/e3eqx/bc9d79af2YwEPDw8P/6r8r1GVt5VcUoZPtXxGhp9rGYEM72vbEBne1hJ5Gb6ayIvwqZppi/CxmgqL8KGaTIrwc3WDfATeV/ffIryr7r+PwNvq/vsIfH17D5/qbw/gb/mLKp/OqnycFPj14yqcJPm4vfp5VJ0cP6jyzqjy9rr2q/Dr4qzBp3V5E1wxp/V3rl8LXn32qF6fAY31Psv2NXi/5lQaH+Vuzak0eLsmPRppVPa3FPiU3QiFFDZmD4FCAh6yxV+Bn7O9isLmx2d/TGHr5rI7obDxtFkcKWyb89M2+U1/sX7Kv7Io8gaxFy5l2D/faRk++3x6PgsalV52+fwUW4j/+eGhDPuneyH/otHmp9jyfHHMLM6n4phZnI/FObA4H4qDWnF+Lo46xXlflG+I864o3xDnbVG+Ic6X9RXSfCrrK6T5WBZASPOhrFCQ5ufyjF+a92XdkjTvyrolad6WdUvS/KawSI7/sQn7JfDl+O+bsF8CX44fN2FvHnNnIT4Nm7BfAl+ON5uwXwJfjA/LCuk2BXvCvN0U7InxflmAtxV1gvx0U2N3luPtdZOlwj/FoR5vbuq85Pi48F6LD0scOk3+sq1cleP9ohlNfkpq/N9pGaMuH7T4ZVqGWYt/nBavyxstftblfaVIWJU/y72yODCfzJH5oMvPh+adLm+PzCdzZD5U/61ClT9Lnvyo8e7QvFHlE3ydj0zOu5ucickhcpgcJofJedUVk8j5b/nGgIeHh4d/I3x/RwLlhgfNH3DavSwGXf7YjT76uqCEQ/P37p9z7uKVmwspd17S7hul3PXK6fbsmnvWhO6GZlMnn3ri8gXN5GzHnX0B35ydj91814CHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHl+d/A9cKjmiL040TAAAAAElFTkSuQmCC',
7
- defaultSchema: { type: 'image', position: { x: 0, y: 0 }, width: 40, height: 40 },
7
+ defaultSchema: {
8
+ type: 'image',
9
+ position: { x: 0, y: 0 },
10
+ width: 40,
11
+ height: 40,
12
+ // If the value of "rotate" is set to undefined or not set at all, rotation will be disabled in the UI.
13
+ // Check this document: https://pdfme.com//docs/custom-schemas#learning-how-to-create-from-pdfmeschemas-code
14
+ rotate: 0,
15
+ },
8
16
  };
@@ -16,7 +16,7 @@ const readFile = (input: File | FileList | null): Promise<string | ArrayBuffer>
16
16
  };
17
17
 
18
18
  fileReader.onerror = (e) => {
19
- reject(new Error('File reading failed'));
19
+ reject(new Error('[@pdfme/schemas] File reading failed'));
20
20
  };
21
21
 
22
22
  let file: File | null = null;
@@ -29,13 +29,13 @@ const readFile = (input: File | FileList | null): Promise<string | ArrayBuffer>
29
29
  if (file) {
30
30
  fileReader.readAsDataURL(file);
31
31
  } else {
32
- reject(new Error('No files provided'));
32
+ reject(new Error('[@pdfme/schemas] No files provided'));
33
33
  }
34
34
  });
35
35
 
36
36
  export const uiRender = async (arg: UIRenderProps<ImageSchema>) => {
37
37
  const { value, rootElement, mode, onChange, stopEditing, tabIndex, placeholder, schema } = arg;
38
- const isForm = mode === 'form';
38
+ const editable = mode === 'form' || mode === 'designer';
39
39
 
40
40
  const size = { width: schema.width * ZOOM, height: schema.height * ZOOM };
41
41
 
@@ -47,7 +47,7 @@ export const uiRender = async (arg: UIRenderProps<ImageSchema>) => {
47
47
  };
48
48
  Object.assign(container.style, containerStyle);
49
49
  container.addEventListener('click', (e) => {
50
- if (isForm) {
50
+ if (editable) {
51
51
  e.stopPropagation();
52
52
  }
53
53
  });
@@ -63,7 +63,7 @@ export const uiRender = async (arg: UIRenderProps<ImageSchema>) => {
63
63
  }
64
64
 
65
65
  // remove button
66
- if (value && isForm) {
66
+ if (value && editable) {
67
67
  const button = document.createElement('button');
68
68
  button.textContent = 'x';
69
69
  const buttonStyle: CSS.Properties = {
@@ -90,14 +90,14 @@ export const uiRender = async (arg: UIRenderProps<ImageSchema>) => {
90
90
  }
91
91
 
92
92
  // file input
93
- if (!value && isForm) {
93
+ if (!value && editable) {
94
94
  const label = document.createElement('label');
95
95
  const labelStyle: CSS.Properties = {
96
96
  ...fullSize,
97
- display: isForm ? 'flex' : 'none',
97
+ display: editable ? 'flex' : 'none',
98
98
  position: 'absolute',
99
99
  top: 0,
100
- backgroundColor: isForm || value ? 'rgb(242 244 255 / 75%)' : 'none',
100
+ backgroundColor: editable || value ? 'rgb(242 244 255 / 50%)' : 'none',
101
101
  cursor: 'pointer',
102
102
  };
103
103
  Object.assign(label.style, labelStyle);
package/src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
- import textSchema from './text';
2
- import imageSchema from './image';
3
- import barcodesSchemaObj from './barcodes';
1
+ import text from './text';
2
+ import image from './image';
3
+ import barcodes from './barcodes';
4
+ import { convertForPdfLayoutProps, rotatePoint } from './renderUtils';
4
5
 
5
- export const text = textSchema;
6
- export const image = imageSchema;
7
- export const barcodes = barcodesSchemaObj;
6
+ const builtInPlugins = { Text: text };
7
+
8
+ export { text, image, barcodes, builtInPlugins, convertForPdfLayoutProps, rotatePoint };
@@ -1,74 +1,64 @@
1
- import { PDFPage, degrees, rgb } from '@pdfme/pdf-lib';
1
+ import { degrees, degreesToRadians } from '@pdfme/pdf-lib';
2
2
  import { Schema, mm2pt } from '@pdfme/common';
3
3
 
4
- const hex2rgb = (hex: string) => {
5
- if (hex.slice(0, 1) === '#') hex = hex.slice(1);
6
- if (hex.length === 3)
7
- hex =
8
- hex.slice(0, 1) +
9
- hex.slice(0, 1) +
10
- hex.slice(1, 2) +
11
- hex.slice(1, 2) +
12
- hex.slice(2, 3) +
13
- hex.slice(2, 3);
14
-
15
- return [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map((str) => parseInt(str, 16));
16
- };
17
-
18
- export const hex2RgbColor = (hexString: string | undefined) => {
19
- if (hexString) {
20
- const [r, g, b] = hex2rgb(hexString);
21
-
22
- return rgb(r / 255, g / 255, b / 255);
23
- }
4
+ export const convertForPdfLayoutProps = ({
5
+ schema,
6
+ pageHeight,
7
+ applyRotateTranslate = true,
8
+ }: {
9
+ schema: Schema;
10
+ pageHeight: number;
11
+ applyRotateTranslate?: boolean;
12
+ }) => {
13
+ const { width: mmWidth, height: mmHeight, position, rotate } = schema;
14
+ const { x: mmX, y: mmY } = position;
24
15
 
25
- // eslint-disable-next-line no-undefined
26
- return undefined;
27
- };
16
+ const rotateDegrees = rotate ? -rotate : 0;
17
+ const width = mm2pt(mmWidth);
18
+ const height = mm2pt(mmHeight);
19
+ let x = mm2pt(mmX);
20
+ // PDF coordinate system is from bottom left, UI is top left, so we need to flip the y axis
21
+ let y = pageHeight - mm2pt(mmY) - height;
28
22
 
29
- export const calcX = (
30
- x: number,
31
- alignment: 'left' | 'center' | 'right',
32
- boxWidth: number,
33
- textWidth: number
34
- ) => {
35
- let addition = 0;
36
- if (alignment === 'center') {
37
- addition = (boxWidth - textWidth) / 2;
38
- } else if (alignment === 'right') {
39
- addition = boxWidth - textWidth;
23
+ if (rotateDegrees && applyRotateTranslate) {
24
+ // If rotating we must pivot around the same point as the UI performs its rotation.
25
+ // The UI performs rotation around the objects center point (the pivot point below),
26
+ // pdflib rotates around the bottom left corner of the object.
27
+ // We must therefore adjust the X and Y by rotating the bottom left corner by this pivot point.
28
+ const pivotPoint = { x: x + width / 2, y: pageHeight - mm2pt(mmY) - height / 2 };
29
+ const rotatedPoint = rotatePoint({ x, y }, pivotPoint, rotateDegrees);
30
+ x = rotatedPoint.x;
31
+ y = rotatedPoint.y;
40
32
  }
41
33
 
42
- return mm2pt(x) + addition;
34
+ return {
35
+ position: {
36
+ x: x,
37
+ y: y,
38
+ },
39
+ height: height,
40
+ width: width,
41
+ rotate: degrees(rotateDegrees),
42
+ };
43
43
  };
44
44
 
45
- export const calcY = (y: number, pageHeight: number, itemHeight: number) =>
46
- pageHeight - mm2pt(y) - itemHeight;
47
-
48
- export const renderBackgroundColor = (arg: {
49
- schema: Schema;
50
- page: PDFPage;
51
- pageHeight: number;
52
- }) => {
53
- const { schema, page, pageHeight } = arg;
54
- if (!schema.backgroundColor) return;
55
- const { width, height } = convertSchemaDimensionsToPt(schema);
56
- const color = hex2RgbColor(schema.backgroundColor as string);
57
- page.drawRectangle({
58
- x: calcX(schema.position.x, 'left', width, width),
59
- y: calcY(schema.position.y, pageHeight, height),
60
- width,
61
- height,
62
- color,
63
- });
64
- };
45
+ export const rotatePoint = (
46
+ point: { x: number; y: number },
47
+ pivot: { x: number; y: number },
48
+ angleDegrees: number
49
+ ): { x: number; y: number } => {
50
+ const angleRadians = degreesToRadians(angleDegrees);
65
51
 
66
- export const convertSchemaDimensionsToPt = (schema: Schema) => {
67
- const width = mm2pt(schema.width);
68
- const height = mm2pt(schema.height);
69
- const rotate = degrees(schema.rotate ? schema.rotate : 0);
52
+ const x =
53
+ Math.cos(angleRadians) * (point.x - pivot.x) -
54
+ Math.sin(angleRadians) * (point.y - pivot.y) +
55
+ pivot.x;
56
+ const y =
57
+ Math.sin(angleRadians) * (point.x - pivot.x) +
58
+ Math.cos(angleRadians) * (point.y - pivot.y) +
59
+ pivot.y;
70
60
 
71
- return { width, height, rotate };
61
+ return { x, y };
72
62
  };
73
63
 
74
64
  export const getCacheKey = (schema: Schema, input: string) => `${schema.type}${input}`;
@@ -1,5 +1,5 @@
1
- import { PDFFont, PDFDocument } from '@pdfme/pdf-lib';
2
- import { PDFRenderProps, Font, getDefaultFont, getFallbackFontName } from '@pdfme/common';
1
+ import { PDFFont, PDFDocument, rgb } from '@pdfme/pdf-lib';
2
+ import { PDFRenderProps, Font, getDefaultFont, getFallbackFontName, mm2pt } from '@pdfme/common';
3
3
  import type { TextSchema, FontWidthCalcValues } from './types';
4
4
  import {
5
5
  VERTICAL_ALIGN_TOP,
@@ -20,13 +20,31 @@ import {
20
20
  getSplittedLines,
21
21
  widthOfTextAtSize,
22
22
  } from './helper';
23
- import {
24
- hex2RgbColor,
25
- calcX,
26
- calcY,
27
- renderBackgroundColor,
28
- convertSchemaDimensionsToPt,
29
- } from '../renderUtils';
23
+ import { convertForPdfLayoutProps, rotatePoint } from '../renderUtils';
24
+
25
+ const hex2rgb = (hex: string) => {
26
+ if (hex.slice(0, 1) === '#') hex = hex.slice(1);
27
+ if (hex.length === 3)
28
+ hex =
29
+ hex.slice(0, 1) +
30
+ hex.slice(0, 1) +
31
+ hex.slice(1, 2) +
32
+ hex.slice(1, 2) +
33
+ hex.slice(2, 3) +
34
+ hex.slice(2, 3);
35
+
36
+ return [hex.slice(0, 2), hex.slice(2, 4), hex.slice(4, 6)].map((str) => parseInt(str, 16));
37
+ };
38
+
39
+ const hex2RgbColor = (hexString: string | undefined) => {
40
+ if (hexString) {
41
+ const [r, g, b] = hex2rgb(hexString);
42
+
43
+ return rgb(r / 255, g / 255, b / 255);
44
+ }
45
+
46
+ return undefined;
47
+ };
30
48
 
31
49
  const embedAndGetFontObjCache = new WeakMap();
32
50
  const embedAndGetFontObj = async (arg: { pdfDoc: PDFDocument; font: Font }) => {
@@ -99,9 +117,17 @@ export const pdfRender = async (arg: PDFRenderProps<TextSchema>) => {
99
117
  const pdfFontValue = pdfFontObj[fontName];
100
118
 
101
119
  const pageHeight = page.getHeight();
102
- renderBackgroundColor({ schema, page, pageHeight });
103
-
104
- const { width, height, rotate } = convertSchemaDimensionsToPt(schema);
120
+ const {
121
+ width,
122
+ height,
123
+ rotate,
124
+ position: { x, y },
125
+ } = convertForPdfLayoutProps({ schema, pageHeight, applyRotateTranslate: false });
126
+
127
+ if (schema.backgroundColor) {
128
+ const color = hex2RgbColor(schema.backgroundColor as string);
129
+ page.drawRectangle({ x, y, width, height, rotate, color });
130
+ }
105
131
 
106
132
  page.pushOperators(pdfLib.setCharacterSpacing(characterSpacing ?? DEFAULT_CHARACTER_SPACING));
107
133
 
@@ -117,7 +143,7 @@ export const pdfRender = async (arg: PDFRenderProps<TextSchema>) => {
117
143
  };
118
144
 
119
145
  let lines: string[] = [];
120
- value.split(/\r|\n|\r\n/g).forEach((line) => {
146
+ value.split(/\r|\n|\r\n/g).forEach((line: string) => {
121
147
  lines = lines.concat(getSplittedLines(line, fontWidthCalcValues));
122
148
  });
123
149
 
@@ -136,13 +162,32 @@ export const pdfRender = async (arg: PDFRenderProps<TextSchema>) => {
136
162
  }
137
163
  }
138
164
 
165
+ const pivotPoint = { x: x + width / 2, y: pageHeight - mm2pt(schema.position.y) - height / 2 };
166
+
139
167
  lines.forEach((line, rowIndex) => {
140
168
  const textWidth = widthOfTextAtSize(line, fontKitFont, fontSize, characterSpacing);
141
169
  const rowYOffset = lineHeight * fontSize * rowIndex;
142
170
 
171
+ let xLine = x;
172
+ if (alignment === 'center') {
173
+ xLine += (width - textWidth) / 2;
174
+ } else if (alignment === 'right') {
175
+ xLine += width - textWidth;
176
+ }
177
+
178
+ let yLine = pageHeight - mm2pt(schema.position.y) - yOffset - rowYOffset;
179
+
180
+ if (rotate.angle !== 0) {
181
+ // As we draw each line individually from different points, we must translate each lines position
182
+ // relative to the UI rotation pivot point. see comments in convertForPdfLayoutProps() for more info.
183
+ const rotatedPoint = rotatePoint({ x: xLine, y: yLine }, pivotPoint, rotate.angle);
184
+ xLine = rotatedPoint.x;
185
+ yLine = rotatedPoint.y;
186
+ }
187
+
143
188
  page.drawText(line, {
144
- x: calcX(schema.position.x, alignment, width, textWidth),
145
- y: calcY(schema.position.y, pageHeight, yOffset) - rowYOffset,
189
+ x: xLine,
190
+ y: yLine,
146
191
  rotate,
147
192
  size: fontSize,
148
193
  color,
@@ -49,7 +49,7 @@ const UseDynamicFontSize = (props: PropPanelWidgetProps) => {
49
49
  };
50
50
 
51
51
  export const propPanel: PropPanel<TextSchema> = {
52
- propPanelSchema: ({ options, activeSchema }) => {
52
+ schema: ({ options, activeSchema }) => {
53
53
  const font = options.font || { [DEFAULT_FONT_NAME]: { data: '', fallback: true } };
54
54
  const fontNames = Object.keys(font);
55
55
  const fallbackFontName = getFallbackFontName(font);
@@ -63,8 +63,16 @@ export const propPanel: PropPanel<TextSchema> = {
63
63
  widget: 'select',
64
64
  default: fallbackFontName,
65
65
  props: { options: fontNames.map((name) => ({ label: name, value: name })) },
66
- span: 8,
66
+ span: 12,
67
+ },
68
+ fontSize: {
69
+ title: 'Size',
70
+ type: 'number',
71
+ widget: 'inputNumber',
72
+ span: 6,
73
+ disabled: enableDynamicFont,
67
74
  },
75
+ characterSpacing: { title: 'Spacing', type: 'number', widget: 'inputNumber', span: 6 },
68
76
  alignment: {
69
77
  title: 'Text Align',
70
78
  type: 'string',
@@ -91,15 +99,7 @@ export const propPanel: PropPanel<TextSchema> = {
91
99
  },
92
100
  span: 8,
93
101
  },
94
- fontSize: {
95
- title: 'Font Size',
96
- type: 'number',
97
- widget: 'inputNumber',
98
- span: 8,
99
- disabled: enableDynamicFont,
100
- },
101
102
  lineHeight: { title: 'Line Height', type: 'number', widget: 'inputNumber', span: 8 },
102
- characterSpacing: { title: 'Char Spc', type: 'number', widget: 'inputNumber', span: 8 },
103
103
  useDynamicFontSize: { type: 'boolean', widget: 'UseDynamicFontSize', bind: false },
104
104
  dynamicFontSize: {
105
105
  type: 'object',
@@ -135,6 +135,9 @@ export const propPanel: PropPanel<TextSchema> = {
135
135
  position: { x: 0, y: 0 },
136
136
  width: 45,
137
137
  height: 10,
138
+ // If the value of "rotate" is set to undefined or not set at all, rotation will be disabled in the UI.
139
+ // Check this document: https://pdfme.com//docs/custom-schemas#learning-how-to-create-from-pdfmeschemas-code
140
+ rotate: 0,
138
141
  alignment: DEFAULT_ALIGNMENT,
139
142
  verticalAlignment: DEFAULT_VERTICAL_ALIGNMENT,
140
143
  fontSize: DEFAULT_FONT_SIZE,
@@ -30,6 +30,20 @@ const mapVerticalAlignToFlex = (verticalAlignmentValue: string | undefined) => {
30
30
  return 'flex-start';
31
31
  };
32
32
 
33
+ const getBackgroundColor = (
34
+ mode: 'form' | 'viewer' | 'designer',
35
+ value: string,
36
+ schema: Schema
37
+ ) => {
38
+ if ((mode === 'form' || mode === 'designer') && value && schema.backgroundColor) {
39
+ return schema.backgroundColor as string;
40
+ } else if (mode === 'viewer') {
41
+ return (schema.backgroundColor as string) ?? 'transparent';
42
+ } else {
43
+ return 'rgb(242 244 255 / 75%)';
44
+ }
45
+ };
46
+
33
47
  export const uiRender = async (arg: UIRenderProps<TextSchema>) => {
34
48
  const {
35
49
  value,
@@ -68,15 +82,6 @@ export const uiRender = async (arg: UIRenderProps<TextSchema>) => {
68
82
  const bottomAdjustment = bottomAdj;
69
83
 
70
84
  const container = document.createElement('div');
71
- function getBackgroundColor(mode: 'form' | 'viewer', value: string, schema: Schema) {
72
- if (mode === 'form' && value && schema.backgroundColor) {
73
- return schema.backgroundColor as string;
74
- } else if (mode === 'viewer') {
75
- return (schema.backgroundColor as string) ?? 'transparent';
76
- } else {
77
- return 'rgb(242 244 255 / 75%)';
78
- }
79
- }
80
85
 
81
86
  const containerStyle: CSS.Properties = {
82
87
  padding: 0,
@@ -104,7 +109,7 @@ export const uiRender = async (arg: UIRenderProps<TextSchema>) => {
104
109
  wordBreak: 'break-word',
105
110
  };
106
111
 
107
- if (mode === 'form') {
112
+ if (mode === 'form' || mode === 'designer') {
108
113
  const textarea = document.createElement('textarea');
109
114
  const textareaStyle: CSS.Properties = {
110
115
  padding: 0,
@@ -128,8 +133,10 @@ export const uiRender = async (arg: UIRenderProps<TextSchema>) => {
128
133
  textarea.addEventListener('blur', () => stopEditing && stopEditing());
129
134
  textarea.value = value;
130
135
  container.appendChild(textarea);
131
- textarea.setSelectionRange(value.length, value.length);
132
- textarea.focus();
136
+ if (mode === 'designer') {
137
+ textarea.setSelectionRange(value.length, value.length);
138
+ textarea.focus();
139
+ }
133
140
  } else {
134
141
  const div = document.createElement('div');
135
142
  const divStyle: CSS.Properties = {