在 JavaScript 中将演示文稿幻灯片渲染为 SVG 图像

概述

SVG 是一种可伸缩的基于 XML 的图像格式,适用于网络发布、幻灯片查看器、可访问性工作流和自动后期处理。Aspose.Slides for Node.js via Java 将每张幻灯片导出为单独的 SVG 文件,并让您控制文本、字体、图片和 SVG 元素的写入方式。

当导出的 SVG 必须紧凑、在各浏览器之间表现可预期或准备好用于交互时,请使用 SVGOptions。

将幻灯片导出为 SVG

创建一个 Presentation,选择一张幻灯片,并使用 Slide.writeAsSvg 将其写入流。以下示例将演示文稿中的每张幻灯片导出为单独的 SVG 文件。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation("presentation.pptx");
try {
    const slideCount = presentation.getSlides().size();
    for (let slideIndex = 0; slideIndex < slideCount; slideIndex++) {
        const slide = presentation.getSlides().get_Item(slideIndex);
        const outputFileName = `slide-${slide.getSlideNumber()}.svg`;
        const svgStream = java.newInstanceSync("java.io.FileOutputStream", outputFileName);
        try {
            slide.writeAsSvg(svgStream);
        } finally {
            svgStream.close();
        }
    }
} finally {
    presentation.dispose();
}

文件名使用 Slide.getSlideNumber 而不是循环索引。当幻灯片查看器或网页仅需要某个形状时,您也可以使用 Shape.writeAsSvg 导出单个形状。

配置 SVG 输出

SVGOptions 控制 SVG 渲染。对于文本框,SVGOptions.setUseFrameSize 将文本框包括在渲染区域中,SVGOptions.setUseFrameRotation 决定是否应用框的旋转。当文本必须在不使用连字的情况下渲染时,将 SVGOptions.setDisableFontLigatures 设置为 true。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation("presentation.pptx");
try {
    const svgOptions = new slides.SVGOptions();
    svgOptions.setDisableFontLigatures(true);
    svgOptions.setUseFrameSize(true);
    svgOptions.setUseFrameRotation(false);

    const slide = presentation.getSlides().get_Item(0);
    const svgStream = java.newInstanceSync(
        "java.io.FileOutputStream",
        "slide-with-custom-options.svg"
    );
    try {
        slide.writeAsSvg(svgStream, svgOptions);
    } finally {
        svgStream.close();
    }
} finally {
    presentation.dispose();
}

控制箭头端的线端裁剪

SVG 1.1 没有标记内嵌设置来指定线在箭头下方应停在哪里。默认情况下,Aspose.Slides 会缩短导出的线,以使其笔画不会穿过标记。使用 true 调用 SVGOptions.setDisableLineEndCropping 可将笔画绘制到线的原始终点。此设置仅影响 SVG 输出。该线在幻灯片上的位置和长度保持不变。

示例在宽度为 2 和 10 点的线段上绘制三角形和菱形箭头。它两次导出同一张幻灯片,仅更改裁剪选项。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation();
try {
    const slide = presentation.getSlides().get_Item(0);
    const lineColor = java.newInstanceSync("java.awt.Color", 0, 0, 139);
    let y = 40;

    for (const style of [slides.LineArrowheadStyle.Triangle, slides.LineArrowheadStyle.Diamond]) {
        for (const width of [2, 10]) {
            const line = slide.getShapes().addAutoShape(slides.ShapeType.Line, 40, y, 200, 0);
            const format = line.getLineFormat();
            format.getFillFormat().setFillType(java.newByte(slides.FillType.Solid));
            format.getFillFormat().getSolidFillColor().setColor(lineColor);
            format.setWidth(width);
            format.setEndArrowheadStyle(java.newByte(style));
            y += 50;
        }
    }

    const options = new slides.SVGOptions();
    options.setDisableLineEndCropping(false);
    const cropped = java.newInstanceSync("java.io.FileOutputStream", "cropped.svg");
    try {
        slide.writeAsSvg(cropped, options);
    } finally {
        cropped.close();
    }

    options.setDisableLineEndCropping(true);
    const uncropped = java.newInstanceSync("java.io.FileOutputStream", "uncropped.svg");
    try {
        slide.writeAsSvg(uncropped, options);
    } finally {
        uncropped.close();
    }
} finally {
    presentation.dispose();
}

