( node: GroupNodeData, ctx: RenderContext, renderNode: (childNode: BaseNodeData, ctx: RenderContext) => HTMLElement )
| 23 | * @param renderNode A callback to render individual child nodes (avoids circular deps) |
| 24 | */ |
| 25 | export function renderGroup( |
| 26 | node: GroupNodeData, |
| 27 | ctx: RenderContext, |
| 28 | renderNode: (childNode: BaseNodeData, ctx: RenderContext) => HTMLElement |
| 29 | ): HTMLElement { |
| 30 | const wrapper = document.createElement('div') |
| 31 | wrapper.style.position = 'absolute' |
| 32 | wrapper.style.left = `${node.position.x}px` |
| 33 | wrapper.style.top = `${node.position.y}px` |
| 34 | wrapper.style.width = `${node.size.w}px` |
| 35 | wrapper.style.height = `${node.size.h}px` |
| 36 | |
| 37 | // Apply rotation transform |
| 38 | const transforms: string[] = [] |
| 39 | if (node.rotation !== 0) { |
| 40 | transforms.push(`rotate(${node.rotation}deg)`) |
| 41 | } |
| 42 | if (node.flipH) { |
| 43 | transforms.push('scaleX(-1)') |
| 44 | } |
| 45 | if (node.flipV) { |
| 46 | transforms.push('scaleY(-1)') |
| 47 | } |
| 48 | if (transforms.length > 0) { |
| 49 | wrapper.style.transform = transforms.join(' ') |
| 50 | wrapper.style.transformOrigin = 'center center' |
| 51 | } |
| 52 | |
| 53 | const chOff = node.childOffset |
| 54 | const chExt = node.childExtent |
| 55 | const groupW = node.size.w |
| 56 | const groupH = node.size.h |
| 57 | |
| 58 | // Resolve group fill from grpSpPr for children that use a:grpFill |
| 59 | const grpSpPr = node.source.child('grpSpPr') |
| 60 | const childCtx: RenderContext = { ...ctx } |
| 61 | if (grpSpPr.exists()) { |
| 62 | // Check if the group itself has a fill (solidFill, gradFill, etc.) |
| 63 | // that children can inherit via grpFill |
| 64 | const FILL_TAGS = ['solidFill', 'gradFill', 'blipFill', 'pattFill'] |
| 65 | for (const tag of FILL_TAGS) { |
| 66 | if (grpSpPr.child(tag).exists()) { |
| 67 | childCtx.groupFillNode = grpSpPr |
| 68 | break |
| 69 | } |
| 70 | } |
| 71 | // If the group itself uses grpFill, propagate the parent's group fill |
| 72 | if (!childCtx.groupFillNode && grpSpPr.child('grpFill').exists() && ctx.groupFillNode) { |
| 73 | childCtx.groupFillNode = ctx.groupFillNode |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // Cycle diagram: 3 pie sectors + 3 circular arrows → one circle (3 equal 120° sectors) centered in the diagram. |
| 78 | const parsedChildren = new Map<number, BaseNodeData | undefined>() |
| 79 | const parseByIndex = (index: number): BaseNodeData | undefined => { |
| 80 | if (!parsedChildren.has(index)) { |
| 81 | parsedChildren.set(index, parseGroupChild(node.children[index], ctx)) |
| 82 | } |
no test coverage detected