@pdfme/generator 2.0.2 → 2.2.0

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.
@@ -56,7 +56,6 @@ export declare const drawInputByTemplateSchema: (arg: {
56
56
  templateSchema: Schema;
57
57
  pdfDoc: PDFDocument;
58
58
  page: PDFPage;
59
- pageHeight: number;
60
59
  fontSetting: FontSetting;
61
60
  inputImageCache: InputImageCache;
62
61
  }) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdfme/generator",
3
- "version": "2.0.2",
3
+ "version": "2.2.0",
4
4
  "sideEffects": false,
5
5
  "author": "hand-dot",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "pngjs": "^6.0.0"
61
61
  },
62
62
  "peerDependencies": {
63
- "@pdfme/common": "^2.0.0"
63
+ "@pdfme/common": "^2.1.0"
64
64
  },
65
65
  "jest": {
66
66
  "resolver": "ts-jest-resolver",
package/src/generate.ts CHANGED
@@ -75,7 +75,6 @@ const generate = async (props: GenerateProps) => {
75
75
  templateSchema,
76
76
  pdfDoc,
77
77
  page,
78
- pageHeight,
79
78
  fontSetting,
80
79
  inputImageCache,
81
80
  });
package/src/helper.ts CHANGED
@@ -25,14 +25,23 @@ import {
25
25
  BasePdf,
26
26
  BarCodeType,
27
27
  Alignment,
28
- DEFAULT_FONT_SIZE,
29
28
  DEFAULT_ALIGNMENT,
30
- DEFAULT_LINE_HEIGHT,
31
29
  DEFAULT_CHARACTER_SPACING,
32
30
  DEFAULT_FONT_COLOR,
31
+ DEFAULT_FONT_SIZE,
32
+ DEFAULT_LINE_HEIGHT,
33
+ DEFAULT_VERTICAL_ALIGNMENT,
34
+ VERTICAL_ALIGN_TOP,
35
+ VERTICAL_ALIGN_MIDDLE,
36
+ VERTICAL_ALIGN_BOTTOM,
33
37
  calculateDynamicFontSize,
34
38
  heightOfFontAtSize,
35
- getFontKitFont
39
+ getFontDescentInPt,
40
+ getFontKitFont,
41
+ getSplittedLines,
42
+ mm2pt,
43
+ widthOfTextAtSize,
44
+ FontWidthCalcValues,
36
45
  } from '@pdfme/common';
37
46
  import { Buffer } from 'buffer';
38
47
 
