@bill_nee/dwg-reader 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +615 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +15 -0
- package/lib/index.js +355 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +32 -4
- package/vendor/libredwg-web/lib/converter/converter.js +1102 -0
- package/vendor/libredwg-web/lib/converter/dwgColorToMLeaderRawColor.js +55 -0
- package/vendor/libredwg-web/lib/converter/entityConverter.js +2050 -0
- package/vendor/libredwg-web/lib/converter/index.js +3 -0
- package/vendor/libredwg-web/lib/converter/utils.js +46 -0
- package/vendor/libredwg-web/lib/database/classes/class.js +2 -0
- package/vendor/libredwg-web/lib/database/classes/index.js +2 -0
- package/vendor/libredwg-web/lib/database/codepage.js +101 -0
- package/vendor/libredwg-web/lib/database/common.js +2 -0
- package/vendor/libredwg-web/lib/database/database.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/3dface.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/arc.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/arcAlignedText.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/attdef.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/attrib.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/circle.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/dimension.js +70 -0
- package/vendor/libredwg-web/lib/database/entities/ellipse.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/entity.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/hatch.js +54 -0
- package/vendor/libredwg-web/lib/database/entities/image.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/index.js +38 -0
- package/vendor/libredwg-web/lib/database/entities/insert.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/leader.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/line.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/lwpolyline.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/mline.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/mtext.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/multileader.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/ole2frame.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/oleframe.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/point.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/polyline.js +19 -0
- package/vendor/libredwg-web/lib/database/entities/proxy.js +10 -0
- package/vendor/libredwg-web/lib/database/entities/ray.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/section.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/shape.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/solid.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/solid3d.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/spline.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/table.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/text.js +23 -0
- package/vendor/libredwg-web/lib/database/entities/tolerance.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/trace.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/vertex.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/viewport.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/wipeout.js +2 -0
- package/vendor/libredwg-web/lib/database/entities/xline.js +2 -0
- package/vendor/libredwg-web/lib/database/header/header.js +2 -0
- package/vendor/libredwg-web/lib/database/header/index.js +3 -0
- package/vendor/libredwg-web/lib/database/header/variables.js +217 -0
- package/vendor/libredwg-web/lib/database/index.js +12 -0
- package/vendor/libredwg-web/lib/database/objects/common.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/dictionary.js +19 -0
- package/vendor/libredwg-web/lib/database/objects/imageDef.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/index.js +11 -0
- package/vendor/libredwg-web/lib/database/objects/layerFilter.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/layerIndex.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/layout.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/mleaderStyle.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/plotSetting.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/spatialFilter.js +2 -0
- package/vendor/libredwg-web/lib/database/objects/xrecord.js +2 -0
- package/vendor/libredwg-web/lib/database/shared/index.js +2 -0
- package/vendor/libredwg-web/lib/database/shared/xdata.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/appId.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/blockRecord.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/dimStyle.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/index.js +9 -0
- package/vendor/libredwg-web/lib/database/tables/layer.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/ltype.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/style.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/table.js +2 -0
- package/vendor/libredwg-web/lib/database/tables/vport.js +2 -0
- package/vendor/libredwg-web/lib/database/textEscapes.js +19 -0
- package/vendor/libredwg-web/lib/database/version.js +260 -0
- package/vendor/libredwg-web/lib/index.js +4 -0
- package/vendor/libredwg-web/lib/libredwg.js +1271 -0
- package/vendor/libredwg-web/lib/svg/box2d.js +104 -0
- package/vendor/libredwg-web/lib/svg/bspline.js +95 -0
- package/vendor/libredwg-web/lib/svg/color.js +371 -0
- package/vendor/libredwg-web/lib/svg/index.js +2 -0
- package/vendor/libredwg-web/lib/svg/polyline.js +70 -0
- package/vendor/libredwg-web/lib/svg/svgConverter.js +540 -0
- package/vendor/libredwg-web/lib/svg/transformBoundingBoxAndElement.js +20 -0
- package/vendor/libredwg-web/lib/svg/vector.js +27 -0
- package/vendor/libredwg-web/lib/types/common.js +2 -0
- package/vendor/libredwg-web/lib/types/enums.js +390 -0
- package/vendor/libredwg-web/lib/types/index.js +3 -0
- package/vendor/libredwg-web/wasm/libredwg-web.js +2 -0
- package/vendor/libredwg-web/wasm/libredwg-web.wasm +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
import { isModelSpace } from '../converter/utils.js';
|
|
2
|
+
import { DwgAttachmentPoint, DwgTextHorizontalAlign } from '../database/index.js';
|
|
3
|
+
import { Box2D } from './box2d.js';
|
|
4
|
+
import { evaluateBSpline } from './bspline.js';
|
|
5
|
+
import { Color } from './color.js';
|
|
6
|
+
import { interpolatePolyline } from './polyline.js';
|
|
7
|
+
export class SvgConverter {
|
|
8
|
+
blockMap = new Map();
|
|
9
|
+
rotate(point, angle) {
|
|
10
|
+
const cos = Math.cos(angle);
|
|
11
|
+
const sin = Math.sin(angle);
|
|
12
|
+
return {
|
|
13
|
+
x: point.x * cos - point.y * sin,
|
|
14
|
+
y: point.x * sin + point.y * cos
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Interpolates a B-spline curve and returns the resulting polyline.
|
|
19
|
+
*
|
|
20
|
+
* @param controlPoints The control points of the B-spline.
|
|
21
|
+
* @param degree The degree of the B-spline.
|
|
22
|
+
* @param knots The knot vector.
|
|
23
|
+
* @param interpolationsPerSplineSegment Number of interpolated points per spline segment.
|
|
24
|
+
* @param weights Optional weight vector for rational B-splines.
|
|
25
|
+
* @returns An array of interpolated 2D points representing the polyline.
|
|
26
|
+
*/
|
|
27
|
+
interpolateBSpline(controlPoints, degree, knots, interpolationsPerSplineSegment = 25, weights) {
|
|
28
|
+
const polyline = [];
|
|
29
|
+
const controlPointsForLib = controlPoints.map((p) => [p.x, p.y]);
|
|
30
|
+
const segmentTs = [knots[degree]];
|
|
31
|
+
const domain = [
|
|
32
|
+
knots[degree],
|
|
33
|
+
knots[knots.length - 1 - degree]
|
|
34
|
+
];
|
|
35
|
+
for (let k = degree + 1; k < knots.length - degree; ++k) {
|
|
36
|
+
if (segmentTs[segmentTs.length - 1] !== knots[k]) {
|
|
37
|
+
segmentTs.push(knots[k]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (let i = 1; i < segmentTs.length; ++i) {
|
|
41
|
+
const uMin = segmentTs[i - 1];
|
|
42
|
+
const uMax = segmentTs[i];
|
|
43
|
+
for (let k = 0; k <= interpolationsPerSplineSegment; ++k) {
|
|
44
|
+
const u = (k / interpolationsPerSplineSegment) * (uMax - uMin) + uMin;
|
|
45
|
+
let t = (u - domain[0]) / (domain[1] - domain[0]);
|
|
46
|
+
t = Math.max(0, Math.min(1, t)); // Clamp t to [0, 1]
|
|
47
|
+
const p = evaluateBSpline(t, degree, controlPointsForLib, knots, weights);
|
|
48
|
+
polyline.push({ x: p[0], y: p[1] });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return polyline;
|
|
52
|
+
}
|
|
53
|
+
addFlipXIfApplicable(entity, { bbox, element }) {
|
|
54
|
+
if ('extrusionDirection' in entity &&
|
|
55
|
+
entity.extrusionDirection.z === -1) {
|
|
56
|
+
return {
|
|
57
|
+
bbox: new Box2D()
|
|
58
|
+
.expandByPoint({ x: -bbox.min.x, y: bbox.min.y })
|
|
59
|
+
.expandByPoint({ x: -bbox.max.x, y: bbox.max.y }),
|
|
60
|
+
element: `<g transform="matrix(-1 0 0 1 0 0)">${element}</g>`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return { bbox, element };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
line(entity) {
|
|
68
|
+
const bbox = new Box2D()
|
|
69
|
+
.expandByPoint({ x: entity.startPoint.x, y: entity.startPoint.y })
|
|
70
|
+
.expandByPoint({ x: entity.endPoint.x, y: entity.endPoint.y });
|
|
71
|
+
const element = `<line x1="${entity.startPoint.x}" y1="${entity.startPoint.y}" x2="${entity.endPoint.x}" y2="${entity.endPoint.y}" />`;
|
|
72
|
+
return { bbox, element };
|
|
73
|
+
}
|
|
74
|
+
ray(entity) {
|
|
75
|
+
const scale = 10000;
|
|
76
|
+
const firstPoint = entity.firstPoint;
|
|
77
|
+
const secondPoint = {
|
|
78
|
+
x: firstPoint.x + entity.unitDirection.x * scale,
|
|
79
|
+
y: firstPoint.y + entity.unitDirection.y * scale
|
|
80
|
+
};
|
|
81
|
+
const bbox = new Box2D()
|
|
82
|
+
.expandByPoint(firstPoint)
|
|
83
|
+
.expandByPoint(secondPoint);
|
|
84
|
+
const element = `<line x1="${firstPoint.x}" y1="${firstPoint.y}" x2="${secondPoint.x}" y2="${secondPoint.y}" />`;
|
|
85
|
+
return { bbox, element };
|
|
86
|
+
}
|
|
87
|
+
xline(entity) {
|
|
88
|
+
const scale = 10000;
|
|
89
|
+
const firstPoint = {
|
|
90
|
+
x: entity.firstPoint.x - entity.unitDirection.x * scale,
|
|
91
|
+
y: entity.firstPoint.y - entity.unitDirection.y * scale
|
|
92
|
+
};
|
|
93
|
+
const secondPoint = {
|
|
94
|
+
x: entity.firstPoint.x + entity.unitDirection.x * scale,
|
|
95
|
+
y: entity.firstPoint.y + entity.unitDirection.y * scale
|
|
96
|
+
};
|
|
97
|
+
const bbox = new Box2D()
|
|
98
|
+
.expandByPoint(firstPoint)
|
|
99
|
+
.expandByPoint(secondPoint);
|
|
100
|
+
const element = `<line x1="${firstPoint.x}" y1="${firstPoint.y}" x2="${secondPoint.x}" y2="${secondPoint.y}" />`;
|
|
101
|
+
return { bbox, element };
|
|
102
|
+
}
|
|
103
|
+
extractMTextLines(mtext) {
|
|
104
|
+
return (mtext
|
|
105
|
+
// Convert Unicode codes to characters
|
|
106
|
+
.replace(/\\U\+([0-9A-Fa-f]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
|
107
|
+
// Preserve line breaks: replace \P with newline placeholder
|
|
108
|
+
.replace(/\\P/g, '\n')
|
|
109
|
+
// Remove underline, overline
|
|
110
|
+
.replace(/\\[LOlo]/g, '')
|
|
111
|
+
// Remove font specs like \FArial|b0|i0|c134|p49;
|
|
112
|
+
.replace(/\\[Ff][^;\\]*?(?:\|[^;\\]*)*;/g, '')
|
|
113
|
+
// Remove formatting codes like \H1.0x; \W0.5; \C7 etc.
|
|
114
|
+
.replace(/\\[KkCcHhWwTtAa][^;\\]*;?/g, '')
|
|
115
|
+
// Remove general \word; style control codes like \x; \pxqc;
|
|
116
|
+
.replace(/\\[a-zA-Z]+;?/g, '')
|
|
117
|
+
// Remove AutoCAD %% control sequences like %%d, %%p, etc.
|
|
118
|
+
.replace(/%%(d|p|c|%)/gi, '')
|
|
119
|
+
// Replace escaped backslash
|
|
120
|
+
.replace(/\\\\/g, '\\')
|
|
121
|
+
// Replace non-breaking space
|
|
122
|
+
.replace(/\\~/g, '\u00A0')
|
|
123
|
+
// Remove grouping braces
|
|
124
|
+
.replace(/[{}]/g, '')
|
|
125
|
+
// Split by preserved newlines
|
|
126
|
+
.split('\n')
|
|
127
|
+
.map(line => line.trim())
|
|
128
|
+
.filter(line => line.length > 0));
|
|
129
|
+
}
|
|
130
|
+
lines(lines, fontsize, insertionPoint, extentsWidth, anchor = 'start') {
|
|
131
|
+
const bbox = new Box2D()
|
|
132
|
+
.expandByPoint({
|
|
133
|
+
x: insertionPoint.x,
|
|
134
|
+
y: insertionPoint.y
|
|
135
|
+
})
|
|
136
|
+
.expandByPoint({
|
|
137
|
+
x: insertionPoint.x + extentsWidth,
|
|
138
|
+
y: insertionPoint.y - lines.length * fontsize * 1.5
|
|
139
|
+
});
|
|
140
|
+
const texts = lines.map((line, index) => {
|
|
141
|
+
const x = insertionPoint.x;
|
|
142
|
+
const y = insertionPoint.y - index * fontsize * 1.5;
|
|
143
|
+
const transform = `translate(${x},${y}) scale(1,-1) translate(${-x},${-y})`;
|
|
144
|
+
return `<text x="${x}" y="${y}" font-size="${fontsize}" text-anchor="${anchor}" transform="${transform}">${line}</text>`;
|
|
145
|
+
});
|
|
146
|
+
return { bbox, element: texts.join('\n') };
|
|
147
|
+
}
|
|
148
|
+
mtext(entity) {
|
|
149
|
+
const fontsize = entity.textHeight;
|
|
150
|
+
const insertionPoint = entity.insertionPoint;
|
|
151
|
+
const lines = this.extractMTextLines(entity.text);
|
|
152
|
+
const attachmentPoint = entity.attachmentPoint;
|
|
153
|
+
let anchor = 'start';
|
|
154
|
+
if (attachmentPoint == DwgAttachmentPoint.BottomCenter ||
|
|
155
|
+
attachmentPoint == DwgAttachmentPoint.MiddleCenter ||
|
|
156
|
+
attachmentPoint == DwgAttachmentPoint.TopCenter) {
|
|
157
|
+
anchor = 'middle';
|
|
158
|
+
}
|
|
159
|
+
else if (attachmentPoint == DwgAttachmentPoint.BottomRight ||
|
|
160
|
+
attachmentPoint == DwgAttachmentPoint.MiddleRight ||
|
|
161
|
+
attachmentPoint == DwgAttachmentPoint.TopRight) {
|
|
162
|
+
anchor = 'end';
|
|
163
|
+
}
|
|
164
|
+
return this.lines(lines, fontsize, insertionPoint, entity.extentsWidth, anchor);
|
|
165
|
+
}
|
|
166
|
+
table(entity) {
|
|
167
|
+
const { rowCount, columnCount, rowHeightArr, columnWidthArr, startPoint, cells } = entity;
|
|
168
|
+
const originX = startPoint.x;
|
|
169
|
+
const originY = startPoint.y;
|
|
170
|
+
// Compute cell rectangles
|
|
171
|
+
const cellRects = [];
|
|
172
|
+
for (let row = 0, y = originY; row < rowCount; row++) {
|
|
173
|
+
const height = rowHeightArr[row];
|
|
174
|
+
let x = originX;
|
|
175
|
+
for (let col = 0; col < columnCount; col++) {
|
|
176
|
+
const cellIndex = row * columnCount + col;
|
|
177
|
+
const cell = cells[cellIndex];
|
|
178
|
+
const width = columnWidthArr[col];
|
|
179
|
+
cellRects.push({ x, y, width, height, cell, row, col });
|
|
180
|
+
x += width;
|
|
181
|
+
}
|
|
182
|
+
y += height;
|
|
183
|
+
}
|
|
184
|
+
// Create SVG content
|
|
185
|
+
const svgElements = cellRects
|
|
186
|
+
.map(({ x, y, width, height, cell }) => {
|
|
187
|
+
const lines = [];
|
|
188
|
+
if (cell.topBorderVisibility)
|
|
189
|
+
lines.push(`<line x1="${x}" y1="${y}" x2="${x + width}" y2="${y}" stroke="black" />`);
|
|
190
|
+
if (cell.bottomBorderVisibility)
|
|
191
|
+
lines.push(`<line x1="${x}" y1="${y + height}" x2="${x + width}" y2="${y + height}" stroke="black" />`);
|
|
192
|
+
if (cell.leftBorderVisibility)
|
|
193
|
+
lines.push(`<line x1="${x}" y1="${y}" x2="${x}" y2="${y + height}" stroke="black" />`);
|
|
194
|
+
if (cell.rightBorderVisibility)
|
|
195
|
+
lines.push(`<line x1="${x + width}" y1="${y}" x2="${x + width}" y2="${y + height}" stroke="black" />`);
|
|
196
|
+
const textX = x + width / 2;
|
|
197
|
+
const textY = y + height / 2 + cell.textHeight / 3;
|
|
198
|
+
const text = `<text x="${textX}" y="${textY}" font-size="${cell.textHeight}" text-anchor="middle" dominant-baseline="middle">${cell.text}</text>`;
|
|
199
|
+
return [...lines, text].join('\n');
|
|
200
|
+
})
|
|
201
|
+
.join('\n');
|
|
202
|
+
const totalWidth = columnWidthArr.reduce((sum, w) => sum + w, 0);
|
|
203
|
+
const totalHeight = rowHeightArr.reduce((sum, h) => sum + h, 0);
|
|
204
|
+
const bbox = new Box2D()
|
|
205
|
+
.expandByPoint({ x: originX, y: originY })
|
|
206
|
+
.expandByPoint({ x: originX + totalWidth, y: originY + totalHeight });
|
|
207
|
+
const svg = `
|
|
208
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${totalHeight}" viewBox="${originX} ${originY} ${totalWidth} ${totalHeight}">
|
|
209
|
+
${svgElements}
|
|
210
|
+
</svg>
|
|
211
|
+
`.trim();
|
|
212
|
+
return {
|
|
213
|
+
bbox,
|
|
214
|
+
element: svg
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
text(entity) {
|
|
218
|
+
const fontsize = entity.textHeight;
|
|
219
|
+
const insertionPoint = entity.startPoint;
|
|
220
|
+
const lines = [entity.text];
|
|
221
|
+
let extentsWidth = entity.endPoint.x - entity.endPoint.x;
|
|
222
|
+
if (entity.halign == 0) {
|
|
223
|
+
extentsWidth = entity.text.length * fontsize + entity.startPoint.x;
|
|
224
|
+
}
|
|
225
|
+
let anchor = 'start';
|
|
226
|
+
if (entity.halign == DwgTextHorizontalAlign.CENTER) {
|
|
227
|
+
anchor = 'middle';
|
|
228
|
+
}
|
|
229
|
+
else if (entity.halign == DwgTextHorizontalAlign.RIGHT) {
|
|
230
|
+
anchor = 'end';
|
|
231
|
+
}
|
|
232
|
+
return this.lines(lines, fontsize, insertionPoint, extentsWidth, anchor);
|
|
233
|
+
}
|
|
234
|
+
vertices(vertices, closed = false) {
|
|
235
|
+
const bbox = vertices.reduce((acc, point) => acc.expandByPoint(point), new Box2D());
|
|
236
|
+
let d = vertices.reduce((acc, point, i) => {
|
|
237
|
+
acc += i === 0 ? 'M' : 'L';
|
|
238
|
+
acc += point.x + ',' + point.y;
|
|
239
|
+
return acc;
|
|
240
|
+
}, '');
|
|
241
|
+
if (closed) {
|
|
242
|
+
d += 'Z';
|
|
243
|
+
}
|
|
244
|
+
return { bbox, element: `<path d="${d}" />` };
|
|
245
|
+
}
|
|
246
|
+
circle(entity) {
|
|
247
|
+
const bbox0 = new Box2D()
|
|
248
|
+
.expandByPoint({
|
|
249
|
+
x: entity.center.x + entity.radius,
|
|
250
|
+
y: entity.center.y + entity.radius
|
|
251
|
+
})
|
|
252
|
+
.expandByPoint({
|
|
253
|
+
x: entity.center.x - entity.radius,
|
|
254
|
+
y: entity.center.y - entity.radius
|
|
255
|
+
});
|
|
256
|
+
const element0 = `<circle cx="${entity.center.x}" cy="${entity.center.y}" r="${entity.radius}" />`;
|
|
257
|
+
return {
|
|
258
|
+
bbox: bbox0,
|
|
259
|
+
element: element0
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
ellipseOrArc(cx, cy, majorX, majorY, axisRatio, startAngle, endAngle) {
|
|
263
|
+
const rx = Math.sqrt(majorX * majorX + majorY * majorY);
|
|
264
|
+
const ry = axisRatio * rx;
|
|
265
|
+
const rotationAngle = -Math.atan2(-majorY, majorX);
|
|
266
|
+
const bbox = this.bboxEllipseOrArc(cx, cy, majorX, majorY, axisRatio, startAngle, endAngle);
|
|
267
|
+
if (Math.abs(startAngle - endAngle) < 1e-9 ||
|
|
268
|
+
Math.abs(startAngle - endAngle + Math.PI * 2) < 1e-9) {
|
|
269
|
+
const element = `<g transform="rotate(${(rotationAngle / Math.PI) * 180} ${cx}, ${cy})"><ellipse cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}" /></g>`;
|
|
270
|
+
return { bbox, element };
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const startOffset = this.rotate({ x: Math.cos(startAngle) * rx, y: Math.sin(startAngle) * ry }, rotationAngle);
|
|
274
|
+
const startPoint = { x: cx + startOffset.x, y: cy + startOffset.y };
|
|
275
|
+
const endOffset = this.rotate({ x: Math.cos(endAngle) * rx, y: Math.sin(endAngle) * ry }, rotationAngle);
|
|
276
|
+
const endPoint = { x: cx + endOffset.x, y: cy + endOffset.y };
|
|
277
|
+
const adjustedEndAngle = endAngle < startAngle ? endAngle + Math.PI * 2 : endAngle;
|
|
278
|
+
const largeArcFlag = adjustedEndAngle - startAngle < Math.PI ? 0 : 1;
|
|
279
|
+
const d = `M ${startPoint.x} ${startPoint.y} A ${rx} ${ry} ${(rotationAngle / Math.PI) * 180} ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y}`;
|
|
280
|
+
const element = `<path d="${d}" />`;
|
|
281
|
+
return { bbox, element };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
bboxEllipseOrArc(cx, cy, majorX, majorY, axisRatio, startAngle, endAngle) {
|
|
285
|
+
while (startAngle < 0)
|
|
286
|
+
startAngle += Math.PI * 2;
|
|
287
|
+
while (endAngle <= startAngle)
|
|
288
|
+
endAngle += Math.PI * 2;
|
|
289
|
+
const angles = [];
|
|
290
|
+
if (Math.abs(majorX) < 1e-12 || Math.abs(majorY) < 1e-12) {
|
|
291
|
+
for (let i = 0; i < 4; i++) {
|
|
292
|
+
angles.push((i / 2) * Math.PI);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
angles[0] = Math.atan((-majorY * axisRatio) / majorX) - Math.PI;
|
|
297
|
+
angles[1] = Math.atan((majorX * axisRatio) / majorY) - Math.PI;
|
|
298
|
+
angles[2] = angles[0] - Math.PI;
|
|
299
|
+
angles[3] = angles[1] - Math.PI;
|
|
300
|
+
}
|
|
301
|
+
for (let i = 4; i >= 0; i--) {
|
|
302
|
+
while (angles[i] < startAngle)
|
|
303
|
+
angles[i] += Math.PI * 2;
|
|
304
|
+
if (angles[i] > endAngle) {
|
|
305
|
+
angles.splice(i, 1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
angles.push(startAngle);
|
|
309
|
+
angles.push(endAngle);
|
|
310
|
+
const pts = angles.map(a => ({ x: Math.cos(a), y: Math.sin(a) }));
|
|
311
|
+
const M = [
|
|
312
|
+
[majorX, -majorY * axisRatio],
|
|
313
|
+
[majorY, majorX * axisRatio]
|
|
314
|
+
];
|
|
315
|
+
const rotatedPts = pts.map(p => ({
|
|
316
|
+
x: p.x * M[0][0] + p.y * M[0][1] + cx,
|
|
317
|
+
y: p.x * M[1][0] + p.y * M[1][1] + cy
|
|
318
|
+
}));
|
|
319
|
+
const bbox = rotatedPts.reduce((acc, p) => {
|
|
320
|
+
acc.expandByPoint(p);
|
|
321
|
+
return acc;
|
|
322
|
+
}, new Box2D());
|
|
323
|
+
return bbox;
|
|
324
|
+
}
|
|
325
|
+
ellipse(entity) {
|
|
326
|
+
const { bbox: bbox0, element: element0 } = this.ellipseOrArc(entity.center.x, entity.center.y, entity.majorAxisEndPoint.x, entity.majorAxisEndPoint.y, entity.axisRatio, entity.startAngle, entity.endAngle);
|
|
327
|
+
return {
|
|
328
|
+
bbox: bbox0,
|
|
329
|
+
element: element0
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
arc(entity) {
|
|
333
|
+
const { bbox: bbox0, element: element0 } = this.ellipseOrArc(entity.center.x, entity.center.y, entity.radius, 0, 1, entity.startAngle, entity.endAngle);
|
|
334
|
+
return {
|
|
335
|
+
bbox: bbox0,
|
|
336
|
+
element: element0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
dimension(entity) {
|
|
340
|
+
const block = this.blockMap.get(entity.name);
|
|
341
|
+
if (block) {
|
|
342
|
+
return {
|
|
343
|
+
bbox: block.bbox,
|
|
344
|
+
element: `<use href="#${entity.name}" />`
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
insert(entity) {
|
|
350
|
+
const block = this.blockMap.get(entity.name);
|
|
351
|
+
if (block) {
|
|
352
|
+
// In SVG, the unit of rotate is degrees — not radians.
|
|
353
|
+
const insertionPoint = entity.insertionPoint;
|
|
354
|
+
// const basePoint = block.bbox.min
|
|
355
|
+
const rotation = entity.rotation * (180 / Math.PI);
|
|
356
|
+
const transform = `translate(${insertionPoint.x},${insertionPoint.y}) rotate(${rotation}) scale(${entity.xScale},${entity.yScale})`;
|
|
357
|
+
const newBBox = block.bbox
|
|
358
|
+
.clone()
|
|
359
|
+
.transform({ x: entity.xScale, y: entity.yScale }, { x: insertionPoint.x, y: insertionPoint.y })
|
|
360
|
+
.rotate(entity.rotation, insertionPoint);
|
|
361
|
+
return {
|
|
362
|
+
bbox: newBBox,
|
|
363
|
+
element: `<use href="#${entity.name}" transform="${transform}" />`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
block(block, dwg) {
|
|
369
|
+
const entities = block.entities;
|
|
370
|
+
const { bbox, elements } = entities.reduce((acc, entity) => {
|
|
371
|
+
const boundsAndElement = this.entityToBoundsAndElement(entity);
|
|
372
|
+
if (boundsAndElement) {
|
|
373
|
+
const { bbox, element } = boundsAndElement;
|
|
374
|
+
if (bbox.valid) {
|
|
375
|
+
acc.bbox.expandByPoint(bbox.min);
|
|
376
|
+
acc.bbox.expandByPoint(bbox.max);
|
|
377
|
+
}
|
|
378
|
+
const color = this.getEntityColor(dwg.tables.LAYER.entries, entity);
|
|
379
|
+
const fill = entity.type == 'TEXT' || entity.type == 'MTEXT'
|
|
380
|
+
? color.cssColor
|
|
381
|
+
: 'none';
|
|
382
|
+
if (color.isByBlock) {
|
|
383
|
+
acc.elements.push(`<g id="${entity.handle}">${element}</g>`);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
acc.elements.push(`<g id="${entity.handle}" stroke="${color.cssColor}" fill="${fill}">${element}</g>`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return acc;
|
|
390
|
+
}, {
|
|
391
|
+
bbox: new Box2D(),
|
|
392
|
+
elements: []
|
|
393
|
+
});
|
|
394
|
+
if (bbox.valid) {
|
|
395
|
+
return {
|
|
396
|
+
bbox,
|
|
397
|
+
element: `<g id="${block.name}">${elements.join('\n')}</g>`
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
entityToBoundsAndElement(entity) {
|
|
403
|
+
let result = null;
|
|
404
|
+
switch (entity.type) {
|
|
405
|
+
case 'ARC':
|
|
406
|
+
result = this.arc(entity);
|
|
407
|
+
break;
|
|
408
|
+
case 'CIRCLE':
|
|
409
|
+
result = this.circle(entity);
|
|
410
|
+
break;
|
|
411
|
+
case 'DIMENSION':
|
|
412
|
+
result = this.dimension(entity);
|
|
413
|
+
break;
|
|
414
|
+
case 'ELLIPSE':
|
|
415
|
+
result = this.ellipse(entity);
|
|
416
|
+
break;
|
|
417
|
+
case 'INSERT':
|
|
418
|
+
result = this.insert(entity);
|
|
419
|
+
break;
|
|
420
|
+
case 'LINE':
|
|
421
|
+
result = this.line(entity);
|
|
422
|
+
break;
|
|
423
|
+
case 'LWPOLYLINE': {
|
|
424
|
+
const lwpolyline = entity;
|
|
425
|
+
const closed = !!(lwpolyline.flag & 0x200);
|
|
426
|
+
const vertices = interpolatePolyline(lwpolyline, closed);
|
|
427
|
+
result = this.vertices(vertices, closed);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case 'MTEXT':
|
|
431
|
+
result = this.mtext(entity);
|
|
432
|
+
break;
|
|
433
|
+
case 'SPLINE': {
|
|
434
|
+
const spline = entity;
|
|
435
|
+
result = this.vertices(this.interpolateBSpline(spline.controlPoints, spline.degree, spline.knots, 25, spline.weights));
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case 'POLYLINE': {
|
|
439
|
+
// const polyline = entity as DwgPolylineEntity
|
|
440
|
+
// const closed = !!(polyline.flag & 0x1)
|
|
441
|
+
// const vertices = interpolatePolyline(polyline, closed)
|
|
442
|
+
// result = this.vertices(vertices, closed)
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
case 'RAY':
|
|
446
|
+
result = this.ray(entity);
|
|
447
|
+
break;
|
|
448
|
+
case 'TABLE':
|
|
449
|
+
case 'ACAD_TABLE':
|
|
450
|
+
result = this.table(entity);
|
|
451
|
+
break;
|
|
452
|
+
case 'TEXT':
|
|
453
|
+
result = this.text(entity);
|
|
454
|
+
break;
|
|
455
|
+
case 'XLINE':
|
|
456
|
+
result = this.xline(entity);
|
|
457
|
+
break;
|
|
458
|
+
default:
|
|
459
|
+
result = null;
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
if (result) {
|
|
463
|
+
return this.addFlipXIfApplicable(entity, result);
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
getEntityColor(layers, entity) {
|
|
468
|
+
// Get entity color
|
|
469
|
+
const color = new Color();
|
|
470
|
+
if (entity.colorIndex != null) {
|
|
471
|
+
color.colorIndex = entity.colorIndex;
|
|
472
|
+
}
|
|
473
|
+
else if (entity.colorName) {
|
|
474
|
+
color.colorName = entity.colorName;
|
|
475
|
+
}
|
|
476
|
+
else if (entity.color != null) {
|
|
477
|
+
color.color = entity.color;
|
|
478
|
+
}
|
|
479
|
+
// ACI 7 is one background-dependent color
|
|
480
|
+
if (color.colorIndex == 7) {
|
|
481
|
+
color.colorIndex = 256;
|
|
482
|
+
}
|
|
483
|
+
// If color is 'byLayer', use the layer color
|
|
484
|
+
if (color.isByLayer) {
|
|
485
|
+
const layer = layers.find((layer) => layer.name === entity.layer);
|
|
486
|
+
if (layer != null) {
|
|
487
|
+
color.colorIndex = layer.colorIndex;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (color.color == null) {
|
|
491
|
+
color.color = 0xffffff;
|
|
492
|
+
}
|
|
493
|
+
return color;
|
|
494
|
+
}
|
|
495
|
+
convert(dwg) {
|
|
496
|
+
let modelSpace = null;
|
|
497
|
+
this.blockMap.clear();
|
|
498
|
+
let blockElements = '';
|
|
499
|
+
dwg.tables.BLOCK_RECORD.entries.forEach(block => {
|
|
500
|
+
if (isModelSpace(block.name)) {
|
|
501
|
+
modelSpace = block;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
const item = this.block(block, dwg);
|
|
505
|
+
if (item) {
|
|
506
|
+
blockElements += item.element;
|
|
507
|
+
this.blockMap.set(block.name, item);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
const ms = this.block(modelSpace, dwg);
|
|
512
|
+
const viewBox = ms && ms.bbox.valid
|
|
513
|
+
? {
|
|
514
|
+
x: ms.bbox.min.x,
|
|
515
|
+
y: -ms.bbox.max.y,
|
|
516
|
+
width: ms.bbox.max.x - ms.bbox.min.x,
|
|
517
|
+
height: ms.bbox.max.y - ms.bbox.min.y
|
|
518
|
+
}
|
|
519
|
+
: {
|
|
520
|
+
x: 0,
|
|
521
|
+
y: 0,
|
|
522
|
+
width: 0,
|
|
523
|
+
height: 0
|
|
524
|
+
};
|
|
525
|
+
return `<?xml version="1.0"?>
|
|
526
|
+
<svg
|
|
527
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
528
|
+
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
|
529
|
+
preserveAspectRatio="xMinYMin meet"
|
|
530
|
+
viewBox="${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}"
|
|
531
|
+
width="100%" height="100%"
|
|
532
|
+
>
|
|
533
|
+
<defs>${blockElements}</defs>
|
|
534
|
+
<g stroke="#000000" stroke-width="0.1%" fill="none" transform="matrix(1,0,0,-1,0,0)">
|
|
535
|
+
${ms ? ms.element : ''}
|
|
536
|
+
</g>
|
|
537
|
+
</svg>`;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
//# sourceMappingURL=svgConverter.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const transformBoundingBoxAndElement = (bbox, element, transforms = undefined) => {
|
|
2
|
+
if (!transforms || transforms.length === 0) {
|
|
3
|
+
return { bbox, element };
|
|
4
|
+
}
|
|
5
|
+
let transformedElement = element;
|
|
6
|
+
transforms.forEach(transform => {
|
|
7
|
+
// Apply each transformation to the element
|
|
8
|
+
if (transform.type === 'translate') {
|
|
9
|
+
transformedElement = `<g transform="translate(${transform.x}, ${transform.y})">${transformedElement}</g>`;
|
|
10
|
+
}
|
|
11
|
+
else if (transform.type === 'rotate') {
|
|
12
|
+
transformedElement = `<g transform="rotate(${transform.angle})">${transformedElement}</g>`;
|
|
13
|
+
}
|
|
14
|
+
else if (transform.type === 'scale') {
|
|
15
|
+
transformedElement = `<g transform="scale(${transform.x}, ${transform.y})">${transformedElement}</g>`;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
return { bbox, element: transformedElement };
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=transformBoundingBoxAndElement.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class Vector2D {
|
|
2
|
+
x;
|
|
3
|
+
y;
|
|
4
|
+
constructor(x, y) {
|
|
5
|
+
this.x = x;
|
|
6
|
+
this.y = y;
|
|
7
|
+
}
|
|
8
|
+
add(v) {
|
|
9
|
+
return new Vector2D(this.x + v.x, this.y + v.y);
|
|
10
|
+
}
|
|
11
|
+
sub(v) {
|
|
12
|
+
return new Vector2D(this.x - v.x, this.y - v.y);
|
|
13
|
+
}
|
|
14
|
+
multiply(scalar) {
|
|
15
|
+
return new Vector2D(this.x * scalar, this.y * scalar);
|
|
16
|
+
}
|
|
17
|
+
length() {
|
|
18
|
+
return Math.sqrt(this.x ** 2 + this.y ** 2);
|
|
19
|
+
}
|
|
20
|
+
norm() {
|
|
21
|
+
const len = this.length();
|
|
22
|
+
if (len === 0)
|
|
23
|
+
return new Vector2D(0, 0);
|
|
24
|
+
return new Vector2D(this.x / len, this.y / len);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=vector.js.map
|