diff --git a/packages/abstract-document/src/abstract-document-exporters/pdf/render-image.ts b/packages/abstract-document/src/abstract-document-exporters/pdf/render-image.ts
index 2ba6655a..623fbaeb 100644
--- a/packages/abstract-document/src/abstract-document-exporters/pdf/render-image.ts
+++ b/packages/abstract-document/src/abstract-document-exporters/pdf/render-image.ts
@@ -100,6 +100,12 @@ function abstractComponentToPdf(
}
});
+ // svgToPdfKit doesn't seem to have great support currently, need to find workaround.
+
+ // if (component.rotation) {
+ // svgUpdated = ``;
+ // }
+
const imageWidth = component.bottomRight.x - component.topLeft.x;
const imageHeight = component.bottomRight.y - component.topLeft.y;
svgToPdfKit(pdf, svgUpdated, component.topLeft.x, component.topLeft.y, {
diff --git a/packages/abstract-image/src/exporters/react-svg-export-image.tsx b/packages/abstract-image/src/exporters/react-svg-export-image.tsx
index 5c611077..7e355421 100644
--- a/packages/abstract-image/src/exporters/react-svg-export-image.tsx
+++ b/packages/abstract-image/src/exporters/react-svg-export-image.tsx
@@ -3,6 +3,7 @@ import * as B64 from "base64-js";
import * as React from "react";
import * as AbstractImage from "../model/index";
import { TextEncoder } from "util";
+import { Point3D } from "../model/index";
export interface ReactSvgCallbacks {
readonly onClick?: MouseCallback;
@@ -98,6 +99,7 @@ function _visit(key: string, component: AbstractImage.Component): Array,
];
@@ -113,48 +115,48 @@ function _visit(key: string, component: AbstractImage.Component): Array,
];
case "text":
if (!component.text) {
return [];
}
- const lineHeight = component.fontSize;
- const shadowStyle = {
+ const baseStyle = {
textAnchor: getTextAnchor(component.horizontalGrowthDirection),
fontSize: component.fontSize.toString() + "px",
fontWeight: component.fontWeight === "mediumBold" ? "bold" : component.fontWeight,
fontFamily: component.fontFamily,
+ };
+
+ const shadowStyle = {
+ ...baseStyle,
stroke: colorToRgb(component.strokeColor),
strokeWidth: component.strokeThickness,
};
- const style = {
- textAnchor: getTextAnchor(component.horizontalGrowthDirection),
- fontSize: component.fontSize.toString() + "px",
- fontWeight: component.fontWeight === "mediumBold" ? "bold" : component.fontWeight,
- fontFamily: component.fontFamily,
- fill: colorToRgb(component.textColor),
- };
- const dy = getBaselineAdjustment(component.verticalGrowthDirection);
+ const style = { ...baseStyle, fill: colorToRgb(component.textColor) };
- const transform =
- "rotate(" +
- component.clockwiseRotationDegrees.toString() +
- " " +
- component.position.x.toString() +
- " " +
- component.position.y.toString() +
- ")";
+ // component.clockwiseRotationDegrees is legacy
+ const transform = component.rotation
+ ? rotationTransform(component.rotation)
+ : "rotate(" +
+ component.clockwiseRotationDegrees.toString() +
+ " " +
+ component.position.x.toString() +
+ " " +
+ component.position.y.toString() +
+ ")";
+ const dy = getBaselineAdjustment(component.verticalGrowthDirection);
const lines: Array = component.text !== null ? component.text.split("\n") : [];
const tSpans = lines.map((t) =>
renderLine(
t,
component.position.x,
- component.position.y + (lines.indexOf(t) + dy) * lineHeight,
+ component.position.y + (lines.indexOf(t) + dy) * component.fontSize,
component.fontSize,
- lineHeight
+ component.fontSize
)
);
let cs: Array> = [];
@@ -189,6 +191,7 @@ function _visit(key: string, component: AbstractImage.Component): Array,
];
case "polyline":
@@ -202,6 +205,7 @@ function _visit(key: string, component: AbstractImage.Component): Array,
];
case "polygon":
@@ -216,6 +220,7 @@ function _visit(key: string, component: AbstractImage.Component): Array,
];
case "rectangle":
@@ -232,6 +237,7 @@ function _visit(key: string, component: AbstractImage.Component): Array,
];
default:
@@ -280,6 +286,13 @@ function renderLine(text: string, x: number, y: number, fontSize: number, lineHe
);
}
+const rotationStyle = (rotation: Point3D): { readonly style: React.CSSProperties } => ({
+ style: { transform: rotationTransform(rotation) },
+});
+
+const rotationTransform = (rotation: Point3D): string =>
+ `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg) rotateZ(${rotation.z}deg)`;
+
function getBaselineAdjustment(d: AbstractImage.GrowthDirection): number {
if (d === "up") {
return 0.0;
diff --git a/packages/abstract-image/src/exporters/svg-export-image.ts b/packages/abstract-image/src/exporters/svg-export-image.ts
index c9dc869e..64ca1d10 100644
--- a/packages/abstract-image/src/exporters/svg-export-image.ts
+++ b/packages/abstract-image/src/exporters/svg-export-image.ts
@@ -1,5 +1,6 @@
import * as B64 from "base64-js";
import * as AbstractImage from "../model/index";
+import { Point3D } from "../model/index";
export function createSVG(image: AbstractImage.AbstractImage, pixelWidth?: number, pixelHeight?: number): string {
const imageElements = image.components.map((c: AbstractImage.Component) => abstractComponentToSVG(c));
@@ -36,6 +37,7 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
width: (component.bottomRight.x - component.topLeft.x).toString(),
height: (component.bottomRight.y - component.topLeft.y).toString(),
href: url,
+ style: rotationStyle(component.rotation),
},
[]
);
@@ -52,6 +54,7 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
stroke: colorToRgb(component.strokeColor),
strokeOpacity: colorToOpacity(component.strokeColor),
strokeWidth: component.strokeThickness.toString(),
+ style: rotationStyle(component.rotation),
},
[]
);
@@ -64,6 +67,7 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
stroke: colorToRgb(component.strokeColor),
strokeOpacity: colorToOpacity(component.strokeColor),
strokeWidth: component.strokeThickness.toString(),
+ style: rotationStyle(component.rotation),
},
[]
);
@@ -71,37 +75,37 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
if (!component.text) {
return "";
}
- const lineHeight = component.fontSize;
- const shadowStyle = {
+ const baseStyle = {
textAnchor: getTextAnchor(component.horizontalGrowthDirection),
fontSize: component.fontSize.toString() + "px",
fontWeight: component.fontWeight,
fontFamily: component.fontFamily,
+ };
+
+ const shadowStyle = {
+ ...baseStyle,
stroke: colorToRgb(component.strokeColor),
strokeOpacity: colorToOpacity(component.strokeColor),
strokeWidth: component.strokeThickness.toString() + "px",
};
-
const style = {
- textAnchor: getTextAnchor(component.horizontalGrowthDirection),
- fontSize: component.fontSize.toString() + "px",
- fontWeight: component.fontWeight,
- fontFamily: component.fontFamily,
+ ...baseStyle,
fill: colorToRgb(component.textColor),
fillOpacity: colorToOpacity(component.textColor),
};
const dy = getBaselineAdjustment(component.verticalGrowthDirection);
-
- const transform =
- "rotate(" +
- component.clockwiseRotationDegrees.toString() +
- " " +
- component.position.x.toString() +
- " " +
- component.position.y.toString() +
- ")";
+ // component.clockwiseRotationDegrees is legacy
+ const transform = component.rotation
+ ? rotationTransform(component.rotation)
+ : "rotate(" +
+ component.clockwiseRotationDegrees.toString() +
+ " " +
+ component.position.x.toString() +
+ " " +
+ component.position.y.toString() +
+ ")";
const lines: Array = component.text !== null ? component.text.split("\n") : [];
@@ -110,8 +114,8 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
"tspan",
{
x: component.position.x.toString(),
- y: (component.position.y + (lines.indexOf(t) + dy) * lineHeight).toString(),
- height: lineHeight.toString() + "px",
+ y: (component.position.y + (lines.indexOf(t) + dy) * component.fontSize).toString(),
+ height: component.fontSize.toString() + "px",
},
[
t
@@ -163,6 +167,7 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
strokeWidth: component.strokeThickness.toString(),
fill: colorToRgb(component.fillColor),
fillOpacity: colorToOpacity(component.fillColor),
+ style: rotationStyle(component.rotation),
},
[]
);
@@ -176,6 +181,7 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
strokeWidth: component.strokeThickness.toString(),
fill: colorToRgb(component.fillColor),
fillOpacity: colorToOpacity(component.fillColor),
+ style: rotationStyle(component.rotation),
},
[]
);
@@ -192,6 +198,7 @@ function abstractComponentToSVG(component: AbstractImage.Component): string {
strokeWidth: component.strokeThickness.toString(),
fill: colorToRgb(component.fillColor),
fillOpacity: colorToOpacity(component.fillColor),
+ style: rotationStyle(component.rotation),
},
[]
);
@@ -291,6 +298,12 @@ function colorToOpacity(color: AbstractImage.Color): string {
return (color.a / 255).toString();
}
+const rotationStyle = (rotation: Point3D | undefined): string =>
+ rotation ? `transform: ${rotationTransform(rotation)}` : "";
+
+const rotationTransform = (rotation: Point3D): string =>
+ `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg) rotateZ(${rotation.z}deg)`;
+
function getImageUrl(format: AbstractImage.BinaryFormat, data: AbstractImage.ImageData): string {
if (data.type === "url") {
return data.url;
diff --git a/packages/abstract-image/src/model/component.ts b/packages/abstract-image/src/model/component.ts
index 7b3982c7..d3d93183 100644
--- a/packages/abstract-image/src/model/component.ts
+++ b/packages/abstract-image/src/model/component.ts
@@ -31,6 +31,7 @@ export interface BinaryImage {
readonly format: BinaryFormat;
readonly data: ImageData;
readonly id: string | undefined;
+ readonly rotation?: Point.Point3D | undefined;
}
export type ImageData = ImageBytes | ImageUrl;
@@ -50,7 +51,8 @@ export function createBinaryImage(
bottomRight: Point.Point,
format: BinaryFormat,
data: ImageData,
- id?: string
+ id?: string,
+ rotation?: Point.Point & { readonly z: number }
): BinaryImage {
return {
type: "binaryimage",
@@ -59,6 +61,7 @@ export function createBinaryImage(
format: format,
data: data,
id: id,
+ rotation,
};
}
@@ -70,6 +73,7 @@ export interface Ellipse {
readonly strokeThickness: number;
readonly fillColor: Color.Color;
readonly id: string | undefined;
+ readonly rotation?: Point.Point3D | undefined;
}
export function createEllipse(
@@ -78,7 +82,8 @@ export function createEllipse(
strokeColor: Color.Color,
strokeThickness: number,
fillColor: Color.Color,
- id?: string
+ id?: string,
+ rotation?: Point.Point3D
): Ellipse {
return {
type: "ellipse",
@@ -88,6 +93,7 @@ export function createEllipse(
strokeThickness: strokeThickness,
fillColor: fillColor,
id: id,
+ rotation,
};
}
@@ -98,6 +104,7 @@ export interface Line {
readonly strokeColor: Color.Color;
readonly strokeThickness: number;
readonly id: string | undefined;
+ readonly rotation?: Point.Point3D | undefined;
}
export function createLine(
@@ -105,7 +112,8 @@ export function createLine(
end: Point.Point,
strokeColor: Color.Color,
strokeThickness: number,
- id?: string
+ id?: string,
+ rotation?: Point.Point3D
): Line {
return {
type: "line",
@@ -114,6 +122,7 @@ export function createLine(
strokeColor: strokeColor,
strokeThickness: strokeThickness,
id: id,
+ rotation,
};
}
@@ -123,13 +132,15 @@ export interface PolyLine {
readonly strokeColor: Color.Color;
readonly strokeThickness: number;
readonly id: string | undefined;
+ readonly rotation?: Point.Point3D | undefined;
}
export function createPolyLine(
points: Array,
strokeColor: Color.Color,
strokeThickness: number,
- id?: string
+ id?: string,
+ rotation?: Point.Point3D
): PolyLine {
return {
type: "polyline",
@@ -137,6 +148,7 @@ export function createPolyLine(
strokeColor: strokeColor,
strokeThickness: strokeThickness,
id: id,
+ rotation,
};
}
@@ -147,6 +159,7 @@ export interface Polygon {
readonly strokeThickness: number;
readonly fillColor: Color.Color;
readonly id: string | undefined;
+ readonly rotation?: Point.Point3D | undefined;
}
export function createPolygon(
@@ -154,7 +167,8 @@ export function createPolygon(
strokeColor: Color.Color,
strokeThickness: number,
fillColor: Color.Color,
- id?: string
+ id?: string,
+ rotation?: Point.Point3D
): Polygon {
return {
type: "polygon",
@@ -163,6 +177,7 @@ export function createPolygon(
strokeThickness: strokeThickness,
fillColor: fillColor,
id: id,
+ rotation,
};
}
@@ -174,6 +189,7 @@ export interface Rectangle {
readonly strokeThickness: number;
readonly fillColor: Color.Color;
readonly id: string | undefined;
+ readonly rotation?: Point.Point3D | undefined;
}
export function createRectangle(
@@ -182,7 +198,8 @@ export function createRectangle(
strokeColor: Color.Color,
strokeThickness: number,
fillColor: Color.Color,
- id?: string
+ id?: string,
+ rotation?: Point.Point3D
): Rectangle {
return {
type: "rectangle",
@@ -192,6 +209,7 @@ export function createRectangle(
strokeThickness: strokeThickness,
fillColor: fillColor,
id: id,
+ rotation,
};
}
@@ -225,6 +243,7 @@ export interface Text {
readonly strokeThickness: number;
readonly strokeColor: Color.Color;
readonly italic: boolean;
+ readonly rotation?: Point.Point3D | undefined;
}
export function createText(
@@ -240,7 +259,8 @@ export function createText(
verticalGrowthDirection: GrowthDirection,
strokeThickness: number,
strokeColor: Color.Color,
- italic: boolean
+ italic: boolean,
+ rotation?: Point.Point3D
): Text {
return {
type: "text",
@@ -257,6 +277,7 @@ export function createText(
strokeThickness: strokeThickness,
strokeColor: strokeColor,
italic: italic,
+ rotation,
};
}
diff --git a/packages/abstract-image/src/model/point.ts b/packages/abstract-image/src/model/point.ts
index 0ff4f9be..ef137f15 100644
--- a/packages/abstract-image/src/model/point.ts
+++ b/packages/abstract-image/src/model/point.ts
@@ -6,6 +6,14 @@ export interface Point {
export function createPoint(x: number, y: number): Point {
return {
x: x,
- y: y
+ y: y,
};
}
+
+export interface Point3D {
+ readonly x: number;
+ readonly y: number;
+ readonly z: number;
+}
+
+export const createPoint3D = (x: number, y: number, z: number): Point3D => ({ x, y, z });