gitbrent / PptxGenJS

Create PowerPoint presentations with a powerful, concise JavaScript API.
https://gitbrent.github.io/PptxGenJS/
MIT License
2.84k stars 625 forks source link

Theme Injection #1243

Open prescience-data opened 1 year ago

prescience-data commented 1 year ago

Just wondering if you'd be interested a PR that touches this component to inject custom theme data, which would allow referencing colours by relative accent1 tags rather than absolute hex codes?

https://github.com/gitbrent/PptxGenJS/blob/5a30b0f5c75a1b7495202155064e5b8595ecf8f8/src/gen-xml.ts#L1753

I prefer to get input before unilaterally trying to make any design decisions in the PR, so just opening this ticket as a discussion point first.

This pseudocode is roughly what I had in mind:

import PptxGenJS from "pptxgenjs"

import HexColor = PptxGenJS.HexColor
import ThemeColor = PptxGenJS.ThemeColor

/** Update existing theme props. */
export interface ThemeProps {
  name?: string
  colors: Record<ThemeColor, HexColor>
  headFontFace?: string
  bodyFontFace?: string
}

/** Update existing presentation props. */
export interface IPresentationProps {
  theme: ThemeProps
}

/** Inject theme into presentation. */
export function makeXmlTheme({
  theme: {
    name = "Office",
    colors, // TODO: Resolve default colors.
    headFontFace = "Calibri Light",
    bodyFontFace = "Calibri"
  }
}: IPresentationProps): string {
  return /* language=XML */ `
      <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
      <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme">
          <a:themeElements>
              <a:clrScheme name="${name}">
                  <a:dk1>
                      <a:sysClr val="windowText" lastClr="${colors.tx1.replace(/^#/, ``)}"/>
                  </a:dk1>
                  <a:lt1>
                      <a:sysClr val="window" lastClr="${colors.bg1.replace(/^#/, ``)}"/>
                  </a:lt1>
                  <a:dk2>
                      <a:srgbClr val="${colors.tx2.replace(/^#/, ``)}"/>
                  </a:dk2>
                  <a:lt2>
                      <a:srgbClr val="${colors.bg2.replace(/^#/, ``)}"/>
                  </a:lt2>
                  <a:accent1>
                      <a:srgbClr val="${colors.accent1.replace(/^#/, ``)}"/>
                  </a:accent1>
                  <a:accent2>
                      <a:srgbClr val="${colors.accent2.replace(/^#/, ``)}"/>
                  </a:accent2>
                  <a:accent3>
                      <a:srgbClr val="${colors.accent3.replace(/^#/, ``)}"/>
                  </a:accent3>
                  <a:accent4>
                      <a:srgbClr val="${colors.accent4.replace(/^#/, ``)}"/>
                  </a:accent4>
                  <a:accent5>
                      <a:srgbClr val="${colors.accent5.replace(/^#/, ``)}"/>
                  </a:accent5>
                  <a:accent6>
                      <a:srgbClr val="${colors.accent6.replace(/^#/, ``)}"/>
                  </a:accent6>
                  <a:hlink>
                      <a:srgbClr val="${colors.accent1.replace(/^#/, ``)}"/>
                  </a:hlink>
                  <a:folHlink>
                      <a:srgbClr val="${colors.accent1.replace(/^#/, ``)}"/>
                  </a:folHlink>
              </a:clrScheme>
              <a:fontScheme name="${name}">
                  <a:majorFont>
                      <a:latin typeface="${headFontFace}"/>
                      <a:ea typeface=""/>
                      <a:cs typeface=""/>
                  </a:majorFont>
                  <a:minorFont>
                      <a:latin typeface="${bodyFontFace}"/>
                      <a:ea typeface=""/>
                      <a:cs typeface=""/>
                  </a:minorFont>
              </a:fontScheme>
              /** ... rest of theme **/
      </a:theme>
  `
    .trim()
    .replace(/\n+/g, ` `)
    .replace(/>\s+</g, `><`)
}

}

FWIW I have this POC working with the following custom implementation:

 async #writeWithTheme(filename: string): Promise<void> {
    const data = await this.pptx().write({
      outputType: "nodebuffer",
      compression: false
    })

    logger.debug(`Adding office theme to "${filename}".`)
    const zip = new JSZip()

    await zip.loadAsync(data)

    zip.file("ppt/theme/theme1.xml", makeXmlTheme("Office", this.theme))

    logger.debug(`Generating zip content.`)
    const output = await zip.generateAsync({
      type: "nodebuffer",
      compression: "DEFLATE"
    })

    logger.debug(`Writing presentation to "${filename}".`)
    await writeFile(filename, output)
  }

image