Skip to content

Commit

Permalink
support gradients for slide background
Browse files Browse the repository at this point in the history
  • Loading branch information
tjinauyeung committed Jul 27, 2022
1 parent d5ca25a commit 66f18bf
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 16 deletions.
4 changes: 2 additions & 2 deletions demos/browser/js/pptxgen.bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demos/browser/js/pptxgen.bundle.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/pptxgen.bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pptxgen.bundle.js.map

Large diffs are not rendered by default.

60 changes: 58 additions & 2 deletions dist/pptxgen.cjs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* PptxGenJS 3.11.0-beta @ 2022-07-25T16:08:18.214Z */
/* PptxGenJS 3.11.0-beta @ 2022-07-27T06:59:00.146Z */
'use strict';

var JSZip = require('jszip');
Expand Down Expand Up @@ -803,6 +803,57 @@ function createGlowElement(options, defaults) {
strXml += "</a:glow>";
return strXml;
}
/**
* Create gradient elements i.e. `a:gsLst` and `a:lin`
* @param {Gradient} gradient
* @param {string} internalElements
* @see http://officeopenxml.com/drwSp-GradFill.php
* @returns XML string
*/
function createGradientElements(gradient, internalElements) {
var strXml = '', stops = gradient.stops;
if (!isGradient(gradient)) {
console.warn("This is not a valid gradient:", gradient);
console.warn("Fallback to default gradient with \"".concat(SchemeColor.background1, "\" and \"").concat(SchemeColor.background2, "\"."));
console.warn("Please provide a valid gradient e.g. { angle:0, stops: { 0:\"#FFFFFF\", 100:\"#000000\" } }.");
stops = { 0: SchemeColor.background1, 100: SchemeColor.background2 };
}
strXml += createGradientList(stops, internalElements);
strXml += createLinearGradient(gradient.angle);
return strXml;
}
/**
* Create gradient list element i.e. `a:gsLst`
* @param {Gradient} gradient
* @param {string} internalElements
* @returns XML string
*/
function createGradientList(stops, internalElements) {
var multiplier = 1000;
var res = Object.keys(stops).map(function (pos) { return "<a:gs pos=\"".concat(Number(pos) * multiplier, "\">").concat(createColorElement(stops[pos], internalElements), "</a:gs>"); });
return "<a:gsLst>".concat(res.join(''), "</a:gsLst>");
}
/**
* Create linear gradient element i.e. `a:lin`
* @param {number} angle
* @returns XML string
*/
function createLinearGradient(angle) {
if (angle === void 0) { angle = 0; }
var multiplier = 60000;
return "<a:lin ang=\"".concat(angle * multiplier, "\"/>");
}
/**
* Validate gradient
* @param {Gradient} gradient
* @returns boolean
*/
function isGradient(gradient) {
return Boolean((gradient === null || gradient === void 0 ? void 0 : gradient.stops) &&
typeof gradient.stops === 'object' &&
Object.keys(gradient.stops).length > 0 &&
Object.keys(gradient.stops).every(function (stop) { return Number.isFinite(Number(stop)); }));
}
/**
* Create color selection
* @param {Color | ShapeFillProps | ShapeLineProps} props fill props
Expand All @@ -819,6 +870,8 @@ function genXmlColorSelection(props) {
else {
if (props.type)
fillType = props.type;
if (props.gradient)
fillType = 'gradient';
if (props.color)
colorVal = props.color;
if (props.alpha)
Expand All @@ -830,6 +883,9 @@ function genXmlColorSelection(props) {
case 'solid':
outText += "<a:solidFill>".concat(createColorElement(colorVal, internalElements), "</a:solidFill>");
break;
case 'gradient':
outText += "<a:gradFill>".concat(createGradientElements(props.gradient, internalElements), "</a:gradFill>");
break;
default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter
outText += '';
break;
Expand Down Expand Up @@ -1606,7 +1662,7 @@ function slideObjectToXml(slide) {
if (slide._bkgdImgRid) {
strSlideXml += "<p:bg><p:bgPr><a:blipFill dpi=\"0\" rotWithShape=\"1\"><a:blip r:embed=\"rId".concat(slide._bkgdImgRid, "\"><a:lum/></a:blip><a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill><a:effectLst/></p:bgPr></p:bg>");
}
else if (slide.background && slide.background.color) {
else if (slide.background && (slide.background.color || slide.background.gradient)) {
strSlideXml += "<p:bg><p:bgPr>".concat(genXmlColorSelection(slide.background), "</p:bgPr></p:bg>");
}
else if (!slide.bkgd && slide._name && slide._name === DEF_PRES_LAYOUT_NAME) {
Expand Down
60 changes: 58 additions & 2 deletions dist/pptxgen.es.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* PptxGenJS 3.11.0-beta @ 2022-07-25T16:08:18.219Z */
/* PptxGenJS 3.11.0-beta @ 2022-07-27T06:59:00.149Z */
import JSZip from 'jszip';

