Skip to content

Commit 680c9d6

Browse files
sunflatmugi-unohidakatsuya
committed
Add utility modules
Co-authored-by: mugi-uno <mugi.uno@gmail.com> Co-authored-by: Katsuya HIDAKA <hidakatsuya@gmail.com>
1 parent ff49df4 commit 680c9d6

7 files changed

Lines changed: 424 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { calcPlus, calcMinus, calcDiv } from './strict-calculator';
2+
import { BoundingPoints, BoundingBox, Coords, EllipseItemBounds } from '@/types';
3+
4+
export class BoundsTransformer {
5+
bounds: BoundingPoints;
6+
7+
static fromBBox (bBox: BoundingBox) {
8+
return new BoundsTransformer({
9+
x1: bBox.x,
10+
y1: bBox.y,
11+
x2: calcPlus(bBox.x, bBox.width),
12+
y2: calcPlus(bBox.y, bBox.height)
13+
});
14+
}
15+
16+
constructor (bounds: BoundingPoints) {
17+
this.bounds = bounds;
18+
}
19+
20+
relativeFrom (coords: Coords): BoundsTransformer {
21+
return new BoundsTransformer({
22+
x1: calcMinus(this.bounds.x1, coords.x),
23+
y1: calcMinus(this.bounds.y1, coords.y),
24+
x2: calcMinus(this.bounds.x2, coords.x),
25+
y2: calcMinus(this.bounds.y2, coords.y)
26+
});
27+
}
28+
29+
expand (coords: Coords): BoundsTransformer {
30+
return new BoundsTransformer({
31+
x1: calcPlus(this.bounds.x1, coords.x),
32+
y1: calcPlus(this.bounds.y1, coords.y),
33+
x2: calcPlus(this.bounds.x2, coords.x),
34+
y2: calcPlus(this.bounds.y2, coords.y)
35+
});
36+
}
37+
38+
toBPoints (): BoundingPoints {
39+
return { ...this.bounds };
40+
}
41+
42+
toBBox (): BoundingBox {
43+
return {
44+
x: Math.min(this.bounds.x1, this.bounds.x2),
45+
y: Math.min(this.bounds.y1, this.bounds.y2),
46+
width: Math.abs(calcMinus(this.bounds.x2, this.bounds.x1)),
47+
height: Math.abs(calcMinus(this.bounds.y2, this.bounds.y1))
48+
};
49+
}
50+
}
51+
52+
export const convertBPointsToEllipseItemBounds = (bPoints: BoundingPoints): EllipseItemBounds => {
53+
const bBox = new BoundsTransformer(bPoints).toBBox();
54+
const rx = calcDiv(bBox.width, 2);
55+
const ry = calcDiv(bBox.height, 2);
56+
57+
return {
58+
cx: calcPlus(bBox.x, rx),
59+
cy: calcPlus(bBox.y, ry),
60+
rx,
61+
ry
62+
};
63+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { camelCase } from 'camel-case';
2+
import kebabCase from 'kebab-case';
3+
4+
const deepChangeCase = (data: unknown, converter: (arg: any) => string): unknown => {
5+
if (Array.isArray(data)) {
6+
return data.map(d => deepChangeCase(d, converter));
7+
}
8+
9+
if (typeof data === 'object') {
10+
if (data === null) {
11+
return data;
12+
}
13+
14+
const keys = Object.keys(data);
15+
if (!keys.length) {
16+
return data;
17+
}
18+
19+
return keys.reduce((obj: { [keys: string]: unknown }, key: string) => {
20+
obj[converter(key)] = deepChangeCase((data as any)[key], converter);
21+
return obj;
22+
}, {});
23+
}
24+
25+
return data;
26+
};
27+
28+
export const deepChangeToKebabCase = (data: unknown) => {
29+
return deepChangeCase(data, kebabCase);
30+
};
31+
32+
export const deepChangeToCamelCase = (data: unknown) => {
33+
return deepChangeCase(data, camelCase);
34+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { calcDiv } from './strict-calculator';
2+
3+
export const inverseScale = (scalled: number, scale: number) => {
4+
return calcDiv(scalled, scale);
5+
};
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import BigNumber from 'bignumber.js';
2+
import _cloneDeep from 'lodash.clonedeep';
3+
import { RectItem, LineItem, TextItem, TextBlockItem, ImageBlockItem, ImageItem, StackViewItem, BoundingBox, StackViewRow, StackViewItemBounds, BoundingPoints } from '@/types';
4+
5+
export const round = (value: number, digits = 2) => new BigNumber(value).decimalPlaces(digits, BigNumber.ROUND_HALF_UP).toNumber();
6+
7+
export const roundBoundingBox = (boundingBox: BoundingBox): BoundingBox => {
8+
return {
9+
x: round(boundingBox.x),
10+
y: round(boundingBox.y),
11+
width: round(boundingBox.width),
12+
height: round(boundingBox.height)
13+
};
14+
};
15+
16+
export const roundBoundingPoints = (boundingPoints: BoundingPoints): BoundingPoints => {
17+
return {
18+
x1: round(boundingPoints.x1),
19+
y1: round(boundingPoints.y1),
20+
x2: round(boundingPoints.x2),
21+
y2: round(boundingPoints.y2)
22+
};
23+
};
24+
25+
export const roundRectItem = (item: RectItem): RectItem => {
26+
return {
27+
..._cloneDeep(item),
28+
...roundBoundingBox(item)
29+
};
30+
};
31+
32+
export const roundLineItem = (item: LineItem): LineItem => {
33+
return {
34+
..._cloneDeep(item),
35+
x1: round(item.x1),
36+
y1: round(item.y1),
37+
x2: round(item.x2),
38+
y2: round(item.y2)
39+
};
40+
};
41+
42+
export const roundTextItem = (item: TextItem): TextItem => {
43+
const newItem = {
44+
..._cloneDeep(item),
45+
...roundBoundingBox(item)
46+
};
47+
48+
if (newItem.style.letterSpacing !== '') {
49+
newItem.style.letterSpacing = round(newItem.style.letterSpacing, 1);
50+
}
51+
52+
return newItem;
53+
};
54+
55+
export const roundTextBlockItem = (item: TextBlockItem): TextBlockItem => {
56+
const newItem = {
57+
..._cloneDeep(item),
58+
...roundBoundingBox(item)
59+
};
60+
61+
if (newItem.style.letterSpacing !== '') {
62+
newItem.style.letterSpacing = round(newItem.style.letterSpacing, 1);
63+
}
64+
65+
return newItem;
66+
};
67+
68+
export const roundImageBlockItem = (item: ImageBlockItem): ImageBlockItem => {
69+
return {
70+
..._cloneDeep(item),
71+
...roundBoundingBox(item)
72+
};
73+
};
74+
75+
export const roundImageItem = (item: ImageItem): ImageItem => {
76+
return {
77+
..._cloneDeep(item),
78+
...roundBoundingBox(item)
79+
};
80+
};
81+
82+
export const roundStackViewItem = (item: StackViewItem): StackViewItem => {
83+
const roundedBounds: StackViewItemBounds = {
84+
x: round(item.x),
85+
y: round(item.y),
86+
width: round(item.width)
87+
};
88+
return {
89+
..._cloneDeep(item),
90+
...roundedBounds
91+
};
92+
};
93+
94+
export const roundStackViewRow = (row: StackViewRow): StackViewRow => {
95+
return {
96+
..._cloneDeep(row),
97+
height: round(row.height)
98+
};
99+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Size, ImageData } from '@/types';
2+
3+
const ACCEPT_IMAGE_TYPES = ['image/png', 'image/jpeg'];
4+
5+
const openImageSelectDialog = async () => {
6+
return new Promise((resolve: (value: File) => void, reject: (reason: Error) => void) => {
7+
const input = document.createElement('input');
8+
9+
input.type = 'file';
10+
input.accept = ACCEPT_IMAGE_TYPES.join(',');
11+
input.value = '';
12+
input.onchange = e => {
13+
const input = e.target as HTMLInputElement;
14+
15+
if (input && input.files) {
16+
resolve(input.files[0]);
17+
} else {
18+
reject(new Error('Missing InputElement'));
19+
}
20+
};
21+
input.click();
22+
});
23+
};
24+
25+
const readAsDataUrl = async (file: Blob) => {
26+
return new Promise((resolve: (value: string) => void, reject: (reason: Error) => void) => {
27+
const reader = new FileReader();
28+
reader.onload = () => resolve(reader.result as string);
29+
reader.onerror = () => reject(new Error('Error occurred reading file'));
30+
reader.readAsDataURL(file);
31+
});
32+
};
33+
34+
const calcImageSize = async (dataUrl: string) => {
35+
return new Promise((resolve: (value: Size) => void) => {
36+
const image = new Image();
37+
image.onload = () => resolve({ width: image.width, height: image.height });
38+
image.src = dataUrl;
39+
});
40+
};
41+
42+
const extractTypeAndData = (dataUrl: string): ImageData => {
43+
const data = dataUrl.match(/^data:(.+?);base64,(.+)/);
44+
45+
if (!data) throw new Error('Invalid image data');
46+
47+
const mimeType = data[1];
48+
const base64 = data[2];
49+
50+
if (mimeType !== 'image/png' && mimeType !== 'image/jpeg') throw new Error(`Invalid image type: ${mimeType}`);
51+
52+
return { mimeType, base64 };
53+
};
54+
55+
export const selectImage = async () => {
56+
const file = await openImageSelectDialog();
57+
const dataUrl = await readAsDataUrl(file);
58+
const imageSize = await calcImageSize(dataUrl);
59+
60+
return {
61+
...imageSize,
62+
...extractTypeAndData(dataUrl)
63+
};
64+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import BigNumber from 'bignumber.js';
2+
3+
export const calcDiv = (a: number | BigNumber, b: number | BigNumber): number => new BigNumber(a).div(b).toNumber();
4+
export const calcPlus = (a: number | BigNumber, b: number | BigNumber): number => new BigNumber(a).plus(b).toNumber();
5+
export const calcMinus = (a: number | BigNumber, b: number | BigNumber): number => new BigNumber(a).minus(b).toNumber();
6+
export const calcMul = (a: number | BigNumber, b: number | BigNumber): number => new BigNumber(a).multipliedBy(b).toNumber();

0 commit comments

Comments
 (0)