Skip to content

Commit

Permalink
SVG export postprocessor (#454)
Browse files Browse the repository at this point in the history
* SVG export postprocessor

Signed-off-by: Guillaume Fontorbe <[email protected]>

* Create svg export options object

Signed-off-by: Guillaume Fontorbe <[email protected]>

---------

Signed-off-by: Guillaume Fontorbe <[email protected]>
  • Loading branch information
gfontorbe authored May 28, 2024
1 parent 697b39f commit e36c87c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 24 deletions.
3 changes: 2 additions & 1 deletion packages/sprotty/src/base/types.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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'),
Expand Down
12 changes: 9 additions & 3 deletions packages/sprotty/src/features/export/export.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -39,16 +39,22 @@ export class ExportSvgKeyListener extends KeyListener {
}
}

export interface ExportSvgOptions {
skipCopyStyles?: boolean
}

export interface RequestExportSvgAction extends RequestAction<ExportSvgAction> {
kind: typeof RequestExportSvgAction.KIND
options?: ExportSvgOptions
}
export namespace RequestExportSvgAction {
export const KIND = 'requestExportSvg';

export function create(): RequestExportSvgAction {
export function create(options: ExportSvgOptions = {}): RequestExportSvgAction {
return {
kind: KIND,
requestId: generateRequestId()
requestId: generateRequestId(),
options
};
}
}
Expand Down
21 changes: 21 additions & 0 deletions packages/sprotty/src/features/export/svg-export-postprocessor.ts
Original file line number Diff line number Diff line change
@@ -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;
}
59 changes: 40 additions & 19 deletions packages/sprotty/src/features/export/svg-exporter.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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, ExportSvgOptions } 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;
options?: ExportSvgOptions;
}
export namespace ExportSvgAction {
export const KIND = 'exportSvg';

export function create(svg: string, requestId: string): ExportSvgAction {
export function create(svg: string, requestId: string, options?: ExportSvgOptions): ExportSvgAction {
return {
kind: KIND,
svg,
responseId: requestId
responseId: requestId,
options
};
}
}
Expand All @@ -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<ExportSvgAction>): void {
export(root: SModelRootImpl, request?: RequestExportSvgAction): void {
if (typeof document !== 'undefined') {
const hiddenDiv = document.getElementById(this.options.hiddenDiv);
if (hiddenDiv === null) {
Expand All @@ -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?.options || {}, request);
this.actionDispatcher.dispatch(ExportSvgAction.create(svg, request ? request.requestId : '', request?.options));
}
}

protected createSvg(svgElementOrig: SVGSVGElement, root: SModelRootImpl): string {
protected createSvg(svgElementOrig: SVGSVGElement, root: SModelRootImpl, options?: ExportSvgOptions, cause?: Action): string {
const serializer = new XMLSerializer();
const svgCopy = serializer.serializeToString(svgElementOrig);
const iframe: HTMLIFrameElement = document.createElement('iframe');
Expand All @@ -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 (!options?.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;
}

Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion packages/sprotty/src/index.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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';
Expand Down

0 comments on commit e36c87c

Please sign in to comment.