/******************************************************************************
Expand Down Expand Up @@ -797,6 +797,57 @@ function createGlowElement(options, defaults) {
strXml += "</a:glow>";
return strXml;
}
/**
* Create gradient elements i.e. `a:gsLst` and `a:lin`
* @param {Gradient} gradient
* @param {string} internalElements
* @see http://officeopenxml.com/drwSp-GradFill.php
* @returns XML string
*/
function createGradientElements(gradient, internalElements) {
var strXml = '', stops = gradient.stops;
if (!isGradient(gradient)) {
console.warn("This is not a valid gradient:", gradient);
console.warn("Fallback to default gradient with \"".concat(SchemeColor.background1, "\" and \"").concat(SchemeColor.background2, "\"."));
console.warn("Please provide a valid gradient e.g. { angle:0, stops: { 0:\"#FFFFFF\", 100:\"#000000\" } }.");
stops = { 0: SchemeColor.background1, 100: SchemeColor.background2 };
}
strXml += createGradientList(stops, internalElements);
strXml += createLinearGradient(gradient.angle);
return strXml;
}
/**
* Create gradient list element i.e. `a:gsLst`
* @param {Gradient} gradient
* @param {string} internalElements
* @returns XML string
*/
function createGradientList(stops, internalElements) {
var multiplier = 1000;
var res = Object.keys(stops).map(function (pos) { return "<a:gs pos=\"".concat(Number(pos) * multiplier, "\">").concat(createColorElement(stops[pos], internalElements), "</a:gs>"); });
return "<a:gsLst>".concat(res.join(''), "</a:gsLst>");
}
/**
* Create linear gradient element i.e. `a:lin`
* @param {number} angle
* @returns XML string
*/
function createLinearGradient(angle) {
if (angle === void 0) { angle = 0; }
var multiplier = 60000;
return "<a:lin ang=\"".concat(angle * multiplier, "\"/>");
}
/**
* Validate gradient
* @param {Gradient} gradient
* @returns boolean
*/
function isGradient(gradient) {
return Boolean((gradient === null || gradient === void 0 ? void 0 : gradient.stops) &&
typeof gradient.stops === 'object' &&
Object.keys(gradient.stops).length > 0 &&
Object.keys(gradient.stops).every(function (stop) { return Number.isFinite(Number(stop)); }));
}
/**
* Create color selection
* @param {Color | ShapeFillProps | ShapeLineProps} props fill props
Expand All @@ -813,6 +864,8 @@ function genXmlColorSelection(props) {
else {
if (props.type)
fillType = props.type;
if (props.gradient)
fillType = 'gradient';
if (props.color)
colorVal = props.color;
if (props.alpha)
Expand All @@ -824,6 +877,9 @@ function genXmlColorSelection(props) {
case 'solid':
outText += "<a:solidFill>".concat(createColorElement(colorVal, internalElements), "</a:solidFill>");
break;
case 'gradient':
outText += "<a:gradFill>".concat(createGradientElements(props.gradient, internalElements), "</a:gradFill>");
break;
default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter
outText += '';
break;
Expand Down Expand Up @@ -1600,7 +1656,7 @@ function slideObjectToXml(slide) {
if (slide._bkgdImgRid) {
strSlideXml += "<p:bg><p:bgPr><a:blipFill dpi=\"0\" rotWithShape=\"1\"><a:blip r:embed=\"rId".concat(slide._bkgdImgRid, "\"><a:lum/></a:blip><a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill><a:effectLst/></p:bgPr></p:bg>");
}
else if (slide.background && slide.background.color) {
else if (slide.background && (slide.background.color || slide.background.gradient)) {
strSlideXml += "<p:bg><p:bgPr>".concat(genXmlColorSelection(slide.background), "</p:bgPr></p:bg>");
}
else if (!slide.bkgd && slide._name && slide._name === DEF_PRES_LAYOUT_NAME) {
Expand Down
4 changes: 2 additions & 2 deletions dist/pptxgen.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/pptxgen.min.js.map

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion src/core-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export interface BackgroundProps extends DataOrPathProps, ShapeFillProps {
export type HexColor = string
export type ThemeColor = 'tx1' | 'tx2' | 'bg1' | 'bg2' | 'accent1' | 'accent2' | 'accent3' | 'accent4' | 'accent5' | 'accent6'
export type Color = HexColor | ThemeColor
export type Gradient = { angle?: number; stops: GradientStops }
export type GradientStops = { [key: number]: Color }
export type Margin = number | [number, number, number, number]
export type HAlign = 'left' | 'center' | 'right' | 'justify'
export type VAlign = 'top' | 'middle' | 'bottom'
Expand Down Expand Up @@ -176,6 +178,14 @@ export interface ShapeFillProps {
* @example pptx.SchemeColor.text1 // Theme color (Text1)
*/
color?: Color
/**
* Fill gradient
* - Key inside object is the color position in percentage
* - `HextColor` or `ThemeColor`
* @example { angle:45, stops: { 0:'FF0000', 100:'00FF00' } }
* @example { angle:90, stops: { 0:pptx.SchemeColor.accent1, 100:pptx.SchemeColor.accent1 } }
*/
gradient?: Gradient
/**
* Transparency (percent)
* - MS-PPT > Format Shape > Fill & Line > Fill > Transparency
Expand All @@ -187,7 +197,7 @@ export interface ShapeFillProps {
* Fill type
* @default 'solid'
*/
type?: 'none' | 'solid'
type?: 'none' | 'solid' | 'gradient'

