pdfkit/lib/utils.js
Jake Holland 54e6600f1c
Fix precision rounding issues in LineWrapper (#1595)
* Fix further LineWrapper precision issues

* add test of bounded text precision issue

* add rowSpanning table example

* add failure threshold

* implement toContainText jest matcher

* create a unit test for bounded text precision

* remove round up rounding code path

---------

Co-authored-by: Luiz Américo Pereira Câmara <blikblum@users.noreply.github.com>
2025-05-02 23:05:11 -03:00

143 lines
3.6 KiB
JavaScript

const fArray = new Float32Array(1);
const uArray = new Uint32Array(fArray.buffer);
export function PDFNumber(n) {
// PDF numbers are strictly 32bit
// so convert this number to a 32bit number
// @see ISO 32000-1 Annex C.2 (real numbers)
const rounded = Math.fround(n);
if (rounded <= n) return rounded;
// Will have to perform 32bit float truncation
fArray[0] = n;
// Get the 32-bit representation as integer and shift bits
if (n <= 0) {
uArray[0] += 1;
} else {
uArray[0] -= 1;
}
// Return the float value
return fArray[0];
}
/**
* Measurement of size
*
* @typedef {number | `${number}` | `${number}${'em' | 'in' | 'px' | 'cm' | 'mm' | 'pc' | 'ex' | 'ch' | 'rem' | 'vw' | 'vmin' | 'vmax' | '%' | 'pt'}`} Size
*/
/**
* @typedef {Array<PDFTilingPattern | PDFColor> | string | Array<number>} PDFColor
*/
/** @typedef {string | Buffer | Uint8Array | ArrayBuffer} PDFFontSource */
/**
* Side definitions
* - To define all sides, use a single value
* - To define up-down left-right, use a `[Y, X]` array
* - To define each side, use `[top, right, bottom, left]` array
* - Or `{vertical: SideValue, horizontal: SideValue}`
* - Or `{top: SideValue, right: SideValue, bottom: SideValue, left: SideValue}`
*
* @template T
* @typedef {T | [T, T] | [T, T, T, T] | { vertical: T; horizontal: T } | ExpandedSideDefinition<T>} SideDefinition<T>
**/
/**
* @template T
* @typedef {{ top: T; right: T; bottom: T; left: T }} ExpandedSideDefinition<T>
*/
/**
* Convert any side definition into a static structure
*
* @template S
* @template D
* @template O
* @template {S | D} T
* @param {SideDefinition<S>} sides - The sides to convert
* @param {SideDefinition<D>} defaultDefinition - The value to use when no definition is provided
* @param {function(T): O} transformer - The transformation to apply to the sides once normalized
* @returns {ExpandedSideDefinition<O>}
*/
export function normalizeSides(
sides,
defaultDefinition = undefined,
transformer = (v) => v,
) {
if (
sides == null ||
(typeof sides === 'object' && Object.keys(sides).length === 0)
) {
sides = defaultDefinition;
}
if (sides == null || typeof sides !== 'object') {
sides = { top: sides, right: sides, bottom: sides, left: sides };
} else if (Array.isArray(sides)) {
if (sides.length === 2) {
sides = { vertical: sides[0], horizontal: sides[1] };
} else {
sides = {
top: sides[0],
right: sides[1],
bottom: sides[2],
left: sides[3],
};
}
}
if ('vertical' in sides || 'horizontal' in sides) {
sides = {
top: sides.vertical,
right: sides.horizontal,
bottom: sides.vertical,
left: sides.horizontal,
};
}
return {
top: transformer(sides.top),
right: transformer(sides.right),
bottom: transformer(sides.bottom),
left: transformer(sides.left),
};
}
export const MM_TO_CM = 1 / 10; // 1MM = 1CM
export const CM_TO_IN = 1 / 2.54; // 1CM = 1/2.54 IN
export const PX_TO_IN = 1 / 96; // 1 PX = 1/96 IN
export const IN_TO_PT = 72; // 1 IN = 72 PT
export const PC_TO_PT = 12; // 1 PC = 12 PT
/**
* Get cosine in degrees of a
*
* Rounding errors are handled
* @param a
* @returns {number}
*/
export function cosine(a) {
if (a === 0) return 1;
if (a === 90) return 0;
if (a === 180) return -1;
if (a === 270) return 0;
return Math.cos((a * Math.PI) / 180);
}
/**
* Get sine in degrees of a
*
* Rounding errors are handled
* @param a
* @returns {number}
*/
export function sine(a) {
if (a === 0) return 0;
if (a === 90) return 1;
if (a === 180) return 0;
if (a === 270) return -1;
return Math.sin((a * Math.PI) / 180);
}