使用Apache POI无法在PPTX幻灯片中渲染图片问题排查
问题描述
我用Scala结合Apache POI读取PPTX模板,替换文本和图片占位符。文本替换正常,但图片始终不渲染,只显示图片文件路径。相关代码如下:
def replaceTags(slide: XSLFSlide, data: Data, slideShow: XMLSlideShow): Unit = { slide.getShapes.asScala .foreach { case textShape: XSLFTextShape => // shape that can hold text. val text = textShape.getText val updatedText = replaceTags(text, data) textShape.setText(updatedText) val updatedAnchor = getPlaceholderAnchor(textShape) textShape.setAnchor(updatedAnchor) textShape case pictureShape: XSLFPictureShape => val updatedPictureOpt = getPictureData(data.PIM_MAIN_IMAGE, slideShow) updatedPictureOpt.foreach { updatedPicture => slide.createPicture(updatedPicture) pictureShape.setAnchor(getPlaceholderAnchor(pictureShape)) } case shape => shape } } private def getPictureData(filePathOpt: Option[String], slideShow: XMLSlideShow): Option[XSLFPictureData] = { filePathOpt.flatMap { filePath => val imgBytes = IOUtils.toByteArray(Files.newInputStream(Path.of(filePath))) val pictureType = PictureData.PictureType.valueOf(FileNameUtils.getExtension(filePath)) slideShow.addPicture(imgBytes, pictureType).some } }
问题分析与解决方案
1. 占位符类型不匹配(核心问题)
你看到的“图片路径”说明代码执行了文本替换逻辑,而非图片形状替换逻辑——模板中的图片占位符大概率是文本框(比如写着{{PIM_MAIN_IMAGE}}的文本容器),而非真正的XSLFPictureShape。此时代码里的case pictureShape: XSLFPictureShape分支根本不会触发,文本替换把占位符换成了文件路径,自然只显示路径。
解决方法:在文本形状处理分支中,先判断文本是否包含图片占位符标记,再替换为图片:
case textShape: XSLFTextShape => val text = textShape.getText // 假设图片占位符标记是{{PIM_MAIN_IMAGE}} if (text.contains("{{PIM_MAIN_IMAGE}}")) { getPictureData(data.PIM_MAIN_IMAGE, slideShow).foreach { updatedPicture => // 保留原占位符的位置 val anchor = textShape.getAnchor // 删除原文本占位符 slide.removeShape(textShape) // 在原位置插入新图片 val newPicture = slide.createPicture(updatedPicture) newPicture.setAnchor(anchor) } } else { // 常规文本替换逻辑 val updatedText = replaceTags(text, data) textShape.setText(updatedText) val updatedAnchor = getPlaceholderAnchor(textShape) textShape.setAnchor(updatedAnchor) }
2. PictureType枚举大小写不匹配
FileNameUtils.getExtension(filePath)返回的扩展名是小写(比如png),但PictureData.PictureType枚举值是大写(比如PNG),直接调用valueOf会抛出IllegalArgumentException,导致getPictureData返回None,无法创建图片。
修复getPictureData方法:
private def getPictureData(filePathOpt: Option[String], slideShow: XMLSlideShow): Option[XSLFPictureData] = { filePathOpt.flatMap { filePath => val imgBytes = IOUtils.toByteArray(Files.newInputStream(Path.of(filePath))) // 转大写匹配枚举值,同时处理不支持的格式 val extension = FileNameUtils.getExtension(filePath).toUpperCase() val pictureType = try { PictureData.PictureType.valueOf(extension) } catch { case _: IllegalArgumentException => // 可选:默认用PNG,或抛出异常提示不支持的格式 PictureData.PictureType.PNG } Some(slideShow.addPicture(imgBytes, pictureType)) } }
3. 图片形状替换逻辑错误
如果模板中确实是XSLFPictureShape类型的占位符,你的代码逻辑也有问题:你新建了一张图片,但没有替换原图片形状的内容,反而修改原形状的锚点,导致原占位符(可能是空白图片)和新图片重叠或位置错误。
正确的替换方式是直接修改原图片形状的图片数据:
case pictureShape: XSLFPictureShape => getPictureData(data.PIM_MAIN_IMAGE, slideShow).foreach { updatedPicture => // 替换原图片形状的内容 pictureShape.setPictureData(updatedPicture) // 调整位置(如果需要) val updatedAnchor = getPlaceholderAnchor(pictureShape) pictureShape.setAnchor(updatedAnchor) }
总结
优先检查模板中图片占位符的类型:是文本框还是图片形状,对应调整代码分支;其次修复PictureType的大小写问题;最后确保图片替换逻辑正确(删除文本占位符/替换图片形状数据)。
内容的提问来源于stack exchange,提问作者kajoe14

