From eaa0652d18340efa3fd57d7f079be26ca21847c3 Mon Sep 17 00:00:00 2001 From: Guillaume Fontorbe Date: Mon, 13 May 2024 16:42:34 +0200 Subject: [PATCH] SVG export postprocessor Signed-off-by: Guillaume Fontorbe --- packages/sprotty/src/base/types.ts | 3 +- .../sprotty/src/features/export/export.ts | 8 ++- .../export/svg-export-postprocessor.ts | 21 +++++++ .../src/features/export/svg-exporter.ts | 59 +++++++++++++------ packages/sprotty/src/index.ts | 3 +- 5 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 packages/sprotty/src/features/export/svg-export-postprocessor.ts diff --git a/packages/sprotty/src/base/types.ts b/packages/sprotty/src/base/types.ts index 191d2572..12b5d78d 100644 --- a/packages/sprotty/src/base/types.ts +++ b/packages/sprotty/src/base/types.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2018 TypeFox and others. + * Copyright (c) 2017-2024 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -66,6 +66,7 @@ export const TYPES = { SModelRegistry: Symbol('SModelRegistry'), ISnapper: Symbol('ISnapper'), SvgExporter: Symbol('SvgExporter'), + ISvgExportPostprocessor: Symbol('ISvgExportPostprocessor'), IUIExtension: Symbol('IUIExtension'), UIExtensionRegistry: Symbol('UIExtensionRegistry'), IVNodePostprocessor: Symbol('IVNodePostprocessor'), diff --git a/packages/sprotty/src/features/export/export.ts b/packages/sprotty/src/features/export/export.ts index 8ed389ef..44fe9bca 100644 --- a/packages/sprotty/src/features/export/export.ts +++ b/packages/sprotty/src/features/export/export.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2018 TypeFox and others. + * Copyright (c) 2017-2024 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -41,14 +41,16 @@ export class ExportSvgKeyListener extends KeyListener { export interface RequestExportSvgAction extends RequestAction { kind: typeof RequestExportSvgAction.KIND + skipCopyStyles?: boolean } export namespace RequestExportSvgAction { export const KIND = 'requestExportSvg'; - export function create(): RequestExportSvgAction { + export function create(skipCopyStyles?: boolean): RequestExportSvgAction { return { kind: KIND, - requestId: generateRequestId() + requestId: generateRequestId(), + skipCopyStyles }; } } diff --git a/packages/sprotty/src/features/export/svg-export-postprocessor.ts b/packages/sprotty/src/features/export/svg-export-postprocessor.ts new file mode 100644 index 00000000..9aa8318e --- /dev/null +++ b/packages/sprotty/src/features/export/svg-export-postprocessor.ts @@ -0,0 +1,21 @@ +/******************************************************************************** + * Copyright (c) 2024 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { Action } from "sprotty-protocol/lib/actions"; + +export interface ISvgExportPostProcessor { + postUpdate(element: SVGSVGElement, cause?: Action): void; +} diff --git a/packages/sprotty/src/features/export/svg-exporter.ts b/packages/sprotty/src/features/export/svg-exporter.ts index 01ac7679..75a03597 100644 --- a/packages/sprotty/src/features/export/svg-exporter.ts +++ b/packages/sprotty/src/features/export/svg-exporter.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2018 TypeFox and others. + * Copyright (c) 2017-2024 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -14,29 +14,33 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from "inversify"; -import { RequestAction, ResponseAction } from 'sprotty-protocol/lib/actions'; +import { inject, injectable, multiInject, optional } from "inversify"; +import { Action, ResponseAction } from 'sprotty-protocol/lib/actions'; import { Bounds } from 'sprotty-protocol/lib/utils/geometry'; -import { ViewerOptions } from '../../base/views/viewer-options'; -import { isBoundsAware } from '../bounds/model'; import { ActionDispatcher } from '../../base/actions/action-dispatcher'; -import { TYPES } from '../../base/types'; import { SModelRootImpl } from '../../base/model/smodel'; +import { TYPES } from '../../base/types'; +import { ViewerOptions } from '../../base/views/viewer-options'; import { ILogger } from '../../utils/logging'; +import { isBoundsAware } from '../bounds/model'; +import { RequestExportSvgAction } from "./export"; +import { ISvgExportPostProcessor } from "./svg-export-postprocessor"; export interface ExportSvgAction extends ResponseAction { kind: typeof ExportSvgAction.KIND; - svg: string - responseId: string + svg: string; + responseId: string; + skipCopyStyles?: boolean; } export namespace ExportSvgAction { export const KIND = 'exportSvg'; - export function create(svg: string, requestId: string): ExportSvgAction { + export function create(svg: string, requestId: string, skipCopyStyles: boolean = false): ExportSvgAction { return { kind: KIND, svg, - responseId: requestId + responseId: requestId, + skipCopyStyles }; } } @@ -47,8 +51,9 @@ export class SvgExporter { @inject(TYPES.ViewerOptions) protected options: ViewerOptions; @inject(TYPES.IActionDispatcher) protected actionDispatcher: ActionDispatcher; @inject(TYPES.ILogger) protected log: ILogger; + @multiInject(TYPES.ISvgExportPostprocessor) @optional() protected postprocessors: ISvgExportPostProcessor[] = []; - export(root: SModelRootImpl, request?: RequestAction): void { + export(root: SModelRootImpl, request?: RequestExportSvgAction): void { if (typeof document !== 'undefined') { const hiddenDiv = document.getElementById(this.options.hiddenDiv); if (hiddenDiv === null) { @@ -62,12 +67,12 @@ export class SvgExporter { this.log.warn(this, `No svg element found in ${this.options.hiddenDiv} div. Nothing to export.`); return; } - const svg = this.createSvg(svgElement, root); - this.actionDispatcher.dispatch(ExportSvgAction.create(svg, request ? request.requestId : '')); + const svg = this.createSvg(svgElement, root, request?.skipCopyStyles, request); + this.actionDispatcher.dispatch(ExportSvgAction.create(svg, request ? request.requestId : '', request?.skipCopyStyles)); } } - protected createSvg(svgElementOrig: SVGSVGElement, root: SModelRootImpl): string { + protected createSvg(svgElementOrig: SVGSVGElement, root: SModelRootImpl, skipCopyStyles: boolean = false, cause?: Action): string { const serializer = new XMLSerializer(); const svgCopy = serializer.serializeToString(svgElementOrig); const iframe: HTMLIFrameElement = document.createElement('iframe'); @@ -80,13 +85,24 @@ export class SvgExporter { docCopy.close(); const svgElementNew = docCopy.querySelector('svg')!; svgElementNew.removeAttribute('opacity'); - // inline-size copied from sprotty-hidden svg shrinks the svg so it is not visible. - this.copyStyles(svgElementOrig, svgElementNew, ['width', 'height', 'opacity', 'inline-size']); + if (!skipCopyStyles) { + // inline-size copied from sprotty-hidden svg shrinks the svg so it is not visible. + this.copyStyles(svgElementOrig, svgElementNew, ['width', 'height', 'opacity', 'inline-size']); + } svgElementNew.setAttribute('version', '1.1'); - const bounds = this.getBounds(root); + const bounds = this.getBounds(root, docCopy); + svgElementNew.setAttribute('viewBox', `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`); + svgElementNew.setAttribute('width', `${bounds.width}`); + svgElementNew.setAttribute('height', `${bounds.height}`); + + this.postprocessors.forEach(postprocessor => { + postprocessor.postUpdate(svgElementNew, cause); + }); + const svgCode = serializer.serializeToString(svgElementNew); document.body.removeChild(iframe); + return svgCode; } @@ -114,8 +130,13 @@ export class SvgExporter { } } - protected getBounds(root: SModelRootImpl) { - const allBounds: Bounds[] = [ Bounds.EMPTY ]; + protected getBounds(root: SModelRootImpl, document: Document) { + const svgElement = document.querySelector('svg'); + if (svgElement) { + return svgElement.getBBox(); + } + + const allBounds: Bounds[] = [Bounds.EMPTY]; root.children.forEach(element => { if (isBoundsAware(element)) { allBounds.push(element.bounds); diff --git a/packages/sprotty/src/index.ts b/packages/sprotty/src/index.ts index 89ac96eb..339c0229 100644 --- a/packages/sprotty/src/index.ts +++ b/packages/sprotty/src/index.ts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2017-2018 TypeFox and others. + * Copyright (c) 2017-2024 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -98,6 +98,7 @@ export * from './features/expand/views'; export * from './features/export/export'; export * from './features/export/model'; export * from './features/export/svg-exporter'; +export * from './features/export/svg-export-postprocessor'; export * from './features/fade/fade'; export * from './features/fade/model';