/**
* Transparency (percent)
Expand Down
66 changes: 65 additions & 1 deletion src/gen-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { EMU, REGEX_HEX_COLOR, DEF_FONT_COLOR, ONEPT, SchemeColor, SCHEME_COLORS } from './core-enums'
import { IChartOpts, PresLayout, TextGlowProps, PresSlide, ShapeFillProps, Color, ShapeLineProps, Coord } from './core-interfaces'
import { IChartOpts, PresLayout, TextGlowProps, PresSlide, ShapeFillProps, Color, ShapeLineProps, Coord, Gradient, GradientStops } from './core-interfaces'

/**
* Translates any type of `x`/`y`/`w`/`h` prop to EMU
Expand Down Expand Up @@ -198,6 +198,66 @@ export function createGlowElement(options: TextGlowProps, defaults: TextGlowProp
return strXml
}

/**
* Create gradient elements i.e. `a:gsLst` and `a:lin`
* @param {Gradient} gradient
* @param {string} internalElements
* @see http://officeopenxml.com/drwSp-GradFill.php
* @returns XML string
*/
function createGradientElements(gradient: Gradient, internalElements: string): string {
let strXml = '',
stops = gradient.stops

if (!isGradient(gradient)) {
console.warn(`This is not a valid gradient:`, gradient)
console.warn(`Fallback to default gradient with "${SchemeColor.background1}" and "${SchemeColor.background2}".`)
console.warn(`Please provide a valid gradient e.g. { angle:0, stops: { 0:"#FFFFFF", 100:"#000000" } }.`)
stops = { 0: SchemeColor.background1, 100: SchemeColor.background2 }
}

strXml += createGradientList(stops, internalElements)
strXml += createLinearGradient(gradient.angle)

return strXml
}