对比展示了 10 点线上的三角形箭头。启用裁剪时,箭头尖锐;禁用裁剪时,笔画会穿过标记的倾斜侧面,留下平坦的末端。

启用裁剪时带尖锐尖端的放大 10 点三角形,禁用裁剪时的平坦末端

在示例中的其它线条上,突出笔画在 2 点三角形上不太明显。实心菱形在两种宽度下都覆盖了笔画的末端。

控制文本和字体

将幻灯片导出为 SVG 时,您可以控制文本是保持为文本还是转换为矢量图形,以及外部字体的包含或引用方式。以下选项帮助您在导出 SVG 时平衡视觉一致性、文本选择和字体依赖性。

矢量化所有文本

将 SVGOptions.setVectorizeText 设置为 true 可将所有幻灯片文本写为矢量图形。这消除了字体依赖,使在各浏览器间的视觉效果更一致,但文本不再可作为 SVG 文本进行选择或搜索。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation("presentation.pptx");
try {
    const svgOptions = new slides.SVGOptions();
    svgOptions.setVectorizeText(true);

    const slide = presentation.getSlides().get_Item(0);
    const svgStream = java.newInstanceSync(
        "java.io.FileOutputStream",
        "slide-with-vectorized-text.svg"
    );
    try {
        slide.writeAsSvg(svgStream, svgOptions);
    } finally {
        svgStream.close();
    }
} finally {
    presentation.dispose();
}

选择外部字体的处理方式

SVGOptions.setExternalFontsHandling 对外部加载的字体使用 SvgExternalFontsHandling 值。选择 AddLinksToFontFiles 来引用单独的字体文件,Embed 将字体数据嵌入 SVG,或 Vectorize 将仅使用外部字体的文本渲染为图形。嵌入字体前请确认字体授权。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation("presentation.pptx");
try {
    const slide = presentation.getSlides().get_Item(0);

    const linkedFontsOptions = new slides.SVGOptions();
    linkedFontsOptions.setExternalFontsHandling(
        slides.SvgExternalFontsHandling.AddLinksToFontFiles
    );
    const linkedFontsStream = java.newInstanceSync(
        "java.io.FileOutputStream",
        "slide-with-font-links.svg"
    );
    try {
        slide.writeAsSvg(linkedFontsStream, linkedFontsOptions);
    } finally {
        linkedFontsStream.close();
    }

    const embeddedFontsOptions = new slides.SVGOptions();
    embeddedFontsOptions.setExternalFontsHandling(
        slides.SvgExternalFontsHandling.Embed
    );
    const embeddedFontsStream = java.newInstanceSync(
        "java.io.FileOutputStream",
        "slide-with-embedded-fonts.svg"
    );
    try {
        slide.writeAsSvg(embeddedFontsStream, embeddedFontsOptions);
    } finally {
        embeddedFontsStream.close();
    }

    const vectorizedExternalFontsOptions = new slides.SVGOptions();
    vectorizedExternalFontsOptions.setExternalFontsHandling(
        slides.SvgExternalFontsHandling.Vectorize
    );
    const vectorizedExternalFontsStream = java.newInstanceSync(
        "java.io.FileOutputStream",
        "slide-with-vectorized-external-fonts.svg"
    );
    try {
        slide.writeAsSvg(vectorizedExternalFontsStream, vectorizedExternalFontsOptions);
    } finally {
        vectorizedExternalFontsStream.close();
    }
} finally {
    presentation.dispose();
}

减小嵌入图像大小

