Skip to content

Commit

Permalink
feat: allow units for page size (#2773)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikischin authored Dec 3, 2024
1 parent 8c91bf5 commit 18834ef
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 29 deletions.
6 changes: 6 additions & 0 deletions .changeset/old-otters-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@react-pdf/stylesheet": minor
"@react-pdf/layout": minor
---

Changed unit behavior according to PDF spec. Please note that all unitless values are considered as user unit which is a 72dpi equality of the value. This is according to PDF spec and ensures a consistent layout independent of the dpi setting.
80 changes: 56 additions & 24 deletions packages/layout/src/page/getSize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import isLandscape from './isLandscape';

// Page sizes for 72dpi. 72dpi is used internally by pdfkit.
const PAGE_SIZES = {
'4A0': [4767.87, 6740.79],
'2A0': [3370.39, 4767.87],
Expand Down Expand Up @@ -54,11 +55,58 @@ const PAGE_SIZES = {
ID1: [153, 243],
};

/**
* Parses scalar value in value and unit pairs
*
* @param {string} value scalar value
* @returns {Object} parsed value
*/
const parseValue = (value) => {
const match = /^(-?\d*\.?\d+)(in|mm|cm|pt|px)?$/g.exec(value);

return match
? { value: parseFloat(match[1]), unit: match[2] || 'pt' }
: { value, unit: undefined };
};

/**
* Transform given scalar value to 72dpi equivalent of size
*
* @param {string} value styles value
* @param {number} inputDpi user defined dpi
* @returns {Object} transformed value
*/
const transformUnit = (value, inputDpi) => {
const scalar = parseValue(value);

const outputDpi = 72;
const mmFactor = (1 / 25.4) * outputDpi;
const cmFactor = (1 / 2.54) * outputDpi;

switch (scalar.unit) {
case 'in':
return scalar.value * outputDpi;
case 'mm':
return scalar.value * mmFactor;
case 'cm':
return scalar.value * cmFactor;
case 'px':
return Math.round(scalar.value * (outputDpi / inputDpi));
default:
return scalar.value;
}
};

const transformUnits = ({ width, height }, dpi) => ({
width: transformUnit(width, dpi),
height: transformUnit(height, dpi),
});

/**
* Transforms array into size object
*
* @param {number[]} v array
* @returns {{ width: number, height: number }} size object with width and height
* @param {number[] | string[]} v array
* @returns {{ width: number | string, height: number | string }} size object with width and height
*/
const toSizeObject = (v) => ({ width: v[0], height: v[1] });

Expand All @@ -70,18 +118,6 @@ const toSizeObject = (v) => ({ width: v[0], height: v[1] });
*/
const flipSizeObject = (v) => ({ width: v.height, height: v.width });

/**
* Adjust page size to passed DPI
*
* @param {{ width: number, height: number }} v size object
* @param {number} dpi DPI
* @returns {{ width: number, height: number }} adjusted size object
*/
const adjustDpi = (v, dpi) => ({
width: v.width ? v.width * (72 / dpi) : v.width,
height: v.height ? v.height * (72 / dpi) : v.height,
});

/**
* Returns size object from a given string
*
Expand All @@ -95,10 +131,10 @@ const getStringSize = (v) => {
/**
* Returns size object from a single number
*
* @param {number} n page size number
* @returns {{ width: number, height: number }} size object with width and height
* @param {number|string} n page size number
* @returns {{ width: number|string, height: number|string }} size object with width and height
*/
const getNumberSize = (n) => toSizeObject([n]);
const getNumberSize = (n) => toSizeObject([n, n]);

/**
* Return page size in an object { width, height }
Expand All @@ -116,18 +152,14 @@ const getSize = (page) => {
* @type {{ width: number, height: number }}
*/
let size;

if (type === 'string') {
size = getStringSize(value);
} else if (Array.isArray(value)) {
size = toSizeObject(value);
size = adjustDpi(size, dpi);
size = transformUnits(toSizeObject(value), dpi);
} else if (type === 'number') {
size = getNumberSize(value);
size = adjustDpi(size, dpi);
size = transformUnits(getNumberSize(value), dpi);
} else {
size = value;
size = adjustDpi(size, dpi);
size = transformUnits(value, dpi);
}

return isLandscape(page) ? flipSizeObject(size) : size;
Expand Down
2 changes: 1 addition & 1 deletion packages/layout/src/steps/resolveStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const resolveNodeStyles = (container) => (node) => {
* @returns {Object} document page with resolved styles
*/
export const resolvePageStyles = (page) => {
const dpi = 72; // Removed: page.props?.dpi || 72;
const dpi = page.props?.dpi || 72;
const width = page.box?.width || page.style.width;
const height = page.box?.height || page.style.height;
const orientation = page.props?.orientation || 'portrait';
Expand Down
11 changes: 7 additions & 4 deletions packages/stylesheet/src/transform/units.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ const parseValue = (value) => {
const transformUnit = (container, value) => {
const scalar = parseValue(value);

const dpi = 72; // Removed: container.dpi || 72
const mmFactor = (1 / 25.4) * dpi;
const cmFactor = (1 / 2.54) * dpi;
const outputDpi = 72;
const inputDpi = container.dpi || 72;
const mmFactor = (1 / 25.4) * outputDpi;
const cmFactor = (1 / 2.54) * outputDpi;

switch (scalar.unit) {
case 'rem':
return scalar.value * (container.remBase || 18);
case 'in':
return scalar.value * dpi;
return scalar.value * outputDpi;
case 'mm':
return scalar.value * mmFactor;
case 'cm':
Expand All @@ -39,6 +40,8 @@ const transformUnit = (container, value) => {
return scalar.value * (container.height / 100);
case 'vw':
return scalar.value * (container.width / 100);
case 'px':
return Math.round(scalar.value * (outputDpi / inputDpi));
default:
return scalar.value;
}
Expand Down

0 comments on commit 18834ef

Please sign in to comment.