UNPKG

@pdfme/schemas

Version:

TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!

156 lines 7.33 kB
import { getDefaultFont, getFallbackFontName, mm2pt, } from '@pdfme/common'; import { VERTICAL_ALIGN_TOP, VERTICAL_ALIGN_MIDDLE, VERTICAL_ALIGN_BOTTOM, DEFAULT_FONT_SIZE, DEFAULT_ALIGNMENT, DEFAULT_VERTICAL_ALIGNMENT, DEFAULT_LINE_HEIGHT, DEFAULT_CHARACTER_SPACING, DEFAULT_FONT_COLOR, } from './constants.js'; import { calculateDynamicFontSize, heightOfFontAtSize, getFontDescentInPt, getFontKitFont, widthOfTextAtSize, splitTextToSize, } from './helper.js'; import { convertForPdfLayoutProps, rotatePoint, hex2PrintingColor } from '../utils.js'; const embedAndGetFontObj = async (arg) => { const { pdfDoc, font, _cache } = arg; if (_cache.has(pdfDoc)) { return _cache.get(pdfDoc); } const fontValues = await Promise.all(Object.values(font).map(async (v) => { let fontData = v.data; if (typeof fontData === 'string' && fontData.startsWith('http')) { fontData = await fetch(fontData).then((res) => res.arrayBuffer()); } return pdfDoc.embedFont(fontData, { subset: typeof v.subset === 'undefined' ? true : v.subset, }); })); const fontObj = Object.keys(font).reduce((acc, cur, i) => Object.assign(acc, { [cur]: fontValues[i] }), {}); _cache.set(pdfDoc, fontObj); return fontObj; }; const getFontProp = ({ value, fontKitFont, schema, colorType, }) => { const fontSize = schema.dynamicFontSize ? calculateDynamicFontSize({ textSchema: schema, fontKitFont, value }) : (schema.fontSize ?? DEFAULT_FONT_SIZE); const color = hex2PrintingColor(schema.fontColor || DEFAULT_FONT_COLOR, colorType); return { alignment: schema.alignment ?? DEFAULT_ALIGNMENT, verticalAlignment: schema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT, lineHeight: schema.lineHeight ?? DEFAULT_LINE_HEIGHT, characterSpacing: schema.characterSpacing ?? DEFAULT_CHARACTER_SPACING, fontSize, color, }; }; export const pdfRender = async (arg) => { const { value, pdfDoc, pdfLib, page, options, schema, _cache } = arg; if (!value) return; const { font = getDefaultFont(), colorType } = options; const [pdfFontObj, fontKitFont] = await Promise.all([ embedAndGetFontObj({ pdfDoc, font, _cache: _cache, }), getFontKitFont(schema.fontName, font, _cache), ]); const fontProp = getFontProp({ value, fontKitFont, schema, colorType }); const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } = fontProp; const fontName = (schema.fontName ? schema.fontName : getFallbackFontName(font)); const pdfFontValue = pdfFontObj && pdfFontObj[fontName]; const pageHeight = page.getHeight(); const { width, height, rotate, position: { x, y }, opacity, } = convertForPdfLayoutProps({ schema, pageHeight, applyRotateTranslate: false }); if (schema.backgroundColor) { const color = hex2PrintingColor(schema.backgroundColor, colorType); page.drawRectangle({ x, y, width, height, rotate, color }); } const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize); const descent = getFontDescentInPt(fontKitFont, fontSize); const halfLineHeightAdjustment = lineHeight === 0 ? 0 : ((lineHeight - 1) * fontSize) / 2; const lines = splitTextToSize({ value, characterSpacing, fontSize, fontKitFont, boxWidthInPt: width, }); // Text lines are rendered from the bottom upwards, we need to adjust the position down let yOffset = 0; if (verticalAlignment === VERTICAL_ALIGN_TOP) { yOffset = firstLineTextHeight + halfLineHeightAdjustment; } else { const otherLinesHeight = lineHeight * fontSize * (lines.length - 1); if (verticalAlignment === VERTICAL_ALIGN_BOTTOM) { yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment; } else if (verticalAlignment === VERTICAL_ALIGN_MIDDLE) { yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight; } } const pivotPoint = { x: x + width / 2, y: pageHeight - mm2pt(schema.position.y) - height / 2 }; const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' }); lines.forEach((line, rowIndex) => { const trimmed = line.replace('\n', ''); const textWidth = widthOfTextAtSize(trimmed, fontKitFont, fontSize, characterSpacing); const textHeight = heightOfFontAtSize(fontKitFont, fontSize); const rowYOffset = lineHeight * fontSize * rowIndex; // Adobe Acrobat Reader shows an error if `drawText` is called with an empty text if (line === '') { // return; // this also works line = '\r\n'; } let xLine = x; if (alignment === 'center') { xLine += (width - textWidth) / 2; } else if (alignment === 'right') { xLine += width - textWidth; } let yLine = pageHeight - mm2pt(schema.position.y) - yOffset - rowYOffset; // draw strikethrough if (schema.strikethrough && textWidth > 0) { const _x = xLine + textWidth + 1; const _y = yLine + textHeight / 3; page.drawLine({ start: rotatePoint({ x: xLine, y: _y }, pivotPoint, rotate.angle), end: rotatePoint({ x: _x, y: _y }, pivotPoint, rotate.angle), thickness: (1 / 12) * fontSize, color: color, opacity, }); } // draw underline if (schema.underline && textWidth > 0) { const _x = xLine + textWidth + 1; const _y = yLine - textHeight / 12; page.drawLine({ start: rotatePoint({ x: xLine, y: _y }, pivotPoint, rotate.angle), end: rotatePoint({ x: _x, y: _y }, pivotPoint, rotate.angle), thickness: (1 / 12) * fontSize, color: color, opacity, }); } if (rotate.angle !== 0) { // As we draw each line individually from different points, we must translate each lines position // relative to the UI rotation pivot point. see comments in convertForPdfLayoutProps() for more info. const rotatedPoint = rotatePoint({ x: xLine, y: yLine }, pivotPoint, rotate.angle); xLine = rotatedPoint.x; yLine = rotatedPoint.y; } let spacing = characterSpacing; if (alignment === 'justify' && line.slice(-1) !== '\n') { // if alignment is `justify` but the end of line is not newline, then adjust the spacing const iterator = segmenter.segment(trimmed)[Symbol.iterator](); const len = Array.from(iterator).length; spacing += (width - textWidth) / len; } page.pushOperators(pdfLib.setCharacterSpacing(spacing)); page.drawText(trimmed, { x: xLine, y: yLine, rotate, size: fontSize, color, lineHeight: lineHeight * fontSize, font: pdfFontValue, opacity, }); }); }; //# sourceMappingURL=pdfRender.js.map