使用 SVGOptions.setPicturesCompression 降低嵌入图片的分辨率,使用 SVGOptions.setDeletePicturesCroppedAreas 省略裁剪后的源区域,并使用 SVGOptions.setJpegQuality 控制 JPEG 编码质量。这些设置会在牺牲图像保真度或保留的图像数据的代价下减小文件大小。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation("presentation.pptx");
try {
    const svgOptions = new slides.SVGOptions();
    svgOptions.setPicturesCompression(slides.PicturesCompression.Dpi150);
    svgOptions.setDeletePicturesCroppedAreas(true);
    svgOptions.setJpegQuality(80);

    const slide = presentation.getSlides().get_Item(0);
    const svgStream = java.newInstanceSync("java.io.FileOutputStream", "compressed-slide.svg");
    try {
        slide.writeAsSvg(svgStream, svgOptions);
    } finally {
        svgStream.close();
    }
} finally {
    presentation.dispose();
}

控制元文件光栅化分辨率

SVGOptions.setMetafileRasterizationDpi 设置在 SVG 导出期间对元文件进行光栅化的最低分辨率限制。当 EMF 或 WMF 图像被光栅化时,可使用它在细节和文件大小之间取得平衡。它并未为 SVG 定义全局 DPI,也不会强制幻灯片上的每个对象都变为光栅图像。

下载 metafile-rasterization.pptx,其中包含具有密集对角线的 EMF 图像。以下示例通过 Slide.writeAsSvg 以三种光栅化分辨率导出其第一张幻灯片。所有其他导出设置保持相同,以便进行对比。

const slides = require("aspose.slides.via.java");
const java = require("java");

const presentation = new slides.Presentation("metafile-rasterization.pptx");
try {
    for (const dpi of [72, 150, 300]) {
        const svgOptions = new slides.SVGOptions();
        svgOptions.setMetafileRasterizationDpi(dpi);
        svgOptions.setPicturesCompression(slides.PicturesCompression.Dpi330);
        svgOptions.setJpegQuality(90);

        const outputFileName = `slide-${dpi}.svg`;
        const svgStream = java.newInstanceSync("java.io.FileOutputStream", outputFileName);
        try {
            presentation.getSlides().get_Item(0).writeAsSvg(svgStream, svgOptions);
        } finally {
            svgStream.close();
        }
    }
} finally {
    presentation.dispose();
}

在输出中,EMF 变成了每个 SVG 中的一个嵌入 PNG。以下测量数据对应所提供的演示文稿;它们并不是将 DPI 转换为像素尺寸的通用公式。

元文件光栅化 DPI 嵌入 PNG 尺寸 SVG 文件大小 (字节)
72 1196 × 596 170,221
150 2492 × 1242 423,149
300 4983 × 2483 1,027,169

下面的对比展示了在相同放大显示尺寸下嵌入光栅的相同区域。对角线在 72 DPI 时出现可见像素化,而在更高分辨率下边缘更平滑,同时 SVG 文件体积增大。提升该值并不一定能改善所有细节或改变每个导出图像。

72、150 和 300 DPI 放大对角线细节

SVGOptions.setPicturesCompression 控制图像分辨率降低,并可能掩盖更改元文件光栅化分辨率的效果。SVGOptions.setJpegQuality 控制 JPEG 编码质量,而非元文件光栅化分辨率。

为形状和文本分配稳定的 ID

将格式化控制器传递给 SVGOptions.setShapeFormattingController 可为每个 SVG 形状设置 SvgShape.setId。同时处理文本跨度的控制器可以在文本 tspan 元素上设置 SvgTSpan.setId 值。

以下控制器使用 Shape.getOfficeInteropShapeId,该 ID 在形状生命周期内保持稳定,并使用可重复计数器为其文本跨度生成 ID。这样生成的 ID 适用于对未更改的演示文稿进行后处理。

const slides = require("aspose.slides.via.java");
const java = require("java");

class StableSvgIdController {
    constructor() {
        this.currentShapeId = "";
        this.textSpanIndex = 0;
    }