@@ -130,14 +139,7 @@ export const getEmbeddedPagesAndEmbedPdfBoxes = async (arg: {
130
139
  return { embeddedPages, embedPdfBoxes };
131
140
  };
132
141
 
133
- const mm2pt = (mm: number): number => {
134
- // https://www.ddc.co.jp/words/archives/20090701114500.html
135
- const ptRatio = 2.8346;
136
-
137
- return parseFloat(String(mm)) * ptRatio;
138
- };
139
-
140
- const getSchemaSizeAndRotate = (schema: Schema) => {
142
+ const convertSchemaDimensionsToPt = (schema: Schema) => {
141
143
  const width = mm2pt(schema.width);
142
144
  const height = mm2pt(schema.height);
143
145
  const rotate = degrees(schema.rotate ? schema.rotate : 0);
@@ -171,13 +173,14 @@ const hex2RgbColor = (hexString: string | undefined) => {
171
173
  };
172
174
 
173
175
  const getFontProp = async ({ input, font, schema }: { input: string, font: Font, schema: TextSchema }) => {
174
- const size = schema.dynamicFontSize ? await calculateDynamicFontSize({ textSchema: schema, font, input }) : schema.fontSize ?? DEFAULT_FONT_SIZE;
176
+ const fontSize = schema.dynamicFontSize ? await calculateDynamicFontSize({ textSchema: schema, font, input }) : schema.fontSize ?? DEFAULT_FONT_SIZE;
175
177
  const color = hex2RgbColor(schema.fontColor ?? DEFAULT_FONT_COLOR);
176
178
  const alignment = schema.alignment ?? DEFAULT_ALIGNMENT;
179
+ const verticalAlignment = schema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT;
177
180
  const lineHeight = schema.lineHeight ?? DEFAULT_LINE_HEIGHT;
178
181
  const characterSpacing = schema.characterSpacing ?? DEFAULT_CHARACTER_SPACING;
179
182
 
180
- return { size, color, alignment, lineHeight, characterSpacing };
183
+ return { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing };
181
184
  };
182
185
 
183
186
  const calcX = (x: number, alignment: Alignment, boxWidth: number, textWidth: number) => {
@@ -191,7 +194,7 @@ const calcX = (x: number, alignment: Alignment, boxWidth: number, textWidth: num
191
194
  return mm2pt(x) + addition;
192
195
  };
193
196
 
194
- const calcY = (y: number, height: number, itemHeight: number) => height - mm2pt(y) - itemHeight;
197
+ const calcY = (y: number, pageHeight: number, itemHeight: number) => pageHeight - mm2pt(y) - itemHeight;
195
198
 
196
199
  const drawBackgroundColor = (arg: {
197
200
  templateSchema: TextSchema;
@@ -200,7 +203,7 @@ const drawBackgroundColor = (arg: {
200
203
  }) => {
201
204
  const { templateSchema, page, pageHeight } = arg;
202
205
  if (!templateSchema.backgroundColor) return;
203
- const { width, height } = getSchemaSizeAndRotate(templateSchema);
206
+ const { width, height } = convertSchemaDimensionsToPt(templateSchema);
204
207
  const color = hex2RgbColor(templateSchema.backgroundColor);
205
208
  page.drawRectangle({
206
209
  x: calcX(templateSchema.position.x, 'left', width, width),
@@ -211,57 +214,6 @@ const drawBackgroundColor = (arg: {
211
214
  });
212
215
  };
213
216
 
214
- type IsOverEval = (testString: string) => boolean;
215
-
216
- /**
217
- * Incrementally checks the current line for its real length
218
- * and returns the position where it exceeds the box width.
219
- * Returns `null` to indicate if inputLine is shorter as the available bbox.
220
- */
221
- const getOverPosition = (inputLine: string, isOverEval: IsOverEval) => {
222
- for (let i = 0; i <= inputLine.length; i++) {
223
- if (isOverEval(inputLine.slice(0, i))) {
224
- return i;
225
- }
226
- }
227
-
228
- return null;
229
- };
230
-
231
- /**
232
- * Gets the position of the split. Splits the exceeding line at
233
- * the last whitespace over it exceeds the bounding box width.
234
- */
235
- const getSplitPosition = (inputLine: string, isOverEval: IsOverEval) => {
236
- const overPos = getOverPosition(inputLine, isOverEval);
237
- if (overPos === null) return inputLine.length; // input line is shorter than the available space
238
-
239
- let overPosTmp = overPos;
240
- while (inputLine[overPosTmp] !== ' ' && overPosTmp >= 0) {
241
- overPosTmp--;
242
- }
243
-
244
- // For very long lines with no whitespace use the original overPos
245
- return overPosTmp > 0 ? overPosTmp : overPos - 1;
246
- };
247
-
248
- /**
249
- * Recursively splits the line at getSplitPosition.
250
- * If there is some leftover, split the rest again in the same manner.
251
- */
252
- const getSplittedLines = (inputLine: string, isOverEval: IsOverEval): string[] => {
253
- const splitPos = getSplitPosition(inputLine, isOverEval);
254
- const splittedLine = inputLine.substring(0, splitPos);
255
- const rest = inputLine.substring(splitPos).trimStart();
256
-
257
- if (rest.length === 0) {
258
- // end recursion if there is no rest
259
- return [splittedLine];
260
- }
261
-
262
- return [splittedLine, ...getSplittedLines(rest, isOverEval)];
263
- };
264
-
265
217
  interface FontSetting {
266
218
  font: Font;
267
219
  pdfFontObj: {
@@ -275,60 +227,72 @@ const drawInputByTextSchema = async (arg: {
275
227
  templateSchema: TextSchema;
276
228
  pdfDoc: PDFDocument;
277
229
  page: PDFPage;
278
- pageHeight: number;
279
230
  fontSetting: FontSetting;
280
231
  }) => {
281
- const { input, templateSchema, page, pageHeight, fontSetting } = arg;
232
+ const { input, templateSchema, page, fontSetting } = arg;
282
233
  const { font, pdfFontObj, fallbackFontName } = fontSetting;
283
234
 
284
235
  const pdfFontValue = pdfFontObj[templateSchema.fontName ? templateSchema.fontName : fallbackFontName];
285
- const fontKitFont = await getFontKitFont(templateSchema, font)
236
+ const fontKitFont = await getFontKitFont(templateSchema, font);
286
237
 
238
+ const pageHeight = page.getHeight();
287
239
  drawBackgroundColor({ templateSchema, page, pageHeight });
288
240
 
289
- const { width, height, rotate } = getSchemaSizeAndRotate(templateSchema);
290
- const { size, color, alignment, lineHeight, characterSpacing } = await getFontProp({
291
- input,
292
- font,
293
- schema: templateSchema,
294
- });
241
+ const { width, height, rotate } = convertSchemaDimensionsToPt(templateSchema);
242
+ const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } =
243
+ await getFontProp({
244
+ input,
245
+ font,
246
+ schema: templateSchema,
247
+ });
295
248
 
296
249
  page.pushOperators(setCharacterSpacing(characterSpacing));
297
250
 
298
- let beforeLineOver = 0;
251
+ const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize);
252
+ const descent = getFontDescentInPt(fontKitFont, fontSize);
253
+ const halfLineHeightAdjustment = lineHeight === 0 ? 0 : ((lineHeight - 1) * fontSize) / 2;
299
254
 
300
- const isOverEval = (testString: string) => {
301
- const testStringWidth =
302
- pdfFontValue.widthOfTextAtSize(testString, size) + (testString.length - 1) * characterSpacing;
303
- return width <= testStringWidth;
255
+ const fontWidthCalcValues: FontWidthCalcValues = {
256
+ font: fontKitFont,
257
+ fontSize,
258
+ characterSpacing,
259
+ boxWidthInPt: width,
304
260
  };
305
- input.split(/\r|\n|\r\n/g).forEach((inputLine, inputLineIndex) => {
306
- const splitLines = getSplittedLines(inputLine, isOverEval);
307
-
308
- const drawLine = (line: string, lineIndex: number) => {
309
- const textWidth = pdfFontValue.widthOfTextAtSize(line, size) + (line.length - 1) * characterSpacing;
310
- const textHeight = heightOfFontAtSize(fontKitFont, size);
311
-
312
- page.drawText(line, {
313
- x: calcX(templateSchema.position.x, alignment, width, textWidth),
314
- y:
315
- calcY(templateSchema.position.y, pageHeight, height) +
316
- height -
317
- textHeight -
318
- lineHeight * size * (inputLineIndex + lineIndex + beforeLineOver) -
319
- (lineHeight === 0 ? 0 : ((lineHeight - 1) * size) / 2),
320
- rotate,
321
- size,
322
- color,
323
- lineHeight: lineHeight * size,
324
- maxWidth: width,
325
- font: pdfFontValue,
326
- wordBreaks: [''],
327
- });
328
- if (splitLines.length === lineIndex + 1) beforeLineOver += lineIndex;
329
- };
330
261
 
331
- splitLines.forEach(drawLine);
262
+ let lines: string[] = [];
263
+ input.split(/\r|\n|\r\n/g).forEach((inputLine) => {
264
+ lines = lines.concat(getSplittedLines(inputLine, fontWidthCalcValues));
265
+ });
266
+
267
+ // Text lines are rendered from the bottom upwards, we need to adjust the position down
268
+ let yOffset = 0;
269
+ if (verticalAlignment === VERTICAL_ALIGN_TOP) {
270
+ yOffset = firstLineTextHeight + halfLineHeightAdjustment;
271
+ } else {
272
+ const otherLinesHeight = lineHeight * fontSize * (lines.length - 1);
273
+
274
+ if (verticalAlignment === VERTICAL_ALIGN_BOTTOM) {
275
+ yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment;
276
+ } else if (verticalAlignment === VERTICAL_ALIGN_MIDDLE) {
277
+ yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight;
278
+ }
279
+ }
280
+
281
+ lines.forEach((line, rowIndex) => {
282
+ const textWidth = widthOfTextAtSize(line, fontKitFont, fontSize, characterSpacing);
283
+ const rowYOffset = lineHeight * fontSize * rowIndex;
284
+
285
+ page.drawText(line, {
286
+ x: calcX(templateSchema.position.x, alignment, width, textWidth),
287
+ y: calcY(templateSchema.position.y, pageHeight, yOffset) - rowYOffset,
288
+ rotate,
289
+ size: fontSize,
290
+ color,
291
+ lineHeight: lineHeight * fontSize,
292
+ maxWidth: width,
293
+ font: pdfFontValue,
294
+ wordBreaks: [''],
295
+ });
332
296
  });
333
297
  };
334
298
 
@@ -337,17 +301,16 @@ const getCacheKey = (templateSchema: Schema, input: string) => `${templateSchema
337
301
  const drawInputByImageSchema = async (arg: {
338
302
  input: string;
339
303
  templateSchema: ImageSchema;
340
- pageHeight: number;
341
304
  pdfDoc: PDFDocument;
342
305
  page: PDFPage;
343
306
  inputImageCache: InputImageCache;
344
307
  }) => {
345
- const { input, templateSchema, pageHeight, pdfDoc, page, inputImageCache } = arg;
308
+ const { input, templateSchema, pdfDoc, page, inputImageCache } = arg;
346
309
 
347
- const { width, height, rotate } = getSchemaSizeAndRotate(templateSchema);
310
+ const { width, height, rotate } = convertSchemaDimensionsToPt(templateSchema);
348
311
  const opt = {
349
312
  x: calcX(templateSchema.position.x, 'left', width, width),
350
- y: calcY(templateSchema.position.y, pageHeight, height),
313
+ y: calcY(templateSchema.position.y, page.getHeight(), height),
351
314
  rotate,
352
315
  width,
353
316
  height,
@@ -365,18 +328,17 @@ const drawInputByImageSchema = async (arg: {
365
328
  const drawInputByBarcodeSchema = async (arg: {
366
329
  input: string;
367
330
  templateSchema: BarcodeSchema;
368
- pageHeight: number;
369
331
  pdfDoc: PDFDocument;
370
332
  page: PDFPage;
371
333
  inputImageCache: InputImageCache;
372
334
  }) => {
373
- const { input, templateSchema, pageHeight, pdfDoc, page, inputImageCache } = arg;
335
+ const { input, templateSchema, pdfDoc, page, inputImageCache } = arg;
374
336
  if (!validateBarcodeInput(templateSchema.type as BarCodeType, input)) return;
375
337
 
376
- const { width, height, rotate } = getSchemaSizeAndRotate(templateSchema);
338
+ const { width, height, rotate } = convertSchemaDimensionsToPt(templateSchema);
377
339
  const opt = {
378
340
  x: calcX(templateSchema.position.x, 'left', width, width),
379
- y: calcY(templateSchema.position.y, pageHeight, height),
341
+ y: calcY(templateSchema.position.y, page.getHeight(), height),
380
342
  rotate,
381
343
  width,
382
344
  height,
@@ -398,7 +360,6 @@ export const drawInputByTemplateSchema = async (arg: {
398
360
  templateSchema: Schema;
399
361
  pdfDoc: PDFDocument;
400
362
  page: PDFPage;
401
- pageHeight: number;
402
363
  fontSetting: FontSetting;
403
364
  inputImageCache: InputImageCache;
404
365
  }) => {