Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions example/example-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,66 @@ const htmlString = `<!DOCTYPE html>
</li>
</ul>

<!-- Test cases for PR #15: Font size CSS keywords -->
<h3>Font Size CSS Keywords Test (PR #15)</h3>

<p>Testing absolute font-size keywords:</p>
<p style="font-size: xx-small;">This text is xx-small (9px equivalent)</p>
<p style="font-size: x-small;">This text is x-small (10px equivalent)</p>
<p style="font-size: small;">This text is small (13px equivalent)</p>
<p style="font-size: medium;">This text is medium (16px equivalent - default)</p>
<p style="font-size: large;">This text is large (18px equivalent)</p>
<p style="font-size: x-large;">This text is x-large (24px equivalent)</p>
<p style="font-size: xx-large;">This text is xx-large (32px equivalent)</p>
<p style="font-size: xxx-large;">This text is xxx-large (48px equivalent)</p>

<p>Testing relative font-size keywords:</p>
<div style="font-size: 16px;">
<p>Parent has 16px font size</p>
<p style="font-size: smaller;">This text is 'smaller' (5/6 of parent = ~13px)</p>
<p style="font-size: larger;">This text is 'larger' (1.2x of parent = ~19px)</p>
</div>

<p>Testing nested relative keywords:</p>
<div style="font-size: 20px;">
<span>Base: 20px</span>
<div style="font-size: larger;">
<span>First larger: ~24px</span>
<div style="font-size: smaller;">
<span>Then smaller: ~20px</span>
</div>
</div>
</div>

<p>Testing mixed units and keywords:</p>
<ul>
<li style="font-size: small;">Small text in list item</li>
<li style="font-size: 14pt;">14pt text in list item</li>
<li style="font-size: large;">Large text in list item</li>
<li style="font-size: 150%;">150% text in list item</li>
<li style="font-size: xx-large;">XX-Large text in list item</li>
</ul>

<p>Testing keywords in complex nested structures:</p>
<table border="1">
<tr>
<td style="font-size: x-small;">X-Small in table cell</td>
<td style="font-size: medium;">Medium in table cell</td>
<td style="font-size: x-large;">X-Large in table cell</td>
</tr>
<tr>
<td style="font-size: smaller;">Smaller in table cell</td>
<td>Default size in table cell</td>
<td style="font-size: larger;">Larger in table cell</td>
</tr>
</table>

<p>Testing edge cases:</p>
<p style="font-size: invalid-keyword;">Invalid keyword should fallback to default</p>
<p style="font-size: ;">Empty font-size should use default</p>
<p style="font-size: 0;">Zero font-size edge case</p>
<span style="font-size: smaller;">Relative size without explicit parent</span>

<p>Different borders</p>
<table
style="border-collapse: collapse; border-left:1px solid black; border-right: 2px solid brown; border-top: 2px solid yellow; border-bottom: 4px solid orange">
Expand Down
27 changes: 27 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ const colorlessColors = ['transparent', 'auto'];
const verticalAlignValues = ['top', 'middle', 'bottom'];
const defaultPercentageMarginValue = 0;

/**
* CSS absolute font-size keyword mappings to pixel values
* Based on CSS specification: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size
*/
const absoluteFontSizes = {
'xx-small': '9px',
'x-small': '10px',
'small': '13px',
'medium': '16px',
'large': '18px',
'x-large': '24px',
'xx-large': '32px',
'xxx-large': '48px',
};

/**
* Scaling factors for relative font-size keywords
* - smaller: 5/6 of parent font size
* - larger: 1.2x of parent font size
*/
const relativeFontSizeFactors = {
smaller: 5 / 6,
larger: 1.2,
};

export {
defaultDocumentOptions,
defaultTableBorderOptions,
Expand Down Expand Up @@ -155,4 +180,6 @@ export {
defaultDirection,
defaultPercentageMarginValue,
defaultTableBorderAttributeOptions,
absoluteFontSizes,
relativeFontSizeFactors,
};
63 changes: 40 additions & 23 deletions src/helpers/xml-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,17 @@ import {
import { buildImage, buildList } from './render-document-file';
import {
defaultFont,
defaultFontSize,
hyperlinkType,
paragraphBordersObject,
colorlessColors,
verticalAlignValues,
imageType,
internalRelationship,
defaultTableBorderOptions,
defaultTableBorderAttributeOptions
defaultTableBorderAttributeOptions,
absoluteFontSizes,
relativeFontSizeFactors
} from '../constants';
import { vNodeHasChildren } from '../utils/vnode';
import { isValidUrl } from '../utils/url';
Expand Down Expand Up @@ -365,6 +368,18 @@ const fixupLineHeight = (lineHeight, fontSize) => {

// eslint-disable-next-line consistent-return
const fixupFontSize = (fontSizeString, docxDocumentInstance) => {

if (["smaller", "larger"].includes(fontSizeString)) {
// since we dont have access to immediate parent size
// we will use the default font size given to us
return Math.floor(relativeFontSizeFactors[fontSizeString] * docxDocumentInstance.fontSize);
}

if (absoluteFontSizes[fontSizeString]) {
fontSizeString = absoluteFontSizes[fontSizeString];
}

// Handle unit-based font sizes
if (pointRegex.test(fontSizeString)) {
const matchedParts = fontSizeString.match(pointRegex);
// convert point to half point
Expand All @@ -380,10 +395,12 @@ const fixupFontSize = (fontSizeString, docxDocumentInstance) => {
const matchedParts = fontSizeString.match(inchRegex);
return inchToHIP(matchedParts[1]);
} else if (percentageRegex.test(fontSizeString)) {
// need fontsize here default
const matchedParts = fontSizeString.match(percentageRegex);
return (matchedParts[1] * docxDocumentInstance.fontSize) / 100;
}

// Fallback to default font size if no valid format is found
return defaultFontSize;
};

// eslint-disable-next-line consistent-return
Expand Down Expand Up @@ -1066,26 +1083,26 @@ const buildRunOrHyperLink = async (vNode, attributes, docxDocumentInstance) => {

if (vNode.children) {
for (let idx = 0; idx < vNode.children.length; idx++) {
const childVNode = vNode.children[idx];
const modifiedAttributes =
isVNode(childVNode) && childVNode.tagName === 'img'
? { ...attributes, type: 'picture', description: childVNode.properties.alt } : { ...attributes };
modifiedAttributes.hyperlink = true;

const runFragments = await buildRunOrRuns(
childVNode,
modifiedAttributes,
docxDocumentInstance
);
if (Array.isArray(runFragments)) {
for (let index = 0; index < runFragments.length; index++) {
const runFragment = runFragments[index];
const childVNode = vNode.children[idx];
const modifiedAttributes =
isVNode(childVNode) && childVNode.tagName === 'img'
? { ...attributes, type: 'picture', description: childVNode.properties.alt } : { ...attributes };
modifiedAttributes.hyperlink = true;

const runFragments = await buildRunOrRuns(
childVNode,
modifiedAttributes,
docxDocumentInstance
);
if (Array.isArray(runFragments)) {
for (let index = 0; index < runFragments.length; index++) {
const runFragment = runFragments[index];

hyperlinkFragment.import(runFragment);
hyperlinkFragment.import(runFragment);
}
} else {
hyperlinkFragment.import(runFragments);
}
} else {
hyperlinkFragment.import(runFragments);
}
}
}

Expand Down Expand Up @@ -1312,7 +1329,7 @@ const computeImageDimensions = (vNode, attributes) => {
const maximumWidthInEMU = TWIPToEMU(maximumWidth);
let originalWidthInEMU = pixelToEMU(originalWidth);
let originalHeightInEMU = pixelToEMU(originalHeight);

if (originalWidthInEMU > maximumWidthInEMU) {
originalWidthInEMU = maximumWidthInEMU;
originalHeightInEMU = Math.round(originalWidthInEMU / aspectRatio);
Expand Down Expand Up @@ -1382,7 +1399,7 @@ const computeImageDimensions = (vNode, attributes) => {
} else if (modifiedHeight && !modifiedWidth) {
modifiedWidth = Math.round(modifiedHeight * aspectRatio);
}

// Fallback for images with non-dimensional CSS styles
// HTML like <img style="font-family: Poppins;" src="..."> creates a style object
// but contains no width/height properties. The function enters this if block but never
Expand All @@ -1404,7 +1421,7 @@ const computeImageDimensions = (vNode, attributes) => {
modifiedWidth = originalWidthInEMU;
modifiedHeight = originalHeightInEMU;
}

// eslint-disable-next-line no-param-reassign
attributes.width = modifiedWidth;
// eslint-disable-next-line no-param-reassign
Expand Down