diff --git a/demos/modules/demo_shape.mjs b/demos/modules/demo_shape.mjs index c921998c..ce922079 100644 --- a/demos/modules/demo_shape.mjs +++ b/demos/modules/demo_shape.mjs @@ -98,21 +98,36 @@ function genSlide01(pptx) { slide.addShape(pptx.shapes.RIGHT_TRIANGLE, { x: 0.4, y: 4.3, - w: 6.0, + w: 5.0, h: 3.0, fill: { color: pptx.colors.ACCENT5 }, line: { color: pptx.colors.ACCENT1, width: 3 }, shapeName: "First Right Triangle", }); slide.addShape(pptx.shapes.RIGHT_TRIANGLE, { - x: 7.0, + x: 8.0, y: 4.3, - w: 6.0, + w: 5.0, h: 3.0, fill: { color: pptx.colors.ACCENT5 }, line: { color: pptx.colors.ACCENT1, width: 2 }, flipH: true, }); + slide.addShape(pptx.shapes.RECTANGLE, { + x: 5.1, + y: 6, + w: 3.0, + h: 1, + fill: { + type: "linearGradient", + stops: [ + { position: 0, color: '000000', transparency: 10 }, + { position: 100, color: '333333', transparency: 50 }, + ], + angle: 45, + scaled: 1, + }, + }); } /** @@ -253,7 +268,7 @@ function genSlide02(pptx) { align: "center", x: 0.4, y: 4.3, - w: 6, + w: 5, h: 3, fill: { color: pptx.colors.ACCENT5 }, line: { color: "696969", width: 3 }, @@ -261,13 +276,30 @@ function genSlide02(pptx) { slide.addText("HYPERLINK-SHAPE", { shape: pptx.shapes.RIGHT_TRIANGLE, align: "center", - x: 7.0, + x: 8.0, y: 4.3, - w: 6, + w: 5, h: 3, fill: { color: pptx.colors.ACCENT5 }, line: { color: "696969", width: 2 }, flipH: true, hyperlink: { url: "https://github.com/gitbrent/pptxgenjs", tooltip: "Visit Homepage" }, }); + slide.addText("LINEAR GRADIENT", { + shape: pptx.shapes.RECTANGLE, + align: "center", + x: 5.1, + y: 6, + w: 3.0, + h: 1, + fill: { + type: "linearGradient", + stops: [ + { position: 0, color: '000000', transparency: 10 }, + { position: 100, color: '333333', transparency: 50 }, + ], + angle: 45, + scaled: 1, + }, + }); } diff --git a/src/core-interfaces.ts b/src/core-interfaces.ts index df6c617a..5f5a5f65 100644 --- a/src/core-interfaces.ts +++ b/src/core-interfaces.ts @@ -65,7 +65,7 @@ export interface DataOrPathProps { */ data?: string } -export interface BackgroundProps extends DataOrPathProps, ShapeFillProps { +export interface BackgroundProps extends DataOrPathProps, SolidShapeFillProps { /** * Color (hex format) * @deprecated v3.6.0 - use `ShapeFillProps` instead @@ -88,6 +88,7 @@ export type Color = HexColor | ThemeColor export type Margin = number | [number, number, number, number] export type HAlign = 'left' | 'center' | 'right' | 'justify' export type VAlign = 'top' | 'middle' | 'bottom' +export type PresetPatternValues = 'cross' | 'dkDnDiag' | 'dkHorz' | 'dkUpDiag' | 'dkVert' | 'dashDnDiag' | 'dashHorz' | 'dashUpDiag' | 'dashVert' | 'diagBrick' | 'diagCross' | 'divot' | 'dotGrid' | 'dotDmnd' | 'dnDiag' | 'horz' | 'horzBrick' | 'lgCheck' | 'lgConfetti' | 'lgGrid' | 'ltDnDiag' | 'ltHorz' | 'ltUpDiag' | 'ltVert' | 'narHorz' | 'narVert' | 'openDmnd' | 'pct10' | 'pct20' | 'pct25' | 'pct30' | 'pct40' | 'pct5' | 'pct50' | 'pct60' | 'pct70' | 'pct75' | 'pct80' | 'pct90' | 'plaid' | 'shingle' | 'smCheck' | 'smConfetti' | 'smGrid' | 'solidDmnd' | 'sphere' | 'trellis' | 'upDiag' | 'vert' | 'wave' | 'weave' | 'wdDnDiag' | 'wdUpDiag' | 'zigZag' // used by charts, shape, text export interface BorderProps { @@ -171,7 +172,67 @@ export interface ShadowProps { rotateWithShape?: boolean } // used by: shape, table, text -export interface ShapeFillProps { +export interface GradientStop { + /** + * Position (percent) + * - range: 0-100 + */ + position: number + /** + * Gradient stop color + * - `HexColor` or `ThemeColor` + * @example 'FF0000' // hex color (red) + * @example pptx.SchemeColor.text1 // Theme color (Text1) + */ + color: Color + /** + * Transparency (percent) + * - range: 0-100 + * @default 0 + */ + transparency?: number +} +interface BaseGradientShapeFillProps { + /** + * Gradient stops + * - Only used with linearGradient and pathGradient types + */ + stops: GradientStop[] + /** + * Rotate with shape + * @default true + */ + rotWithShape?: boolean + /** + * Tile rectangle + */ + tileRect?: { t?: number, r?: number, b?: number, l?: number } + /** + * Gradient flip direction + * - Only used when tileRect is specified + * @default 'none' + */ + flip?: 'none' | 'x' | 'xy' | 'y' +} +export interface LinearGradientShapeFillProps extends BaseGradientShapeFillProps { + /** + * Linear gradient angle (degrees) + * - range: 0-359 + * @default 0 + */ + angle?: number + /** + * Scaled + * - `true` will scale the gradient with the object + * @default false + */ + scaled?: boolean + /** + * Fill type + */ + type: 'linearGradient' +} +export interface SolidShapeFillProps { /** * Fill color * - `HexColor` or `ThemeColor` @@ -198,7 +259,33 @@ export interface ShapeFillProps { */ alpha?: number } -export interface ShapeLineProps extends ShapeFillProps { + +export interface PatternShapeFillProps { + /** + * Foreground color + * - `HexColor` + * @example 'FF0000' // hex color (red) + */ + color?: HexColor + /** + * Background color + * - `HexColor` + * @example 'FF0000' // hex color (red) + */ + bgColor?: HexColor + /** + * Fill type + */ + type: 'patternFill' + + /** + * Preset Pattern + * @default 'cross' + */ + prst?: PresetPatternValues +} + +export interface ShapeLineProps extends SolidShapeFillProps { /** * Line width (pt) * @default 1 @@ -635,7 +722,7 @@ export interface ShapeProps extends PositionProps, ObjectNameProps { * @example { color:'0088CC', transparency:50 } // hex color, 50% transparent * @example { color:pptx.SchemeColor.accent1 } // Theme color Accent1 */ - fill?: ShapeFillProps + fill?: LinearGradientShapeFillProps | SolidShapeFillProps /** * Flip shape horizontally? * @default false @@ -832,7 +919,7 @@ export interface TableCellProps extends TextBaseProps { * @example { color:'0088CC', transparency:50 } // hex color, 50% transparent * @example { color:pptx.SchemeColor.accent1 } // theme color Accent1 */ - fill?: ShapeFillProps + fill?: LinearGradientShapeFillProps | SolidShapeFillProps hyperlink?: HyperlinkProps /** * Cell margin (inches) @@ -910,7 +997,7 @@ export interface TableProps extends PositionProps, TextBaseProps, ObjectNameProp * @example { color:'0088CC', transparency:50 } // hex color, 50% transparent * @example { color:pptx.SchemeColor.accent1 } // theme color Accent1 */ - fill?: ShapeFillProps + fill?: LinearGradientShapeFillProps | SolidShapeFillProps /** * Cell margin (inches) * - affects all table cells, is superceded by cell options @@ -1017,7 +1104,7 @@ export interface TextPropsOptions extends PositionProps, DataOrPathProps, TextBa * @example { color:'0088CC', transparency:50 } // hex color, 50% transparent * @example { color:pptx.SchemeColor.accent1 } // theme color Accent1 */ - fill?: ShapeFillProps + fill?: LinearGradientShapeFillProps | SolidShapeFillProps /** * Flip shape horizontally? * @default false @@ -1221,7 +1308,7 @@ export interface IChartPropsFillLine { * @example fill: {color: pptx.SchemeColor.background2} // Theme color value * @example fill: {transparency: 50} // 50% transparency */ - fill?: ShapeFillProps + fill?: LinearGradientShapeFillProps | SolidShapeFillProps } export interface IChartAreaProps extends IChartPropsFillLine { /** diff --git a/src/gen-charts.ts b/src/gen-charts.ts index efe0c95e..719499c6 100644 --- a/src/gen-charts.ts +++ b/src/gen-charts.ts @@ -690,7 +690,9 @@ export function makeXmlCharts (rel: ISlideRelChart): string { strXml += ' ' // OPTION: Fill - strXml += rel.opts.plotArea.fill?.color ? genXmlColorSelection(rel.opts.plotArea.fill) : '' + strXml += rel.opts.plotArea.fill.type === 'solid' || rel.opts.plotArea.fill.type === 'linearGradient' + ? genXmlColorSelection(rel.opts.plotArea.fill) + : '' // OPTION: Border strXml += rel.opts.plotArea.border @@ -737,7 +739,9 @@ export function makeXmlCharts (rel: ISlideRelChart): string { // D: CHARTSPACE SHAPE PROPS strXml += '' - strXml += rel.opts.chartArea.fill?.color ? genXmlColorSelection(rel.opts.chartArea.fill) : '' + strXml += rel.opts.chartArea.fill.type === 'solid' || rel.opts.chartArea.fill.type === 'linearGradient' + ? genXmlColorSelection(rel.opts.chartArea.fill) + : '' strXml += rel.opts.chartArea.border ? `${genXmlColorSelection(rel.opts.chartArea.border.color)}` : '' diff --git a/src/gen-objects.ts b/src/gen-objects.ts index 2380a51e..bd527b15 100644 --- a/src/gen-objects.ts +++ b/src/gen-objects.ts @@ -40,6 +40,7 @@ import { ShapeProps, SlideLayout, SlideMasterProps, + SolidShapeFillProps, TableCell, TableProps, TableRow, @@ -316,7 +317,14 @@ export function addChartDefinition (target: PresSlide, type: CHART_NAME | IChart if (options.plotArea.border && (!options.plotArea.border.color || typeof options.plotArea.border.color !== 'string')) { options.plotArea.border.color = DEF_CHART_BORDER.color } if (options.border) options.plotArea.border = options.border // @deprecated [[remove in v4.0]] options.plotArea.fill = options.plotArea.fill || { color: null, transparency: null } - if (options.fill) options.plotArea.fill.color = options.fill // @deprecated [[remove in v4.0]] + if (options.fill) { + const fill: SolidShapeFillProps = { + type: 'solid', + color: options.fill, // @deprecated [[remove in v4.0]] + transparency: null, + } + options.plotArea.fill = fill + } // options.chartArea = options.chartArea || {} options.chartArea.border = options.chartArea.border && typeof options.chartArea.border === 'object' ? options.chartArea.border : null @@ -897,8 +905,8 @@ export function addTableDefinition ( // STEP 4: Convert units to EMU now (we use different logic in makeSlide->table - smartCalc is not used) if (opt.x && opt.x < 20) opt.x = inch2Emu(opt.x) if (opt.y && opt.y < 20) opt.y = inch2Emu(opt.y) - if (opt.w && opt.w < 20) opt.w = inch2Emu(opt.w) - if (opt.h && opt.h < 20) opt.h = inch2Emu(opt.h) + if (opt.w && typeof opt.w === 'number' && opt.w < 20) opt.w = inch2Emu(opt.w) + if (opt.h && typeof opt.h === 'number' && opt.h < 20) opt.h = inch2Emu(opt.h) // STEP 5: Loop over cells: transform each to ITableCell; check to see whether to unset `autoPage` while here arrRows.forEach(row => { diff --git a/src/gen-utils.ts b/src/gen-utils.ts index 16e9f683..3bf79916 100644 --- a/src/gen-utils.ts +++ b/src/gen-utils.ts @@ -3,7 +3,7 @@ */ import { EMU, REGEX_HEX_COLOR, DEF_FONT_COLOR, ONEPT, SchemeColor, SCHEME_COLORS } from './core-enums' -import { PresLayout, TextGlowProps, PresSlide, ShapeFillProps, Color, ShapeLineProps, Coord, ShadowProps } from './core-interfaces' +import { PresLayout, TextGlowProps, PresSlide, SolidShapeFillProps, Color, ShapeLineProps, Coord, ShadowProps, LinearGradientShapeFillProps, PatternShapeFillProps } from './core-interfaces' /** * Translates any type of `x`/`y`/`w`/`h` prop to EMU @@ -182,32 +182,98 @@ export function createGlowElement (options: TextGlowProps, defaults: TextGlowPro /** * Create color selection - * @param {Color | ShapeFillProps | ShapeLineProps} props fill props + * @param {Color | ShapeFillProps | ShapeLineProps | PatternShapeFillProps} props fill props * @returns XML string */ -export function genXmlColorSelection (props: Color | ShapeFillProps | ShapeLineProps): string { - let fillType = 'solid' - let colorVal = '' - let internalElements = '' +export function genXmlColorSelection (props: Color | SolidShapeFillProps | ShapeLineProps | LinearGradientShapeFillProps | PatternShapeFillProps): string { + if (!props) { + return '' + } + let outText = '' - if (props) { - if (typeof props === 'string') colorVal = props - else { - if (props.type) fillType = props.type - if (props.color) colorVal = props.color - if (props.alpha) internalElements += `` // DEPRECATED: @deprecated v3.3.0 - if (props.transparency) internalElements += `` + let safeProps: SolidShapeFillProps | ShapeLineProps | LinearGradientShapeFillProps | PatternShapeFillProps = {} + if (typeof props === 'string') { + safeProps.type = 'solid' + safeProps.color = props + } else { + safeProps = props + safeProps.type = props.type ?? 'solid' + } + + switch (safeProps.type) { + case 'solid': { + const transparency = safeProps.transparency ?? safeProps.alpha + const internalElements = transparency + ? `` + : undefined + outText += `${createColorElement(safeProps.color ?? '', internalElements)}` + break } - switch (fillType) { - case 'solid': - outText += `${createColorElement(colorVal, internalElements)}` - break - default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter - outText += '' - break + case 'linearGradient': { + const stops = safeProps.stops ?? [] + const rotWithShape = safeProps.rotWithShape ?? true + const flip = safeProps.flip ?? 'none' + + outText += `` + + if (stops.length > 0) { + outText += '' + + outText += stops.map( + ({ position, color: stopColor, transparency }) => { + const stopInternalElements = transparency + ? `` + : '' + + return `${createColorElement(stopColor, stopInternalElements)}` + } + ).join('') + + outText += '' + } + + if (safeProps.angle) { + const ang = convertRotationDegrees(safeProps.angle) + const scaled = safeProps.scaled ?? false + outText += `` + } + + if ( + safeProps.tileRect && + ( + safeProps.tileRect.t || + safeProps.tileRect.r || + safeProps.tileRect.b || + safeProps.tileRect.l + ) + ) { + const tAttr = safeProps.tileRect.t ? `t="${safeProps.tileRect.t * 1000}"` : '' + const rAttr = safeProps.tileRect.r ? `r="${safeProps.tileRect.r * 1000}"` : '' + const bAttr = safeProps.tileRect.b ? `b="${safeProps.tileRect.b * 1000}"` : '' + const lAttr = safeProps.tileRect.l ? `l="${safeProps.tileRect.l * 1000}"` : '' + + outText += `` + } + + outText += '' + break + } + case 'patternFill': { + outText += ` + + + + + + + ` + break } + default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter + outText += '' + break } return outText diff --git a/src/slide.ts b/src/slide.ts index a46f1eaf..8fb7fa92 100644 --- a/src/slide.ts +++ b/src/slide.ts @@ -85,7 +85,7 @@ export default class Slide { private _bkgd: string | BackgroundProps public set bkgd (value: string | BackgroundProps) { this._bkgd = value - if (!this._background || !this._background.color) { + if (!this._background?.color) { if (!this._background) this._background = {} if (typeof value === 'string') this._background.color = value } @@ -153,7 +153,7 @@ export default class Slide { return this._slideNumberProps } - public get newAutoPagedSlides(): PresSlide[] { + public get newAutoPagedSlides (): PresSlide[] { return this._newAutoPagedSlides }