@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
JavaScript
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