import {
|
BooleanNumber,
|
BuildTextUtils,
|
createDocumentModelWithStyle,
|
Disposable,
|
DrawingTypeEnum,
|
IAccessor,
|
ICommandService,
|
IImageIoService,
|
IImageIoServiceParam,
|
Inject,
|
Injector,
|
IUniverInstanceService,
|
Nullable,
|
ObjectRelativeFromH,
|
ObjectRelativeFromV,
|
PositionedObjectLayoutType,
|
UniverInstanceType,
|
Workbook,
|
WrapTextType,
|
} from '@univerjs/core';
|
import { docDrawingPositionToTransform } from '@univerjs/docs-ui';
|
import { getImageSize } from '@univerjs/drawing';
|
import { IRenderManagerService } from '@univerjs/engine-render';
|
import { ISheetLocationBase, SetRangeValuesCommand } from '@univerjs/sheets';
|
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
|
import { rotatedBoundingBox } from '../utils';
|
|
export class JnpfSheetsCellImageService extends Disposable {
|
constructor(
|
@Inject(Injector) private readonly _injector: Injector,
|
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
@ICommandService private readonly _commandService: ICommandService,
|
@IImageIoService private readonly _imageIoService: IImageIoService,
|
) {
|
super();
|
}
|
|
/**
|
* 在当前选中的单元格插入图片
|
* @param file 要插入的图片文件
|
* @param row 行
|
* @param col 列
|
* @param cellData 单元格配置信息
|
* @returns 插入结果,成功返回执行命令的结果,失败返回 `false` 或 `null`
|
*/
|
public async insertCellImage(file: File, row: number, col: number, cellData: any) {
|
// 获取当前的工作簿实例
|
const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
if (!workbook) {
|
return false;
|
}
|
const unitId = workbook.getUnitId();
|
|
// 获取当前激活的工作表
|
const worksheet = workbook.getActiveSheet();
|
if (!worksheet) {
|
return false;
|
}
|
const subUnitId = worksheet.getSheetId();
|
|
// 将图片文件保存到 ImageIoService 并获取图片参数
|
let imageParam: Nullable<IImageIoServiceParam>;
|
try {
|
imageParam = await this._imageIoService.saveImage(file);
|
} catch {
|
return false;
|
}
|
if (imageParam == null) {
|
return false;
|
}
|
|
// 获取图片相关信息
|
const { base64Cache, imageId, imageSourceType, source } = imageParam;
|
const { height, image, width } = await getImageSize(base64Cache || '');
|
// 缓存图片数据
|
this._imageIoService.addImageSourceCache(source, imageSourceType, image);
|
|
const docDataModel = createDocumentModelWithStyle('', {});
|
|
// 计算图片适应单元格后的尺寸
|
const imageSize = this._getDrawingSizeByCell(
|
this._injector,
|
{
|
col,
|
row,
|
subUnitId,
|
unitId,
|
},
|
width,
|
height,
|
0,
|
);
|
if (!imageSize) {
|
return false;
|
}
|
|
// 定义图片的文档变换参数
|
const docTransform = {
|
angle: 0,
|
positionH: {
|
posOffset: 0,
|
relativeFrom: ObjectRelativeFromH.PAGE,
|
},
|
positionV: {
|
posOffset: 0,
|
relativeFrom: ObjectRelativeFromV.PARAGRAPH,
|
},
|
size: {
|
height: imageSize.height,
|
width: imageSize.width,
|
},
|
};
|
|
// 定义文档中的绘图参数
|
const docDrawingParam = {
|
behindDoc: BooleanNumber.FALSE,
|
description: '',
|
distB: 0,
|
distL: 0,
|
distR: 0,
|
distT: 0,
|
docTransform,
|
drawingId: imageId,
|
drawingType: DrawingTypeEnum.DRAWING_IMAGE,
|
imageSourceType,
|
layoutType: PositionedObjectLayoutType.INLINE, // Insert inline drawing by default.
|
source,
|
subUnitId: docDataModel.getUnitId(),
|
title: '',
|
transform: docDrawingPositionToTransform(docTransform),
|
unitId: docDataModel.getUnitId(),
|
wrapText: WrapTextType.BOTH_SIDES,
|
};
|
|
// 生成插入绘图的 JSON 结构
|
const jsonXActions = BuildTextUtils.drawing.add({
|
documentDataModel: docDataModel,
|
drawings: [docDrawingParam],
|
selection: {
|
collapsed: true,
|
endOffset: 0,
|
startOffset: 0,
|
},
|
});
|
|
if (!jsonXActions) {
|
return false;
|
}
|
|
docDataModel.apply(jsonXActions);
|
|
return this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, {
|
value: {
|
[row]: {
|
[col]: {
|
custom: {
|
...cellData.custom,
|
drawingId: imageId,
|
},
|
p: docDataModel.getSnapshot(),
|
t: 1,
|
v: cellData.v ?? '',
|
},
|
},
|
},
|
});
|
}
|
|
/**
|
* 获取单元格内图形的大小
|
* @param accessor 访问器对象
|
* @param location 单元格位置信息
|
* @param originImageWidth 原始图像宽度
|
* @param originImageHeight 原始图像高度
|
* @param angle 旋转角度(0-360,单位:度)
|
* @returns 计算后的图像尺寸,或在获取失败时返回 `false`
|
*/
|
private _getDrawingSizeByCell(accessor: IAccessor, location: ISheetLocationBase, originImageWidth: number, originImageHeight: number, angle: number) {
|
const { rotatedHeight, rotatedWidth } = rotatedBoundingBox(originImageWidth, originImageHeight, angle);
|
const renderManagerService = accessor.get(IRenderManagerService);
|
const currentRender = renderManagerService.getRenderById(location.unitId);
|
if (!currentRender) {
|
return false;
|
}
|
const skeletonManagerService = currentRender.with(SheetSkeletonManagerService);
|
const skeleton = skeletonManagerService.getWorksheetSkeleton(location.subUnitId)?.skeleton;
|
if (skeleton == null) {
|
return false;
|
}
|
const cellInfo = skeleton.getCellWithCoordByIndex(location.row, location.col);
|
|
const cellWidth = cellInfo.mergeInfo.endX - cellInfo.mergeInfo.startX - 2;
|
const cellHeight = cellInfo.mergeInfo.endY - cellInfo.mergeInfo.startY - 2;
|
const imageRatio = rotatedWidth / rotatedHeight;
|
const imageWidth = Math.ceil(Math.min(cellWidth, cellHeight * imageRatio));
|
const scale = imageWidth / rotatedWidth;
|
const realScale = !scale || Number.isNaN(scale) ? 0.001 : scale;
|
|
return {
|
height: originImageHeight * realScale,
|
width: originImageWidth * realScale,
|
};
|
}
|
}
|