    formatShape(svgShape, shape) {
        this.currentShapeId = `shape-${shape.getOfficeInteropShapeId()}`;
        this.textSpanIndex = 0;
        svgShape.setId(this.currentShapeId);
    }

    formatText(svgTSpan, portion, textFrame) {
        const textSpanId = `${this.currentShapeId}-text-${this.textSpanIndex++}`;
        svgTSpan.setId(textSpanId);
    }

    createProxy() {
        const controller = this;
        const interfaceName = "com.aspose.slides.ISvgShapeAndTextFormattingController";
        const proxyMethods = {
            formatShape(svgShape, shape) {
                controller.formatShape(svgShape, shape);
            },
            formatText(svgTSpan, portion, textFrame) {
                controller.formatText(svgTSpan, portion, textFrame);
            }
        };
        return java.newProxy(interfaceName, proxyMethods);
    }
}

const presentation = new slides.Presentation("presentation.pptx");
try {
    const svgOptions = new slides.SVGOptions();
    const stableSvgIdController = new StableSvgIdController();
    const controllerProxy = stableSvgIdController.createProxy();
    svgOptions.setShapeFormattingController(controllerProxy);

    const slide = presentation.getSlides().get_Item(0);
    const svgStream = java.newInstanceSync(
        "java.io.FileOutputStream",
        "slide-with-stable-ids.svg"
    );
    try {
        slide.writeAsSvg(svgStream, svgOptions);
    } finally {
        svgStream.close();
    }
} finally {
    presentation.dispose();
}

添加 SVG 事件处理程序

在格式化控制器中,使用 SvgShape.setEventHandler 并传入 SvgEvent 值可为导出的形状添加 JavaScript 事件处理程序。通过 SVGOptions.setShapeFormattingController 分配该控制器,并在承载结果的页面或 SVG 文档中定义 JavaScript 函数。

const slides = require("aspose.slides.via.java");
const java = require("java");

class SvgEventController {
    formatShape(svgShape, shape) {
        if (shape.getName() === "ActionButton") {
            svgShape.setId("action-button");
            svgShape.setEventHandler(
                slides.SvgEvent.OnClick,
                "handleShapeClick(event)"
            );
        }
    }

    createProxy() {
        const controller = this;
        const interfaceName = "com.aspose.slides.ISvgShapeFormattingController";
        const proxyMethods = {
            formatShape(svgShape, shape) {
                controller.formatShape(svgShape, shape);
            }
        };
        return java.newProxy(interfaceName, proxyMethods);
    }
}

const presentation = new slides.Presentation("presentation.pptx");
try {
    const svgOptions = new slides.SVGOptions();
    const svgEventController = new SvgEventController();
    const controllerProxy = svgEventController.createProxy();
    svgOptions.setShapeFormattingController(controllerProxy);

    const slide = presentation.getSlides().get_Item(0);
    const svgStream = java.newInstanceSync("java.io.FileOutputStream", "interactive-slide.svg");
    try {
        slide.writeAsSvg(svgStream, svgOptions);
    } finally {
        svgStream.close();
    }
} finally {
    presentation.dispose();
}

宿主页面可以定义处理程序引用的 JavaScript 函数。分配 ID 和事件处理程序可支持幻灯片查看器、可访问性增强以及其他交互式 SVG 工作流。

常见问题

何时应该使用 SVGOptions.setVectorizeText 而不是 SvgExternalFontsHandling.Vectorize?

当所有文本必须不依赖字体时,请使用 SVGOptions.setVectorizeText。当仅需将使用外部字体的文本转换为图形时,请使用 SvgExternalFontsHandling.Vectorize。

什么是使 SVG 更小的最佳方法?

首先压缩嵌入的图片,删除裁剪的图像区域,并在目标环境能够提供时选择链接的字体文件。对结果进行测试,因为降低图像分辨率、降低 JPEG 质量以及矢量化文本各自都有不同的质量和体积权衡。

导出后我可以修改 SVG 元素吗?

可以。通过格式化控制器分配 ID,然后在后处理工具或浏览器脚本中选择相应的 SVG 元素。