mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 16:23:06 +10:00
We were previously omitting cmaps meaning that when signing documents with certain UTF-8 characters or CJK characters they would appear as outlined squares in the pdf viewer despite the actual pdf looking as expected with the characters displaying correctly.
207 lines
4.6 KiB
TypeScript
207 lines
4.6 KiB
TypeScript
import Konva from 'konva';
|
|
|
|
import {
|
|
DEFAULT_RECT_BACKGROUND,
|
|
RECIPIENT_COLOR_STYLES,
|
|
} from '@documenso/ui/lib/recipient-colors';
|
|
|
|
import type { FieldToRender, RenderFieldElementOptions } from './field-renderer';
|
|
import { calculateFieldPosition } from './field-renderer';
|
|
|
|
export const konvaTextFontFamily =
|
|
'"Noto Sans", "Noto Sans Japanese", "Noto Sans Chinese", "Noto Sans Korean", sans-serif';
|
|
export const konvaTextFill = 'black';
|
|
|
|
export const upsertFieldGroup = (
|
|
field: FieldToRender,
|
|
options: RenderFieldElementOptions,
|
|
): Konva.Group => {
|
|
const { pageWidth, pageHeight, pageLayer, editable, scale } = options;
|
|
|
|
const { fieldX, fieldY, fieldWidth, fieldHeight } = calculateFieldPosition(
|
|
field,
|
|
pageWidth,
|
|
pageHeight,
|
|
);
|
|
|
|
const fieldGroup: Konva.Group =
|
|
pageLayer.findOne(`#${field.renderId}`) ||
|
|
new Konva.Group({
|
|
id: field.renderId,
|
|
name: 'field-group',
|
|
});
|
|
|
|
const maxXPosition = (pageWidth - fieldWidth) * scale;
|
|
const maxYPosition = (pageHeight - fieldHeight) * scale;
|
|
|
|
fieldGroup.setAttrs({
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
x: fieldX,
|
|
y: fieldY,
|
|
draggable: editable,
|
|
dragBoundFunc: (pos) => {
|
|
const newX = Math.max(0, Math.min(maxXPosition, pos.x));
|
|
const newY = Math.max(0, Math.min(maxYPosition, pos.y));
|
|
|
|
return { x: newX, y: newY };
|
|
},
|
|
} satisfies Partial<Konva.GroupConfig>);
|
|
|
|
return fieldGroup;
|
|
};
|
|
|
|
export const upsertFieldRect = (
|
|
field: FieldToRender,
|
|
options: RenderFieldElementOptions,
|
|
): Konva.Rect => {
|
|
const { pageWidth, pageHeight, mode, pageLayer, color } = options;
|
|
|
|
const { fieldWidth, fieldHeight } = calculateFieldPosition(field, pageWidth, pageHeight);
|
|
|
|
const fieldRect: Konva.Rect =
|
|
pageLayer.findOne(`#${field.renderId}-rect`) ||
|
|
new Konva.Rect({
|
|
id: `${field.renderId}-rect`,
|
|
name: 'field-rect',
|
|
});
|
|
|
|
fieldRect.setAttrs({
|
|
width: fieldWidth,
|
|
height: fieldHeight,
|
|
fill: DEFAULT_RECT_BACKGROUND,
|
|
stroke: color ? RECIPIENT_COLOR_STYLES[color].baseRing : '#e5e7eb',
|
|
strokeWidth: 2,
|
|
cornerRadius: 2,
|
|
strokeScaleEnabled: false,
|
|
visible: mode !== 'export',
|
|
} satisfies Partial<Konva.RectConfig>);
|
|
|
|
return fieldRect;
|
|
};
|
|
|
|
export const createSpinner = ({
|
|
fieldWidth,
|
|
fieldHeight,
|
|
}: {
|
|
fieldWidth: number;
|
|
fieldHeight: number;
|
|
}) => {
|
|
const loadingGroup = new Konva.Group({
|
|
name: 'loading-spinner-group',
|
|
});
|
|
|
|
const rect = new Konva.Rect({
|
|
x: 4,
|
|
y: 4,
|
|
width: fieldWidth - 8,
|
|
height: fieldHeight - 8,
|
|
fill: 'white',
|
|
opacity: 0.8,
|
|
});
|
|
|
|
const maxSpinnerSize = 10;
|
|
const smallerDimension = Math.min(fieldWidth, fieldHeight);
|
|
const spinnerSize = Math.min(smallerDimension, maxSpinnerSize);
|
|
|
|
const spinner = new Konva.Arc({
|
|
x: fieldWidth / 2,
|
|
y: fieldHeight / 2,
|
|
innerRadius: spinnerSize,
|
|
outerRadius: spinnerSize / 2,
|
|
angle: 270,
|
|
rotation: 0,
|
|
fill: 'rgba(122, 195, 85, 1)',
|
|
lineCap: 'round',
|
|
});
|
|
|
|
rect.moveToTop();
|
|
spinner.moveToTop();
|
|
|
|
loadingGroup.add(rect);
|
|
loadingGroup.add(spinner);
|
|
|
|
const anim = new Konva.Animation((frame) => {
|
|
spinner.rotate(180 * (frame.timeDiff / 500));
|
|
});
|
|
|
|
anim.start();
|
|
|
|
return loadingGroup;
|
|
};
|
|
|
|
type CreateFieldHoverInteractionOptions = {
|
|
options: RenderFieldElementOptions;
|
|
fieldGroup: Konva.Group;
|
|
fieldRect: Konva.Rect;
|
|
};
|
|
|
|
/**
|
|
* Adds smooth transition-like behavior for hover effects to the field group and rectangle.
|
|
*/
|
|
export const createFieldHoverInteraction = ({
|
|
options,
|
|
fieldGroup,
|
|
fieldRect,
|
|
}: CreateFieldHoverInteractionOptions) => {
|
|
const { mode } = options;
|
|
|
|
if (mode === 'export' || !options.color) {
|
|
return;
|
|
}
|
|
|
|
const hoverColor = RECIPIENT_COLOR_STYLES[options.color].baseRingHover;
|
|
|
|
fieldGroup.on('mouseover', () => {
|
|
const layer = fieldRect.getLayer();
|
|
if (!layer) {
|
|
return;
|
|
}
|
|
|
|
new Konva.Tween({
|
|
node: fieldRect,
|
|
duration: 0.3,
|
|
fill: hoverColor,
|
|
}).play();
|
|
});
|
|
|
|
fieldGroup.on('mouseout', () => {
|
|
const layer = fieldRect.getLayer();
|
|
if (!layer) {
|
|
return;
|
|
}
|
|
|
|
new Konva.Tween({
|
|
node: fieldRect,
|
|
duration: 0.3,
|
|
fill: DEFAULT_RECT_BACKGROUND,
|
|
}).play();
|
|
});
|
|
|
|
fieldGroup.on('transformstart', () => {
|
|
const layer = fieldRect.getLayer();
|
|
if (!layer) {
|
|
return;
|
|
}
|
|
|
|
new Konva.Tween({
|
|
node: fieldRect,
|
|
duration: 0.3,
|
|
fill: hoverColor,
|
|
}).play();
|
|
});
|
|
|
|
fieldGroup.on('transformend', () => {
|
|
const layer = fieldRect.getLayer();
|
|
if (!layer) {
|
|
return;
|
|
}
|
|
|
|
new Konva.Tween({
|
|
node: fieldRect,
|
|
duration: 0.3,
|
|
fill: DEFAULT_RECT_BACKGROUND,
|
|
}).play();
|
|
});
|
|
};
|