From d5cc4f6af774c51c76665d65d1091e2b28082adb Mon Sep 17 00:00:00 2001 From: Tim Svensson Date: Wed, 1 Jun 2022 17:00:10 +0200 Subject: [PATCH 1/3] start binaryimage rotation --- .../src/abstract-document-exporters/pdf/render-image.ts | 6 ++++++ .../abstract-image/src/exporters/react-svg-export-image.tsx | 5 +++++ packages/abstract-image/src/exporters/svg-export-image.ts | 3 +++ packages/abstract-image/src/model/component.ts | 5 ++++- 4 files changed, 18 insertions(+), 1 deletion(-) 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 = `${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..8c1289bc 100644 --- a/packages/abstract-image/src/exporters/react-svg-export-image.tsx +++ b/packages/abstract-image/src/exporters/react-svg-export-image.tsx @@ -98,6 +98,11 @@ function _visit(key: string, component: AbstractImage.Component): Array, ]; diff --git a/packages/abstract-image/src/exporters/svg-export-image.ts b/packages/abstract-image/src/exporters/svg-export-image.ts index c9dc869e..317fb657 100644 --- a/packages/abstract-image/src/exporters/svg-export-image.ts +++ b/packages/abstract-image/src/exporters/svg-export-image.ts @@ -36,6 +36,9 @@ 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: component.rotation + ? `transform: rotateX(${component.rotation.x}deg) rotateY(${component.rotation.y}deg) rotateZ(${component.rotation.z}deg)` + : "", }, [] ); diff --git a/packages/abstract-image/src/model/component.ts b/packages/abstract-image/src/model/component.ts index 7b3982c7..3c66fae1 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.Point & { readonly z: number }) | 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, }; } From 688b164db04aa1717a4caed05b3ff1432ed28ac0 Mon Sep 17 00:00:00 2001 From: Tim Svensson Date: Tue, 24 Jan 2023 10:40:07 +0100 Subject: [PATCH 2/3] make rotation optional --- packages/abstract-image/src/model/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/abstract-image/src/model/component.ts b/packages/abstract-image/src/model/component.ts index 3c66fae1..c75f2780 100644 --- a/packages/abstract-image/src/model/component.ts +++ b/packages/abstract-image/src/model/component.ts @@ -31,7 +31,7 @@ export interface BinaryImage { readonly format: BinaryFormat; readonly data: ImageData; readonly id: string | undefined; - readonly rotation: (Point.Point & { readonly z: number }) | undefined; + readonly rotation?: (Point.Point & { readonly z: number }) | undefined; } export type ImageData = ImageBytes | ImageUrl; From b0e482b5148899bef247324886ee632e8716e1c9 Mon Sep 17 00:00:00 2001 From: Tim Svensson Date: Fri, 24 Feb 2023 11:25:25 +0100 Subject: [PATCH 3/3] 3d rotation all components and svg/react-svg exporters --- .../src/exporters/react-svg-export-image.tsx | 58 +++++++++++-------- .../src/exporters/svg-export-image.ts | 52 ++++++++++------- .../abstract-image/src/model/component.ts | 32 +++++++--- packages/abstract-image/src/model/point.ts | 10 +++- 4 files changed, 98 insertions(+), 54 deletions(-) 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 8c1289bc..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,11 +99,7 @@ function _visit(key: string, component: AbstractImage.Component): Array, ]; @@ -118,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> = []; @@ -194,6 +191,7 @@ function _visit(key: string, component: AbstractImage.Component): Array, ]; case "polyline": @@ -207,6 +205,7 @@ function _visit(key: string, component: AbstractImage.Component): Array, ]; case "polygon": @@ -221,6 +220,7 @@ function _visit(key: string, component: AbstractImage.Component): Array, ]; case "rectangle": @@ -237,6 +237,7 @@ function _visit(key: string, component: AbstractImage.Component): Array, ]; default: @@ -285,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 317fb657..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,9 +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: component.rotation - ? `transform: rotateX(${component.rotation.x}deg) rotateY(${component.rotation.y}deg) rotateZ(${component.rotation.z}deg)` - : "", + style: rotationStyle(component.rotation), }, [] ); @@ -55,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), }, [] ); @@ -67,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), }, [] ); @@ -74,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") : []; @@ -113,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 @@ -166,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), }, [] ); @@ -179,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), }, [] ); @@ -195,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), }, [] ); @@ -294,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 c75f2780..d3d93183 100644 --- a/packages/abstract-image/src/model/component.ts +++ b/packages/abstract-image/src/model/component.ts @@ -31,7 +31,7 @@ export interface BinaryImage { readonly format: BinaryFormat; readonly data: ImageData; readonly id: string | undefined; - readonly rotation?: (Point.Point & { readonly z: number }) | undefined; + readonly rotation?: Point.Point3D | undefined; } export type ImageData = ImageBytes | ImageUrl; @@ -73,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( @@ -81,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", @@ -91,6 +93,7 @@ export function createEllipse( strokeThickness: strokeThickness, fillColor: fillColor, id: id, + rotation, }; } @@ -101,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( @@ -108,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", @@ -117,6 +122,7 @@ export function createLine( strokeColor: strokeColor, strokeThickness: strokeThickness, id: id, + rotation, }; } @@ -126,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", @@ -140,6 +148,7 @@ export function createPolyLine( strokeColor: strokeColor, strokeThickness: strokeThickness, id: id, + rotation, }; } @@ -150,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( @@ -157,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", @@ -166,6 +177,7 @@ export function createPolygon( strokeThickness: strokeThickness, fillColor: fillColor, id: id, + rotation, }; } @@ -177,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( @@ -185,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", @@ -195,6 +209,7 @@ export function createRectangle( strokeThickness: strokeThickness, fillColor: fillColor, id: id, + rotation, }; } @@ -228,6 +243,7 @@ export interface Text { readonly strokeThickness: number; readonly strokeColor: Color.Color; readonly italic: boolean; + readonly rotation?: Point.Point3D | undefined; } export function createText( @@ -243,7 +259,8 @@ export function createText( verticalGrowthDirection: GrowthDirection, strokeThickness: number, strokeColor: Color.Color, - italic: boolean + italic: boolean, + rotation?: Point.Point3D ): Text { return { type: "text", @@ -260,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 });