(node: PicNodeData, ctx: RenderContext)
| 42 | * - Rotation and flip transforms |
| 43 | */ |
| 44 | export function renderImage(node: PicNodeData, ctx: RenderContext): HTMLElement { |
| 45 | const wrapper = document.createElement('div') |
| 46 | wrapper.style.position = 'absolute' |
| 47 | wrapper.style.left = `${node.position.x}px` |
| 48 | wrapper.style.top = `${node.position.y}px` |
| 49 | wrapper.style.width = `${node.size.w}px` |
| 50 | wrapper.style.height = `${node.size.h}px` |
| 51 | wrapper.style.overflow = 'hidden' |
| 52 | |
| 53 | // Apply transforms |
| 54 | const transforms: string[] = [] |
| 55 | if (node.rotation !== 0) { |
| 56 | transforms.push(`rotate(${node.rotation}deg)`) |
| 57 | } |
| 58 | if (node.flipH) { |
| 59 | transforms.push('scaleX(-1)') |
| 60 | } |
| 61 | if (node.flipV) { |
| 62 | transforms.push('scaleY(-1)') |
| 63 | } |
| 64 | if (transforms.length > 0) { |
| 65 | wrapper.style.transform = transforms.join(' ') |
| 66 | } |
| 67 | |
| 68 | // ---- Handle video ---- |
| 69 | if (node.isVideo) { |
| 70 | renderVideo(node, ctx, wrapper) |
| 71 | return wrapper |
| 72 | } |
| 73 | |
| 74 | // ---- Handle audio ---- |
| 75 | if (node.isAudio) { |
| 76 | renderAudio(node, ctx, wrapper) |
| 77 | return wrapper |
| 78 | } |
| 79 | |
| 80 | // ---- Resolve image data ---- |
| 81 | const embedId = node.blipEmbed |
| 82 | if (!embedId) { |
| 83 | renderPlaceholder(wrapper, 'No image data') |
| 84 | return wrapper |
| 85 | } |
| 86 | |
| 87 | const rel = ctx.slide.rels.get(embedId) |
| 88 | if (!rel) { |
| 89 | renderPlaceholder(wrapper, 'Missing image reference') |
| 90 | return wrapper |
| 91 | } |
| 92 | |
| 93 | const mediaPath = resolveMediaPath(rel.target) |
| 94 | |
| 95 | // Check for unsupported formats (WMF) |
| 96 | if (isUnsupportedFormat(mediaPath)) { |
| 97 | renderUnsupportedPlaceholder(wrapper, mediaPath) |
| 98 | return wrapper |
| 99 | } |
| 100 | |
| 101 | const data = ctx.presentation.media.get(mediaPath) |
no test coverage detected