import { DxfWriter, point2d, LWPolylineFlags, point3d, TrueColor, pattern, HatchPredefinedPatterns, HatchBoundaryPaths, HatchPolylineBoundary, vertex, } from "@tarikjabiri/dxf"; import { useStore } from "../store"; import Zip from "jszip"; import { LineData } from "../components/line"; import { CircleData } from "../components/circle"; import { Transform } from "konva/lib/Util"; import { lineLen, lineVector, Pos, Size, verticalVectorLine, zeroEq } from "@/utils/math"; import { RectangleData } from "../components/rectangle"; import { useStage } from "./use-global-vars"; import { Group } from "konva/lib/Group"; import { Text } from "konva/lib/shapes/Text"; import { TextData } from "../components/text"; import { ImageData } from "../components/image"; import { onlyId } from "@/utils/shared"; import { ArrowData, PointerPosition } from "../components/arrow"; import { IconData } from "../components/icon"; import { useSetViewport, useViewer, useViewerInvertTransform, } from "./use-viewer"; import { nextTick } from "vue"; import { SLineData } from "../components/sequent-line"; import { IRect } from "konva/lib/types"; export const useGetDXF = () => { const store = useStore(); const stage = useStage(); const invMat = useViewerInvertTransform(); const { setViewport } = useSetViewport(); const { viewer } = useViewer(); return async () => { const writer = new DxfWriter(); const $stage = stage.value!.getNode(); const zip = new Zip(); const genPromises: Promise[] = []; const fontStyle = writer.tables.styleTable.records[0]; fontStyle.fontFileName = "SimSun"; type PL = { id?: string; points: Pos[]; fill?: string; content?: string; strokeWidth?: number; stroke?: string; }; const writerPolyline = (pl: PL) => { if (pl.fill) { const polyline = new HatchPolylineBoundary(); const boundary = new HatchBoundaryPaths(); pl.points.forEach((p) => polyline.add(vertex(p.x, -p.y))); boundary.addPolylineBoundary(polyline); writer.addHatch( boundary, pattern({ name: HatchPredefinedPatterns.SOLID }), { trueColor: TrueColor.fromHex(pl.fill).toString() } ); } writer.addLWPolyline( pl.points.map((p) => ({ point: point2d(p.x, -p.y) })), { flags: LWPolylineFlags.Closed, constantWidth: pl.strokeWidth, trueColor: TrueColor.fromHex(pl.stroke || "#FFFFFF").toString(), } ); if (!pl.content) return; const $text = $stage.findOne(`#${pl.id}`)?.findOne(".text"); if ($text) { writeText($text); } }; const writeText = ($text: Text, sp = true) => { const mat = $text.getTransform(); const fontSize = $text.fontSize() * 0.8; let text = $text.text(); text = text.replace(/(\n|\r|\t)/gi, ""); const pad = $text.padding(); const align = $text.align(); const textArr: { content: string; charCount: number }[] = []; let lineNum = 1; const width = $text.width(); const charWidth = fontSize * 0.9; const lineCharCount = sp ? Math.max(Math.floor(width / charWidth), 2) : Number.MAX_VALUE; let ndx = 0; let prevNdx = 0; let charCount = 0; while (ndx < text.length) { ndx++; const c = /[\u4e00-\u9fff]/.test(text.charAt(ndx)) ? 2.4 : 1; if (charCount === lineCharCount || ndx >= text.length) { charCount += c; textArr.push({ content: text.substring(prevNdx, ndx + 1), charCount, }); charCount = 0; prevNdx = ndx; } else if (charCount > lineCharCount) { textArr.push({ content: text.substring(prevNdx, ndx), charCount, }); charCount = 0; ndx--; prevNdx = ndx; } else { charCount += c; } } textArr.forEach((item) => { const lineWidth = charWidth * item.charCount; let p = { x: pad, y: pad + lineNum * fontSize * 1.2 }; if (align === "center") { p.x = (width - lineWidth) / 2; } else if (align === "right") { p.x = width - lineWidth; } const start = mat.point(p); const text = writer.addText( point3d(start.x, -start.y), fontSize, item.content, { rotation: $text.rotation(), // horizontalAlignment: // align === "center" // ? TextHorizontalAlignment.Center // : align === "right" // ? TextHorizontalAlignment.Right // : TextHorizontalAlignment.Left, } ); text.trueColor = TrueColor.fromHex($text.fill() as string).toString(); text.height = fontSize; lineNum++; }); }; const writeImage = async (imgGroup: Group, scaleCallback?: (scale: Size, box: IRect) => () => void) => { let curRect = imgGroup.getClientRect(); const oldViewMat = viewer.viewMat; setViewport(curRect); await nextTick(); const imgRect = imgGroup.getClientRect(); const back = scaleCallback && scaleCallback({ width: imgRect.width / curRect.width, height: imgRect.height / curRect.height }, imgRect) await nextTick() const img = (await imgGroup!.toImage({ pixelRatio: 1, quality: 1, mimeType: "image/png", }).catch((e) => { console.error(e) throw e })) as HTMLImageElement; back && back() await nextTick() const start = invMat.value.point({ x: imgRect.x, y: imgRect.y + imgRect.height }); const end = invMat.value.point({ x: imgRect.x + imgRect.width, y: imgRect.y }); const name = onlyId().replace(/\-/g, ""); const path = name + ".png"; const image = writer.addImage( path, name, point3d(start.x, -start.y), img.width, img.height, 1, 0 ); image.ratio = Math.abs(end.x - start.x) / img.width; genPromises.push( fetch(img.src) .then((res) => res.blob()) .then((blob) => zip.file(path, blob, {})) .catch(e => { console.error(e) }) ); viewer.setViewMat(oldViewMat); }; for (const _item of store.sortItems) { if (_item.hide) continue; const type = store.getType(_item.id); let item; let mat; switch (type) { case "sequentLine": item = _item as SLineData; writer.addLWPolyline( item.points.map((p) => ({ point: point2d(p.x, -p.y) })), { flags: LWPolylineFlags.None, constantWidth: item.strokeWidth, trueColor: TrueColor.fromHex(item.stroke || "#FFFFFF").toString(), } ); break; case "line": const litem = _item as LineData; litem.lines.forEach((line) => { const a = litem.points.find((p) => p.id === line.a)!; const b = litem.points.find((p) => p.id === line.b)!; // writer.addLine( // point3d(a.x, a.y, 0), // point3d(b.x, b.y, 0), // { // trueColor: TrueColor.fromHex(line.stroke || "#FFFFFF").toString(), // } // ) writer.addLWPolyline( [ { point: point3d(a.x, -a.y, 0) }, { point: point3d(b.x, -b.y, 0) }, ], { flags: LWPolylineFlags.None, constantWidth: line.strokeWidth, trueColor: TrueColor.fromHex( line.stroke || "#FFFFFF" ).toString(), } ); }); break; case "triangle": case "rectangle": case "polygon": item = _item as RectangleData; writerPolyline(item); break; case "circle": item = _item as CircleData; mat = new Transform(item.mat); const points = []; for (let angle = 0; angle <= 360; angle += 1) { const rad = angle * (Math.PI / 180); const x = item.radiusX * Math.cos(rad); const y = item.radiusY * Math.sin(rad); points.push(mat.point({ x, y })); } writerPolyline({ ...item, points }); break; case "text": item = _item as TextData; writeText($stage.findOne(`#${item.id}`)!, !!item.width); break; case "arrow": item = _item as ArrowData; if (zeroEq(lineLen(item.points[0], item.points[1]))) { item = { ...item, points: [...item.points] } item.points[0] = { ...item.points[0], x: item.points[1].x - (item.pointerLength || 1) } } const isEnd = [PointerPosition.end, PointerPosition.all].includes( item.pointerPosition || PointerPosition.start ); const isStart = [PointerPosition.start, PointerPosition.all].includes( item.pointerPosition || PointerPosition.start ); for (let i = 0; i < item.points.length - 1; i++) { const line = [item.points[i], item.points[i + 1]]; const nline = [...line]; const vector = lineVector(line); if (isStart) { const start = vector .clone() .multiplyScalar(item.pointerLength! * 2) .add(line[0]); nline[0] = start; const l1 = verticalVectorLine( vector, start, item.pointerLength! ); const l2 = verticalVectorLine( vector, start, -item.pointerLength! ); writerPolyline({ points: [line[0], l1[1], l2[1]], fill: item.fill, stroke: item.fill, }); } if (isEnd) { const start = vector .clone() .multiplyScalar(-item.pointerLength! * 2) .add(line[1]); nline[1] = start; const l1 = verticalVectorLine( vector, start, item.pointerLength! ); const l2 = verticalVectorLine( vector, start, -item.pointerLength! ); writerPolyline({ points: [line[1], l1[1], l2[1]], fill: item.fill, stroke: item.fill, }); } writer.addLWPolyline( nline.map((p) => ({ point: point2d(p.x, -p.y) })), { flags: LWPolylineFlags.None, constantWidth: item.strokeWidth, trueColor: TrueColor.fromHex(item.fill || "#FFFFFF").toString(), } ); } break; case "image": item = _item as ImageData; await writeImage($stage.findOne(`#${item.id}`)!); break; case "icon": const iconItem = _item as IconData; const pathGroup = $stage .findOne(`#${iconItem.id}`)! .findOne(".rep-position")!; await writeImage(pathGroup, () => { iconItem.strokeScaleEnabled = true return () => { iconItem.strokeScaleEnabled = false } }); break; } } let dxfString = writer.stringify(); zip.file(onlyId() + ".dxf", dxfString); return Promise.all(genPromises).then(() => zip.generateAsync({ type: "blob" }) ); }; };