/**
* Create gradient list element i.e. `a:gsLst`
* @param {Gradient} gradient
* @param {string} internalElements
* @returns XML string
*/
function createGradientList(stops: GradientStops, internalElements: string): string {
const multiplier = 1000
const res = Object.keys(stops).map(pos => `<a:gs pos="${Number(pos) * multiplier}">${createColorElement(stops[pos], internalElements)}</a:gs>`)
return `<a:gsLst>${res.join('')}</a:gsLst>`
}

/**
* Create linear gradient element i.e. `a:lin`
* @param {number} angle
* @returns XML string
*/
function createLinearGradient(angle = 0): string {
const multiplier = 60000
return `<a:lin ang="${angle * multiplier}"/>`
}

/**
* Validate gradient
* @param {Gradient} gradient
* @returns boolean
*/
export function isGradient(gradient?: Gradient): boolean {
return Boolean(
gradient?.stops &&
typeof gradient.stops === 'object' &&
Object.keys(gradient.stops).length > 0 &&
Object.keys(gradient.stops).every(stop => Number.isFinite(Number(stop)))
)
}

/**
* Create color selection
* @param {Color | ShapeFillProps | ShapeLineProps} props fill props
Expand All @@ -213,6 +273,7 @@ export function genXmlColorSelection(props: Color | ShapeFillProps | ShapeLinePr
if (typeof props === 'string') colorVal = props
else {
if (props.type) fillType = props.type
if (props.gradient) fillType = 'gradient'
if (props.color) colorVal = props.color
if (props.alpha) internalElements += `<a:alpha val="${Math.round((100 - props.alpha) * 1000)}"/>` // DEPRECATED: @deprecated v3.3.0
if (props.transparency) internalElements += `<a:alpha val="${Math.round((100 - props.transparency) * 1000)}"/>`
Expand All @@ -222,6 +283,9 @@ export function genXmlColorSelection(props: Color | ShapeFillProps | ShapeLinePr
case 'solid':
outText += `<a:solidFill>${createColorElement(colorVal, internalElements)}</a:solidFill>`
break
case 'gradient':
outText += `<a:gradFill>${createGradientElements((props as ShapeFillProps).gradient, internalElements)}</a:gradFill>`
break
default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter
outText += ''
break
Expand Down
3 changes: 2 additions & 1 deletion src/gen-xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
getUuid,
inch2Emu,
valToPts,
isGradient,
} from './gen-utils'

let imageSizingXml = {
Expand Down Expand Up @@ -89,7 +90,7 @@ function slideObjectToXml(slide: PresSlide | SlideLayout): string {
// STEP 1: Add background color/image (ensure only a single `<p:bg>` tag is created, ex: when master-baskground has both `color` and `path`)
if (slide._bkgdImgRid) {
strSlideXml += `<p:bg><p:bgPr><a:blipFill dpi="0" rotWithShape="1"><a:blip r:embed="rId${slide._bkgdImgRid}"><a:lum/></a:blip><a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill><a:effectLst/></p:bgPr></p:bg>`
} else if (slide.background && slide.background.color) {
} else if (slide.background && (slide.background.color || slide.background.gradient)) {
strSlideXml += `<p:bg><p:bgPr>${genXmlColorSelection(slide.background)}</p:bgPr></p:bg>`
} else if (!slide.bkgd && slide._name && slide._name === DEF_PRES_LAYOUT_NAME) {
// NOTE: Default [white] background is needed on slideMaster1.xml to avoid gray background in Keynote (and Finder previews)
Expand Down
1 change: 1 addition & 0 deletions src/slide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export default class Slide {
* @type {BackgroundProps}
* @example solid color `background: { color:'FF0000' }`
* @example color+trans `background: { color:'FF0000', transparency:0.5 }`
* @example gradient `background: { gradient: { 0:'FF0000', 100:'00FFFF', angle:45 }`
* @example base64 `background: { data:'image/png;base64,ABC[...]123' }`
* @example url `background: { path:'https://some.url/image.jpg'}`
* @since v3.3.0
Expand Down

0 comments on commit 66f18bf

Please sign in to comment.