pptx-automizer
Version:
A template based pptx generator
288 lines • 11.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const general_helper_1 = require("./general-helper");
const text_replace_helper_1 = __importDefault(require("./text-replace-helper"));
const modify_text_helper_1 = __importDefault(require("./modify-text-helper"));
const xml_helper_1 = require("./xml-helper");
const xml_slide_helper_1 = require("./xml-slide-helper");
const xml_placeholder_helper_1 = __importDefault(require("./xml-placeholder-helper"));
const index_1 = require("../index");
/**
* Mapping between user-friendly property names and their corresponding XML structure
* This allows using multiple alias names for the same shape properties
*/
const map = {
// X position mappings (left)
x: { tag: 'a:off', attribute: 'x' },
l: { tag: 'a:off', attribute: 'x' },
left: { tag: 'a:off', attribute: 'x' },
// Y position mappings (top)
y: { tag: 'a:off', attribute: 'y' },
t: { tag: 'a:off', attribute: 'y' },
top: { tag: 'a:off', attribute: 'y' },
// Width mappings
cx: { tag: 'a:ext', attribute: 'cx' },
w: { tag: 'a:ext', attribute: 'cx' },
width: { tag: 'a:ext', attribute: 'cx' },
// Height mappings
cy: { tag: 'a:ext', attribute: 'cy' },
h: { tag: 'a:ext', attribute: 'cy' },
height: { tag: 'a:ext', attribute: 'cy' },
};
/**
* Helper class for modifying PowerPoint shapes in XML structure
* Provides various methods for manipulating shape appearance, position, and content
*/
class ModifyShapeHelper {
/**
* Determines the type of visual element in PowerPoint
* @param element The XML element to check
* @returns A string identifying the element type
*/
static getElementVisualType(element) {
return xml_slide_helper_1.XmlSlideHelper.getElementVisualType(element);
}
/**
* Forward placeholder info function
* @param element The XML element to check
* @returns The PlaceholderInfo object
*/
static getElementPlaceholderInfo(element) {
return xml_placeholder_helper_1.default.getPlaceholderInfo(element);
}
/**
* Check if the given element has a background which is non-transparent
* @param element The XML element to check
* @returns ShapeBackgroundInfo
*/
static elementHasBackground(element) {
return index_1.ModifyColorHelper.elementHasBackground(element);
}
}
/**
* Set solid fill of modified shape to accent6 color
*
* @param element - XML element representing the shape
*/
ModifyShapeHelper.setSolidFill = (element) => {
element
.getElementsByTagName('a:solidFill')[0]
.getElementsByTagName('a:schemeClr')[0]
.setAttribute('val', 'accent6');
};
/**
* Set text content of a shape
*
* @param text - Text string to set as shape content
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.setText = (text) => (element) => {
modify_text_helper_1.default.setText(text)(element);
};
/**
* Set content to bulleted list within a shape
*
* @param list - Array or string content for the bullet list
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.setBulletList = (list) => (element) => {
modify_text_helper_1.default.setBulletList(list)(element);
};
/**
* Replace tagged text content within modified shape
*
* @param replaceText - Single replacement or array of replacements
* @param options - Optional configuration for text replacement
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.replaceText = (replaceText, options) => (element) => {
const replaceTexts = general_helper_1.GeneralHelper.arrayify(replaceText);
new text_replace_helper_1.default(options, element)
.isolateTaggedNodes()
.applyReplacements(replaceTexts);
};
/**
* Creates missing transformation elements (a:off and a:ext) with default values
* Ensures that a shape has the required XML structure for positioning and sizing
*
* @param element - The XML element to check and modify
* @returns The xfrm element that contains or will contain the transformation data
*/
ModifyShapeHelper.ensureTransformElements = (element) => {
// First find the xfrm element (could be a:xfrm or p:xfrm)
let xfrm = element.getElementsByTagName('a:xfrm')[0];
if (!xfrm) {
xfrm = element.getElementsByTagName('p:xfrm')[0];
}
// If no xfrm element exists, try to find the appropriate parent to create it
if (!xfrm) {
const spPr = element.getElementsByTagName('p:spPr')[0] ||
element.getElementsByTagName('a:spPr')[0];
if (!spPr) {
return null; // Cannot create xfrm without a proper parent
}
// Create the xfrm element
xfrm = element.ownerDocument.createElement('a:xfrm');
spPr.appendChild(xfrm);
}
// Create a:off element if it doesn't exist
if (!xfrm.getElementsByTagName('a:off')[0]) {
const newAOff = element.ownerDocument.createElement('a:off');
// Set default coordinates (0,0)
newAOff.setAttribute('x', '0');
newAOff.setAttribute('y', '0');
// Insert a:off as the first child of xfrm
if (xfrm.hasChildNodes()) {
xfrm.insertBefore(newAOff, xfrm.firstChild);
}
else {
xfrm.appendChild(newAOff);
}
}
// Create a:ext element if it doesn't exist
if (!xfrm.getElementsByTagName('a:ext')[0]) {
const newAExt = element.ownerDocument.createElement('a:ext');
// Set default dimensions - using 1000000 which is about 2.78cm
newAExt.setAttribute('cx', '1000000');
newAExt.setAttribute('cy', '1000000');
// Add the a:ext element after a:off
xfrm.appendChild(newAExt);
}
return xfrm;
};
/**
* Set absolute position and size of a shape
*
* @param pos - Object containing position and size coordinates
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.setPosition = (pos) => (element) => {
// Ensure the transform elements exist
const xfrm = ModifyShapeHelper.ensureTransformElements(element);
if (!xfrm) {
return; // Cannot set position without proper structure
}
// Apply each provided coordinate
Object.keys(pos).forEach((key) => {
let value = Math.round(pos[key]);
// Skip invalid values or unsupported properties
if (typeof value !== 'number' || !map[key])
return;
// Ensure value is not negative
value = value < 0 ? 0 : value;
// Set the value in the appropriate XML tag and attribute
xfrm
.getElementsByTagName(map[key].tag)[0]
.setAttribute(map[key].attribute, value);
});
};
/**
* Incrementally update position and size of a shape by adding delta values
*
* @param pos - Object containing delta values for position and size
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.updatePosition = (pos) => (element) => {
// Ensure the transform elements exist
const xfrm = ModifyShapeHelper.ensureTransformElements(element);
if (!xfrm) {
return; // Cannot update position without proper structure
}
// Apply each provided delta coordinate
Object.keys(pos).forEach((key) => {
let value = Math.round(pos[key]);
// Skip invalid values or unsupported properties
if (typeof value !== 'number' || !map[key])
return;
// Get current value and add the delta
const currentValue = xfrm
.getElementsByTagName(map[key].tag)[0]
.getAttribute(map[key].attribute);
value += Number(currentValue);
// Update the value in the appropriate XML tag and attribute
xfrm
.getElementsByTagName(map[key].tag)[0]
.setAttribute(map[key].attribute, value);
});
};
/**
* Rotate a shape by a given angle in degrees
*
* @param degrees - Rotation angle in degrees (positive = clockwise, negative = counterclockwise)
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.rotate = (degrees) => (element) => {
const spPr = element.getElementsByTagName('p:spPr');
if (spPr) {
const xfrm = spPr.item(0).getElementsByTagName('a:xfrm').item(0);
// Convert negative degrees to equivalent positive value (0-359)
degrees = degrees < 0 ? 360 + degrees : degrees;
// PowerPoint uses 60000 units per degree for rotation
xfrm.setAttribute('rot', String(Math.round(degrees * 60000)));
}
};
/**
* Apply rounded corners to a shape with a specified corner radius
*
* @param degree - Corner radius in EMU units (1 cm = 360000 EMU)
* @returns Function that accepts an XML element to modify
*/
ModifyShapeHelper.roundedCorners = (degree) => (element) => {
// Find the spPr element where we need to add or modify the a:prstGeom element
const spPr = element.getElementsByTagName('p:spPr')[0] ||
element.getElementsByTagName('a:spPr')[0];
if (!spPr) {
return; // Cannot find spPr element
}
// Get the shape dimensions to calculate the appropriate adjustment value
const xfrm = ModifyShapeHelper.ensureTransformElements(element);
if (!xfrm) {
return; // Cannot proceed without proper transformation data
}
// Get current width and height
const width = Number(xfrm.getElementsByTagName('a:ext')[0].getAttribute('cx'));
const height = Number(xfrm.getElementsByTagName('a:ext')[0].getAttribute('cy'));
// Calculate the adjustment value based on the smaller dimension
const minDimension = Math.min(width, height);
// Ensure degree is within reasonable bounds (PowerPoint uses 0-50% for rounded rect)
const clampedDegree = Math.max(0, Math.min(degree, minDimension / 2));
// Calculate the adjustment value (0-100000 where 100000 is 100%)
// PowerPoint uses the percentage of the shorter dimension for corners
const adjValue = Math.round((clampedDegree / minDimension) * 100000);
// Remove any existing prstGeom element
const existingPrstGeom = spPr.getElementsByTagName('a:prstGeom')[0];
if (existingPrstGeom) {
xml_helper_1.XmlHelper.remove(existingPrstGeom);
}
// Create the new prstGeom element with the roundRect preset
const prstGeom = element.ownerDocument.createElement('a:prstGeom');
prstGeom.setAttribute('prst', 'roundRect');
// Create the avLst element and the adjustment value
const avLst = element.ownerDocument.createElement('a:avLst');
const gd = element.ownerDocument.createElement('a:gd');
gd.setAttribute('name', 'adj');
gd.setAttribute('fmla', `val ${adjValue}`);
// Build the element hierarchy
avLst.appendChild(gd);
prstGeom.appendChild(avLst);
// Add the new prstGeom element to spPr at the appropriate position
// It should be added after xfrm but before other elements
const xfrmElement = spPr.getElementsByTagName('a:xfrm')[0];
if (xfrmElement && xfrmElement.nextSibling) {
spPr.insertBefore(prstGeom, xfrmElement.nextSibling);
}
else {
spPr.appendChild(prstGeom);
}
// Check for noFill element - for picture elements, we need to remove noFill
// otherwise the picture will be invisible when we apply rounded corners
const noFillElement = spPr.getElementsByTagName('a:noFill')[0];
if (noFillElement) {
xml_helper_1.XmlHelper.remove(noFillElement);
}
};
exports.default = ModifyShapeHelper;
//# sourceMappingURL=modify-shape-helper.js.map