Skip to content

Commit

Permalink
feat: svg renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
qq15725 committed Dec 10, 2024
1 parent 012ab2c commit 1762c4d
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 35 deletions.
7 changes: 5 additions & 2 deletions docs/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OXML, Pptx } from '../../src'
import { OXML, PPTX, SVGRenderer } from '../../src'

console.log(
OXML.tagToConstructor,
Expand All @@ -12,7 +12,10 @@ input.type = 'file'
input.onchange = async () => {
const file = input.files?.[0]

const pptx = Pptx.parse(new Uint8Array(await file?.arrayBuffer()))
const pptx = PPTX.parse(new Uint8Array(await file?.arrayBuffer()))

const svg = new SVGRenderer().render(pptx)
document.body.appendChild(svg)

console.log(pptx, pptx.toJSON())
}
7 changes: 4 additions & 3 deletions src/OpenXml/Drawing/Paragraph.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TextAlignmentTypeValues } from './_types'
import type { Break } from './Break'
import type { EndParagraphRunProperties } from './EndParagraphRunProperties'
import type { Field } from './Field'
Expand All @@ -16,7 +17,7 @@ export class Paragraph extends OXML {
@defineProperty() style = new _ParagraphStyle(this)
@defineProperty('pPr.lvl') declare level?: number
@defineProperty('pPr.fontAlgn') declare fontAlign?: string
@defineProperty('_children') declare children?: (Break | Run | EndParagraphRunProperties)[]
@defineProperty('_children') declare children: (Break | Run | EndParagraphRunProperties)[]

get _children(): (Break | Run | EndParagraphRunProperties)[] {
return Array.from(this.element.children).map((element) => {
Expand All @@ -40,8 +41,8 @@ export class _ParagraphStyle extends OXML {
@defineProperty('_parent.pPr.indent') declare textIndent: number
@defineProperty('_parent.pPr.lnSpc.spcPct.val') declare lineHeight?: number

get textAlign() { return this._parent.pPr.algn }
get rightToLeft(): string | undefined { return this._parent.pPr.rtl }
get textAlign(): TextAlignmentTypeValues | undefined { return this._parent.pPr.algn }
get rightToLeft(): boolean | undefined { return this._parent.pPr.rtl }

constructor(
protected _parent: Paragraph,
Expand Down
3 changes: 2 additions & 1 deletion src/OpenXml/Drawing/PresetGeometry.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AdjustValueList } from './AdjustValueList'
import { defineAttribute, defineChild, defineElement, OXML } from '../../core'

/**
Expand All @@ -7,5 +8,5 @@ import { defineAttribute, defineChild, defineElement, OXML } from '../../core'
export class PresetGeometry extends OXML {
@defineAttribute('prst') declare prst: string

@defineChild('a:avLst') declare avLst?: OXML
@defineChild('a:avLst') declare avLst?: AdjustValueList
}
2 changes: 0 additions & 2 deletions src/OpenXml/Drawing/Run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export class Run extends OXML {
@defineProperty() style = new _RunStyle(this)
@defineProperty('_content') declare content: string

get color(): string | undefined { return this.rPr.solidFill?.srgbClr.val }
get textIndent(): string | undefined { return this.rPr.kern }
protected get _content(): string { return this.t.element.textContent ?? '' }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import { defineAttribute, defineElement, OXML } from '../../core'
*/
@defineElement('p:cNvSpPr')
export class NonVisualShapeDrawingProperties extends OXML {
@defineAttribute('txBox', 'boolean') declare txBox?: boolean
@defineAttribute('txBox', { type: 'boolean', defaultValue: true }) declare txBox: boolean
}
8 changes: 2 additions & 6 deletions src/core/OXML.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { parseDomFromString } from '../utils'
import { deepMerge, getObjectValueByPath, setObjectValueByPath } from './utils'

export type OXMLProto = new (...args: any[]) => OXML
Expand Down Expand Up @@ -348,12 +349,7 @@ export class OXML {
}

fromXML(xml: string): this {
const doc = new DOMParser().parseFromString(xml, 'text/xml') as XMLDocument
const error = doc.querySelector('parsererror')
if (error) {
throw new Error(error.textContent ?? 'parser error')
}
this.element = doc.documentElement
this.element = parseDomFromString(xml)
return this
}

Expand Down
68 changes: 50 additions & 18 deletions src/Pptx.ts → src/extensions/PPTX.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { Zippable } from 'fflate'
import { unzipSync, zipSync } from 'fflate'
import { CoreProperties, Relationships, Types } from './OPC'
import { Theme } from './OpenXml/Drawing'
import { Properties } from './OpenXml/ExtendedProperties'
import { Picture, Presentation, PresentationProperties, Slide, SlideLayout, SlideMaster, ViewProperties } from './OpenXml/Presentation'
import { joinPaths } from './utils'
import { CoreProperties, Relationships, Types } from '../OPC'
import { Theme } from '../OpenXml/Drawing'
import { Properties } from '../OpenXml/ExtendedProperties'
import { Picture, Presentation, PresentationProperties, Slide, SlideLayout, SlideMaster, ViewProperties } from '../OpenXml/Presentation'
import { joinPaths } from '../utils'

/**
* @link https://learn.microsoft.com/en-us/openspecs/office_standards/ms-pptx/efd8bb2d-d888-4e2e-af25-cad476730c9f
*/
export class PPTX {
unzipped: { [path: string]: Uint8Array } = {}

export class Pptx {
declare app: Properties
declare core: CoreProperties
themes: Theme[] = []
Expand All @@ -24,20 +29,46 @@ export class Pptx {
get width(): number { return this.presentation.sldSz.cx }
get height(): number { return this.presentation.sldSz.cy }

static parse(source: Uint8Array) {
const unzipped = unzipSync(source)
static getRelsPath(path = ''): { base: string, path: string } {
const name = path.split('/').pop()
return {
base: joinPaths(path, '../'),
path: joinPaths(path, '../', '_rels', `${name}.rels`),
}
}

const read = (path: string) => new TextDecoder().decode(unzipped[path])
const getRelsPath = (path = '') => {
const paths = path.split('/')
const name = paths.pop()
return {
base: paths.join('/'),
path: [...paths, '_rels', `${name}.rels`].join('/'),
}
readRid(rId: string, type: string, index = 0): any | undefined {
switch (type) {
case 'slide':
return this.read(this.slidesRels[index][rId].path, 'dataURI')
default:
return undefined
}
}

const pptx = new Pptx()
read(path: string): ArrayBuffer | undefined
read(path: string, type: 'string'): string | undefined
read(path: string, type: 'dataURI'): string | undefined
read(path: string, type?: string): any | undefined {
const uint8Array = this.unzipped[path]
if (!uint8Array) {
return undefined
}
switch (type) {
case 'string':
return new TextDecoder().decode(uint8Array)
case 'dataURI':
return `data:image/png;base64,${btoa(Array.from(uint8Array, byte => String.fromCharCode(byte)).join(''))}`
default:
return uint8Array.buffer
}
}

static parse(source: Uint8Array) {
const pptx = new PPTX()
pptx.unzipped = unzipSync(source)
const read = (path: string): string => pptx.read(path, 'string')!
const { getRelsPath } = PPTX

// [Content_Types].xml
const types = new Types(read('[Content_Types].xml'))
Expand Down Expand Up @@ -104,6 +135,7 @@ export class Pptx {
const slideRels = new Relationships(read(slideRelsPath)).value
Object.values(slideRels).forEach((rel) => {
const path = joinPaths(slideRelsBase, rel.target)
rel.path = path
switch (rel.type) {
// ppt/slideLayout/slideLayout1.xml
case Relationships.types.slideLayout: {
Expand All @@ -112,7 +144,6 @@ export class Pptx {
}
}
})

pptx.slides.push(slide)
pptx.slidesRels.push(slideRels)
})
Expand All @@ -135,6 +166,7 @@ export class Pptx {
const slideMasterRels = new Relationships(read(slideMasterRelsPath)).value
Object.values(slideMasterRels).forEach((rel) => {
const path = joinPaths(slideMasterRelsBase, rel.target)
rel.path = path
switch (rel.type) {
case Relationships.types.theme:
slideMasterMeta.themePath = path
Expand Down
2 changes: 1 addition & 1 deletion src/Xlsx.ts → src/extensions/XLSX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface XlsxProps {
sheets: (ReturnType<typeof Sheet.parse> & { name: string | undefined })[]
}

export class Xlsx {
export class XLSX {
parse(source: Source) {
const jszip = new Jszip()
const zip = await jszip.loadAsync(source)
Expand Down
1 change: 1 addition & 0 deletions src/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './PPTX'
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './core'
export * from './extensions'
export * from './OPC'
export * from './OpenXml'
export * from './pptx'
export * from './renderers'
Loading

0 comments on commit 1762c4d

Please sign in to comment.