ofs-dxf-ts 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.
Files changed (97) hide show
  1. package/API.md +411 -0
  2. package/CHANGELOG.md +26 -0
  3. package/LICENSE +21 -0
  4. package/README.md +119 -0
  5. package/dist/core/dxf-builder.d.ts +348 -0
  6. package/dist/core/dxf-builder.js +549 -0
  7. package/dist/core/dxf-document.d.ts +220 -0
  8. package/dist/core/dxf-document.js +529 -0
  9. package/dist/core/dxf-entities.d.ts +327 -0
  10. package/dist/core/dxf-entities.js +22 -0
  11. package/dist/core/dxf-serializer.d.ts +48 -0
  12. package/dist/core/dxf-serializer.js +595 -0
  13. package/dist/core/hatch-patterns.d.ts +15 -0
  14. package/dist/core/hatch-patterns.js +214 -0
  15. package/dist/core/modelspace.d.ts +106 -0
  16. package/dist/core/modelspace.js +351 -0
  17. package/dist/geometry/math.utils.d.ts +8 -0
  18. package/dist/geometry/math.utils.js +43 -0
  19. package/dist/geometry/offset.utils.d.ts +32 -0
  20. package/dist/geometry/offset.utils.js +87 -0
  21. package/dist/geometry/polyline.utils.d.ts +10 -0
  22. package/dist/geometry/polyline.utils.js +63 -0
  23. package/dist/geometry/text.utils.d.ts +15 -0
  24. package/dist/geometry/text.utils.js +28 -0
  25. package/dist/index.d.ts +23 -0
  26. package/dist/index.js +29 -0
  27. package/dist/parser/auto-cad-color-index.d.ts +8 -0
  28. package/dist/parser/auto-cad-color-index.js +265 -0
  29. package/dist/parser/dim-style-codes.d.ts +5 -0
  30. package/dist/parser/dim-style-codes.js +32 -0
  31. package/dist/parser/dxf-parser.d.ts +16 -0
  32. package/dist/parser/dxf-parser.js +824 -0
  33. package/dist/parser/entities/3dface.d.ts +5 -0
  34. package/dist/parser/entities/3dface.js +76 -0
  35. package/dist/parser/entities/arc.d.ts +5 -0
  36. package/dist/parser/entities/arc.js +34 -0
  37. package/dist/parser/entities/attdef.d.ts +5 -0
  38. package/dist/parser/entities/attdef.js +86 -0
  39. package/dist/parser/entities/attribute.d.ts +5 -0
  40. package/dist/parser/entities/attribute.js +106 -0
  41. package/dist/parser/entities/circle.d.ts +5 -0
  42. package/dist/parser/entities/circle.js +40 -0
  43. package/dist/parser/entities/dimension.d.ts +5 -0
  44. package/dist/parser/entities/dimension.js +65 -0
  45. package/dist/parser/entities/ellipse.d.ts +5 -0
  46. package/dist/parser/entities/ellipse.js +42 -0
  47. package/dist/parser/entities/hatch.d.ts +5 -0
  48. package/dist/parser/entities/hatch.js +321 -0
  49. package/dist/parser/entities/insert.d.ts +5 -0
  50. package/dist/parser/entities/insert.js +52 -0
  51. package/dist/parser/entities/line.d.ts +5 -0
  52. package/dist/parser/entities/line.js +30 -0
  53. package/dist/parser/entities/lwpolyline.d.ts +5 -0
  54. package/dist/parser/entities/lwpolyline.js +101 -0
  55. package/dist/parser/entities/mtext.d.ts +5 -0
  56. package/dist/parser/entities/mtext.js +54 -0
  57. package/dist/parser/entities/point.d.ts +5 -0
  58. package/dist/parser/entities/point.js +30 -0
  59. package/dist/parser/entities/polyline.d.ts +5 -0
  60. package/dist/parser/entities/polyline.js +85 -0
  61. package/dist/parser/entities/solid.d.ts +5 -0
  62. package/dist/parser/entities/solid.js +35 -0
  63. package/dist/parser/entities/spline.d.ts +5 -0
  64. package/dist/parser/entities/spline.js +69 -0
  65. package/dist/parser/entities/text.d.ts +5 -0
  66. package/dist/parser/entities/text.js +47 -0
  67. package/dist/parser/entities/vertex.d.ts +5 -0
  68. package/dist/parser/entities/vertex.js +59 -0
  69. package/dist/parser/extended-data-parser.d.ts +18 -0
  70. package/dist/parser/extended-data-parser.js +100 -0
  71. package/dist/parser/index.d.ts +10 -0
  72. package/dist/parser/index.js +9 -0
  73. package/dist/parser/log.d.ts +14 -0
  74. package/dist/parser/log.js +39 -0
  75. package/dist/parser/parse-helpers.d.ts +23 -0
  76. package/dist/parser/parse-helpers.js +128 -0
  77. package/dist/parser/scanner.d.ts +29 -0
  78. package/dist/parser/scanner.js +143 -0
  79. package/dist/parser/types.d.ts +78 -0
  80. package/dist/parser/types.js +5 -0
  81. package/dist/symbols/slope-symbol.d.ts +21 -0
  82. package/dist/symbols/slope-symbol.js +81 -0
  83. package/dist/symbols/structures.d.ts +9 -0
  84. package/dist/symbols/structures.js +120 -0
  85. package/dist/template/blank-document.d.ts +13 -0
  86. package/dist/template/blank-document.js +13 -0
  87. package/dist/template/block-registry.d.ts +16 -0
  88. package/dist/template/block-registry.js +135 -0
  89. package/dist/template/index.d.ts +17 -0
  90. package/dist/template/index.js +17 -0
  91. package/dist/template/template-injector.d.ts +45 -0
  92. package/dist/template/template-injector.js +487 -0
  93. package/dist/template/template-reader.d.ts +28 -0
  94. package/dist/template/template-reader.js +80 -0
  95. package/dist/types/index.d.ts +24 -0
  96. package/dist/types/index.js +8 -0
  97. package/package.json +59 -0
package/API.md ADDED
@@ -0,0 +1,411 @@
1
+ # คู่มือการใช้งาน (API Reference)
2
+
3
+ `ofs-dxf-ts` — ไลบรารี TypeScript บริสุทธิ์ (pure, API-agnostic, browser-first) สำหรับสร้างไฟล์ DXF จาก coordinate / label / options ทั่วไป โดยจะวาดบน DXF template หรือสร้างจากศูนย์ก็ได้ ทำงานได้ทั้ง **Node.js** และ **Browser/Angular**
4
+
5
+ > เอกสารนี้อิงตาม public API ที่ export จากแพ็กเกจ (`import { ... } from 'ofs-dxf-ts'`) — type declaration ทั้งหมดมากับแพ็กเกจแล้ว
6
+
7
+ ---
8
+
9
+ ## 1. เริ่มต้นใช้งาน (Quick Start)
10
+
11
+ ```typescript
12
+ import { DxfBuilder } from 'ofs-dxf-ts';
13
+ // หรือระหว่างพัฒนาในโปรเจกต์นี้: import { DxfBuilder } from './src';
14
+ ```
15
+
16
+ ### สร้างจากศูนย์ (ไม่มี template)
17
+
18
+ ```typescript
19
+ const builder = new DxfBuilder();
20
+
21
+ builder.addPolyline([[0, 0], [100, 50], [200, 0]], 'CANAL', 1);
22
+ builder.addCircle(50, 50, 25, 'CANAL', 3);
23
+ builder.addText('KM 0+500', 10, 10, 2.5, 0, 'TEXT', 7);
24
+
25
+ const dxf = builder.saveString();
26
+ ```
27
+
28
+ ไฟล์ที่ได้เป็น DXF R2018 (AC1032) หน่วยเมตร โครงสร้างครบ (CLASSES, BLOCK_RECORD, OBJECTS, layouts) เพราะสร้างบนเอกสารเปล่าที่ฝังใน lib — จึงใช้ block, image และ layout ได้เหมือนโหมด template
29
+
30
+ ### สร้างบน DXF template
31
+
32
+ | Environment | วิธีโหลด template |
33
+ |-------------|------------------|
34
+ | **Node.js** | `new DxfBuilder('/path/to/template.dxf')` |
35
+ | **Browser/Angular** | `DxfBuilder.fromString(dxfText)` — โหลดผ่าน `HttpClient` แล้วส่ง text เข้ามา |
36
+
37
+ ```typescript
38
+ // Node.js
39
+ const builder = new DxfBuilder('./template2018.dxf');
40
+
41
+ // Browser/Angular (เช่นใน service)
42
+ const dxfText = await firstValueFrom(http.get('assets/template2018.dxf', { responseType: 'text' }));
43
+ const builder = DxfBuilder.fromString(dxfText);
44
+ ```
45
+
46
+ ทุก section ของ template ถูกคงไว้ตามลำดับเดิม ส่วนที่เพิ่มใหม่ (layer, linetype, style, block, image, layout) จะถูกแทรกเข้าไป — ถ้าชื่อซ้ำกับของเดิมใน template จะใช้ของเดิม (ยกเว้น block / layout ที่ซ้ำจะ `throw`)
47
+
48
+ > ⚠️ ต้องเป็น template DXF R2000 ขึ้นไป (มี BLOCK_RECORD table และ OBJECTS section) จึงจะใช้ block definition, image และ layout ได้ ไฟล์แบบ R12 ยังเพิ่ม layer / entity ได้ตามปกติ
49
+
50
+ ### บันทึก / ส่งออก
51
+
52
+ ```typescript
53
+ builder.saveString(); // → string (ทุก environment)
54
+ builder.saveas('out.dxf'); // เขียนไฟล์ (Node.js เท่านั้น)
55
+ builder.downloadAs('out.dxf'); // trigger download (Browser เท่านั้น)
56
+ ```
57
+
58
+ `saveString()` เรียกซ้ำได้และให้ผลเหมือนเดิมทุกครั้ง
59
+
60
+ > ℹ️ `saveas()` / `DxfDocument.readFile()` ใช้ `fs` ของ Node ผ่าน `process.getBuiltinModule` จึงต้องใช้ **Node.js ≥ 20.16** ถ้ารันบน runtime อื่น (browser, worker, Node เก่ากว่านั้น) ให้ใช้ `saveString()` แล้วเขียนไฟล์เอง — แพ็กเกจนี้เป็น ESM อย่างเดียว
61
+
62
+ ---
63
+
64
+ ## 2. `DxfBuilder` — API หลัก
65
+
66
+ ทุก method ที่รับ `layer` จะสร้าง layer อัตโนมัติถ้ายังไม่มี (สี 7, `CONTINUOUS`)
67
+
68
+ ### การสร้าง / เข้าถึงภายใน
69
+
70
+ | Member | คำอธิบาย |
71
+ |--------|----------|
72
+ | `new DxfBuilder(template?: string \| DxfDocument)` | สร้างใหม่, โหลดจากไฟล์ (Node) หรือห่อ `DxfDocument` ที่มีอยู่ |
73
+ | `static DxfBuilder.fromString(content)` | โหลด template จาก DXF string |
74
+ | `get doc: DxfDocument` | เข้าถึง document |
75
+ | `get msp: Modelspace` | ปลายทางการวาด **ปัจจุบัน** (model space, layout ใน `onLayout`, หรือ block ใน `defineBlock`) สำหรับเรียก API สไตล์ ezdxf เช่น `builder.msp.add_text(...)` |
76
+
77
+ ### Geometry
78
+
79
+ ```typescript
80
+ addLine(start, end, layer?='0', color?, linetype?)
81
+ addPolyline(points, layer?='0', color?, linetype?, closed?=false) // LWPOLYLINE; รับ [x,y] หรือ [x,y,z]
82
+ addCircle(cx, cy, radius, layer?='0', color?)
83
+ addArc(cx, cy, radius, startAngleDeg, endAngleDeg, layer?='0', color?)
84
+ addEllipse(cx, cy, majorAxisX, majorAxisY, ratio, layer?='0', color?) // ratio = minor/major (0..1)
85
+ addPoint(x, y, layer?='0', color?)
86
+ addSpline(controlPoints, degree?=3, layer?='0', color?)
87
+ ```
88
+
89
+ ### Text
90
+
91
+ ```typescript
92
+ addText(text, x, y, height?=2, rotationDeg?=0, layer?='TEXT', color?=7)
93
+ addMText(text, x, y, width?=100, height?=2.5, layer?='TEXT', color?) // "\n" = ขึ้นบรรทัดใหม่
94
+ ```
95
+
96
+ ต้องการ text style หรือการจัดตำแหน่ง (alignment) ให้ใช้ `builder.msp.add_text(text, { height, style, rotation, layer }).set_placement([x, y], TextEntityAlignment.BOTTOM_CENTER)`
97
+
98
+ ### Resources: linetype / text style / layer
99
+
100
+ ```typescript
101
+ addLayer(name, color?=7, linetype?='CONTINUOUS')
102
+ addLineType(name, description, pattern) // pattern หน่วยเดียวกับแบบ: + เส้น, − ช่องว่าง, 0 จุด
103
+ addTextStyle(name, font, height?) // เช่น addTextStyle('THAI', 'THSarabunNew.ttf')
104
+ ```
105
+
106
+ ```typescript
107
+ builder.addLineType('BOUNDARY', '____ . ____', [40, -10, 0, -10]);
108
+ builder.addLayer('ขอบเขตพื้นที่รับประโยชน์', 6, 'BOUNDARY');
109
+ ```
110
+
111
+ ### Block
112
+
113
+ ```typescript
114
+ defineBlock(name, draw: (b: DxfBuilder) => void, basePoint?=[0,0])
115
+ addBlock(name, x, y, scale, rotationDeg, layer?='0', attributes?={})
116
+ ```
117
+
118
+ - `defineBlock` — ทุกคำสั่งใน callback วาดลง block definition (พิกัดเทียบ `basePoint`) วาด entity บน layer `'0'` สีไม่ระบุ เพื่อให้ INSERT รับสี/layer ของตัวที่วาง
119
+ - `addBlock` — ลำดับการ resolve: **block ใน template หรือที่ `defineBlock` ไว้** → INSERT, **block ใน `BLOCK_REGISTRY`** → วาดแยกเป็นเส้น, ไม่พบ → ข้อความ `[Block:name]`
120
+ - `attributes` ถูกวาดเป็นข้อความธรรมดาข้าง block (ยังไม่รองรับ ATTRIB)
121
+
122
+ ```typescript
123
+ builder.defineBlock('BM', b => {
124
+ b.addPolyline([[-1, -1], [1, -1], [1, 1], [-1, 1]], '0', undefined, undefined, true);
125
+ b.addHatch('SOLID', [[[0, 0], [1, 0], [1, 1], [0, 1]]], '0');
126
+ });
127
+ builder.addBlock('BM', 519395.838, 1034018.584, 20, 0, 'SYMBOL');
128
+ ```
129
+
130
+ ### Hatch
131
+
132
+ ```typescript
133
+ addHatch(patternName, boundaries, layer?='0', color?, opts?)
134
+ // patternName: 'SOLID' | ชื่อลายใน HATCH_PATTERNS | ชื่อใดก็ได้ถ้าส่ง opts.patternLines
135
+ // boundaries : [x,y][][] — รูปที่อยู่ในรูปอื่นกลายเป็นรู (odd parity)
136
+ // opts : { scale?, angle?, patternLines? }
137
+ ```
138
+
139
+ - ลายในตัวเป็นหน่วย ISO (มม.) — `scale` คูณเข้ากับลาย เช่นแบบเป็นเมตรที่พิมพ์ 1:12,500 ลาย `DOTS` ใช้ `scale ≈ 8`
140
+ - ลายในตัว: `ANGLE, ANSI31–ANSI38, AR-CONC, AR-SAND, BOX, BRICK, CROSS, DASH, DOTS, EARTH, GRASS, GRATE, GRAVEL, HEX, HONEY, INSUL, LINE, NET, NET3, PLAST, SQUARE, STARS, SWAMP, TRIANG, ZIGZAG`
141
+ - ชื่อที่ไม่รู้จักโดยไม่มี `patternLines` จะ `throw`
142
+
143
+ ```typescript
144
+ builder.addHatch('SOLID', [[[0,0],[100,0],[100,100],[0,100]]], 'WATER', 5);
145
+ builder.addHatch('ANSI31', [outer, hole], 'AREA', 7, { scale: 10, angle: 45 });
146
+
147
+ // ลายกำหนดเอง: [angleDeg, [baseX, baseY], [offsetX, offsetY], dashes]
148
+ builder.addHatch('MY_DOTS', [outline], 'AREA', 3, { patternLines: [[0, [0, 0], [2, 2], [0, -2]]] });
149
+ ```
150
+
151
+ ### Raster image
152
+
153
+ ```typescript
154
+ addImage(imagePath, pixelSize, insertion, worldSize, layer?='0', opts?: ImageOptions)
155
+ // pixelSize : [กว้าง, สูง] หน่วย pixel (DXF อ่านไฟล์ภาพเองไม่ได้)
156
+ // insertion : มุมล่างซ้ายของภาพ (หน่วยแบบ)
157
+ // worldSize : [กว้าง, สูง] หน่วยแบบ
158
+ // opts : { rotation?, clip?: [x,y][], brightness?, contrast?, fade? }
159
+ ```
160
+
161
+ DXF เก็บเพียง **การอ้างอิงไฟล์** — วางไฟล์ภาพไว้ข้าง `.dxf` (ใช้ชื่อไฟล์) หรือใช้ absolute path; ถ้าหาไฟล์ไม่เจอ CAD จะแสดงเพียงกรอบ `clip` ใช้พิกัดโลก
162
+
163
+ ```typescript
164
+ builder.addImage('topo_4927II.jpg', [7087, 7087], [513000, 1027000], [15000, 15000], 'RASTER', { fade: 40 });
165
+ ```
166
+
167
+ ### Annotation: callout และตาราง
168
+
169
+ ```typescript
170
+ addCallout(anchor, text: string | string[], boxAt, opts?: CalloutOptions): [minX, minY, maxX, maxY]
171
+ ```
172
+
173
+ วาดกรอบข้อความ (มุมบนซ้ายที่ `boxAt`) และเส้นชี้จาก `anchor` ไปยังจุดกึ่งกลางขอบกล่องที่ใกล้ที่สุด คืนค่าขอบเขตกล่อง
174
+
175
+ | `CalloutOptions` | ค่าปริยาย | คำอธิบาย |
176
+ |---|---|---|
177
+ | `layer`, `color`, `textColor`, `boxColor`, `style` | `'TEXT'` | |
178
+ | `textHeight` | `2.5` | ความสูงตัวอักษร (หน่วยแบบ) |
179
+ | `lineSpacing` | `1.6` | ระยะบรรทัด × ความสูงตัวอักษร |
180
+ | `padding` | `0.8 × textHeight` | |
181
+ | `boxWidth` | ประมาณจากข้อความ | กำหนดความกว้างเอง |
182
+ | `widthFactor` | `0.75` | ความกว้างเฉลี่ยตัวอักษร / ความสูง (ฟอนต์ไทย ≈ 0.6–0.7) |
183
+ | `align` | `'left'` | `'left' \| 'center'` |
184
+ | `arrow`, `arrowSize` | `'none'` | `'none' \| 'dot' \| 'arrow'` |
185
+
186
+ ```typescript
187
+ addTable(position, columnWidths, rowHeight, rows, layer?='0', textHeight?=2.5, opts?)
188
+ // rows : TableCellDef[][] — { text, colSpan?, rowSpan?, alignment?, color?, style? }
189
+ // opts : { headerColor?, gridColor?, textColor?, rowHeights? }
190
+ ```
191
+
192
+ การรวมเซลล์ใช้กติกาแบบ HTML table: เซลล์ที่ span จะกินตำแหน่งทางขวา/ด้านล่าง และเซลล์ถัดไปจะข้ามตำแหน่งที่ถูกกินไปแล้ว ตารางถูกวาดเป็น LINE + TEXT (ไม่ใช่ ACAD_TABLE)
193
+
194
+ ```typescript
195
+ builder.addTable([650, 300], [14, 26, 42, 42, 22], 7, [
196
+ [{ text: 'ลำดับ', rowSpan: 2 }, { text: 'หมุด', rowSpan: 2 }, { text: 'พิกัด', colSpan: 2 }, { text: 'ค่าระดับ', rowSpan: 2 }],
197
+ [{ text: 'North' }, { text: 'East' }],
198
+ [{ text: '1' }, { text: 'BM.07' }, { text: '1,034,018.584' }, { text: '519,395.838' }, { text: '4.622' }],
199
+ ], 'TABLE', 3);
200
+ ```
201
+
202
+ อื่น ๆ:
203
+
204
+ ```typescript
205
+ addDimension(point1, point2, dimLinePosition, layer?='DIM', color?, text?)
206
+ addLeader(vertices, layer?='0', color?, hasArrowhead?=true)
207
+ addMLeader(vertices, text, textPosition, layer?, color?, opts?) // ⚠️ deprecated → วาดด้วย addCallout
208
+ addLineFromObject(onfarmLine, layer?='structure') // วาด OnfarmLine (ดูหัวข้อ 4)
209
+ ```
210
+
211
+ ### Paper space layout
212
+
213
+ ```typescript
214
+ addLayout(name, paperWidth, paperHeight, margins?=[10,10,10,10], viewports?: LayoutViewportDef[])
215
+ onLayout(name, draw: (b: DxfBuilder) => void)
216
+ ```
217
+
218
+ - หน่วยกระดาษเป็น **มม.** จุดกำเนิดพิกัดอยู่ที่มุมล่างซ้ายของพื้นที่พิมพ์ (ขอบใน margin) ตามแบบ AutoCAD — ใช้ `margins: [0,0,0,0]` ถ้าต้องการให้ตรงกับมุมกระดาษ
219
+ - `onLayout` — ทุกคำสั่งใน callback วาดลง paper space ใช้ได้ทั้ง layout ของ template (`'Layout1'`) และที่สร้างด้วย `addLayout`; ชื่อ `'Model'` = model space; ชื่อที่ไม่มีจะ `throw`
220
+
221
+ | `LayoutViewportDef` | คำอธิบาย |
222
+ |---|---|
223
+ | `center`, `width`, `height` | ตำแหน่ง/ขนาดบนกระดาษ (มม.) |
224
+ | `viewCenter` | จุดกึ่งกลางที่มองใน model space |
225
+ | `viewHeight?` | ความสูงที่มองเห็นใน model space — ถ้าไม่ระบุคำนวณจาก `scale` |
226
+ | `scale?` | ตัวหารมาตราส่วน เช่น `12500` = 1:12,500 → `viewHeight = height × scale × unitFactor` |
227
+ | `unitFactor?` | หน่วย model ต่อ 1 มม. กระดาษที่ 1:1 — ค่าปริยาย `0.001` (model เป็นเมตร) |
228
+ | `frozenLayers?` | layer ที่ซ่อนเฉพาะ viewport นี้ |
229
+ | `locked?` | ล็อก viewport ไม่ให้ zoom/pan เปลี่ยนมาตราส่วน |
230
+
231
+ ```typescript
232
+ builder.addLayout('BM-A1', 841, 594, [0, 0, 0, 0], [
233
+ { center: [330, 330], width: 600, height: 440, viewCenter: [519000, 1033500], scale: 12500, locked: true },
234
+ ]);
235
+ builder.onLayout('BM-A1', s => s.addText('แผนที่แสดงตำแหน่งหมุดหลักฐาน', 250, 70, 9, 0, 'SHEET'));
236
+ ```
237
+
238
+ ### View / Zoom
239
+
240
+ ```typescript
241
+ zoomExtents(padding?=0) // auto-fit entity ใน model space ($EXTMIN/$EXTMAX/$VIEWCTR/$VIEWSIZE)
242
+ zoomWindow(minX, minY, maxX, maxY)
243
+ setExtents(minX, minY, maxX, maxY)
244
+ setViewCenter(x, y, viewHeight?)
245
+ ```
246
+
247
+ ### Layer control
248
+
249
+ ```typescript
250
+ setLayerColor(name, color)
251
+ setLayerLinetype(name, linetype)
252
+ freezeLayer(name) / thawLayer(name) // ซ่อน + ไม่ plot + เลือกไม่ได้
253
+ lockLayer(name) / unlockLayer(name) // เห็นแต่แก้ไม่ได้
254
+ setLayerOff(name) / setLayerOn(name) // ซ่อนแต่ยังเลือกได้
255
+ ```
256
+
257
+ > ⚠️ คำสั่งเหล่านี้มีผลกับ layer ที่สร้างใหม่เท่านั้น — layer ที่มีอยู่แล้วใน template ถูกเขียนออกตามเดิม
258
+
259
+ ### GeoReference
260
+
261
+ ```typescript
262
+ builder.setGeoReference({
263
+ crsId: 'EPSG:32647', // UTM Zone 47N
264
+ designPoint: [0, 0],
265
+ referencePoint: [600000, 1700000],
266
+ northDirection: 0,
267
+ scaleFactor: 1,
268
+ coordinateType: 2,
269
+ });
270
+ ```
271
+
272
+ > ⚠️ ปัจจุบันเขียน `$INSUNITS`, `$UCSORG` (= `referencePoint`) และ `$ANGBASE` ลง HEADER — `$UCSORG` อาจทำให้ CAD แสดงพิกัดสัมพัทธ์กับจุดนั้น ถ้าแบบวาดด้วยพิกัด UTM จริงอยู่แล้วไม่จำเป็นต้องเรียก
273
+
274
+ ---
275
+
276
+ ## 3. `DxfDocument` และ `Modelspace` (ระดับล่าง)
277
+
278
+ ```typescript
279
+ DxfDocument.create() // เอกสารเปล่า R2018
280
+ DxfDocument.fromString(dxfText) // จาก template string
281
+ DxfDocument.readFile(path) // Node.js
282
+
283
+ doc.modelspace(): Modelspace
284
+ doc.paperspace(layoutName): Modelspace // collector ของ layout
285
+ doc.addLayer / addLineType / addTextStyle
286
+ doc.addBlockDefinition(name, entities: DxfEntity[], basePoint?)
287
+ doc.addLayout(layoutDef)
288
+ doc.layoutNames / hasLayout(name) / hasBlock(name) / getBlockNames()
289
+ doc.setHeaderVar(name, [{ code, value }]) // แทนค่าเดิมใน HEADER
290
+ doc.toDxfString() / saveas(path) / downloadAs(filename)
291
+ ```
292
+
293
+ `Modelspace` เป็นตัวเก็บ entity แบบ ezdxf (`add_line`, `add_lwpolyline`, `add_text(...).set_placement(...)`, `add_hatch`, `add_image`, `add_table`, `add_viewport`, `add_blockref` …) ใช้ทั้งกับ model space, layout และ block
294
+
295
+ ```typescript
296
+ const doc = DxfDocument.create();
297
+ doc.modelspace().add_text('ศูนย์กลาง', { height: 3, style: 'THAI', layer: 'NOTE' })
298
+ .set_placement([50, 50], TextEntityAlignment.MIDDLE_CENTER);
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 4. ตัวสร้างสัญลักษณ์ (Symbol generators)
304
+
305
+ ฟังก์ชันบริสุทธิ์ — **คืนค่า geometry ไม่วาดเอง**
306
+
307
+ ### อาคารคลอง → `Record<string, OnfarmLine>`
308
+
309
+ ```typescript
310
+ objCheck(xyCord, kmUS)
311
+ objCheckDrop(xyCord, kmUS)
312
+ objChangeSlopeStructure(xyCord, kmUS)
313
+ objFlume(xyCord, kmUS, kmDS, centerline)
314
+ objSiphon(xyCord, kmUS, kmDS, centerline)
315
+ objCulvert(xyCord, kmUS, kmDS, roadLength)
316
+ objCheckCulvert(xyCord, kmUS, kmDS, roadLength)
317
+ objCheckDropCulvert(xyCord, kmUS, kmDS, roadLength)
318
+ ```
319
+
320
+ ```typescript
321
+ const struc = objCheck([[0, 0], [1000, 0]], 500);
322
+ for (const key in struc) builder.addLineFromObject(struc[key]);
323
+ ```
324
+
325
+ ### สัญลักษณ์ลาด/สันคันนา → segments
326
+
327
+ ```typescript
328
+ slopeSymbolSegments(line, interval, side, halfLen, size): [Coordinate, Coordinate][]
329
+ ```
330
+
331
+ ```typescript
332
+ const segs = slopeSymbolSegments(ridgeLine, 20, 'L', 2, 4);
333
+ for (const [start, end] of segs) builder.addLine(start, end, 'SLOPE_SYMBOL', 8);
334
+ ```
335
+
336
+ ---
337
+
338
+ ## 5. ยูทิลิตี้ (Geometry / Text)
339
+
340
+ ```typescript
341
+ // math.utils
342
+ formatDistance(distance) // 1500 → 'KM 1+500'
343
+ calculateDistance(p1, p2)
344
+ calculateAzimuth(x1, y1, x2, y2) // วัดจากแกน Y ตามเข็ม 0..360
345
+ rotatePoint(x, y, angleDeg, shiftX, shiftY)
346
+ rotatePointAzimuth(x, y, azimuthDeg, shiftX, shiftY)
347
+ isPointInBounds(x, y, xmin, ymin, xmax, ymax)
348
+ snapBound(value, step, direction?='down')
349
+
350
+ // polyline.utils
351
+ kmPointPolyline(points, targetDistance) // → { point, az }
352
+ dividePolylineWithDistance(coords, segmentLength=100) // → { points, distances, angles }
353
+
354
+ // offset.utils (ระนาบ หน่วยเดียวกับแบบ — UTM เมตร)
355
+ getLineOffset(points, offsetDistance=1, opts?) // → { left, right } เส้นขนานสองข้าง z=0 (เช่นขอบคลอง)
356
+ offsetPolyline(points, distance, opts?) // ข้างเดียว: distance บวก = ซ้ายของทิศทางเส้น, ลบ = ขวา
357
+ // opts: { closed?, miterLimit?=4 }
358
+ // มุมต่อกันที่จุดตัด (miter); มุมแหลมที่จุดตัดไกลเกิน miterLimit × distance จะถูกตัดมุม (bevel)
359
+ // จุดซ้ำติดกันถูกข้าม; เส้นที่จุดแรก = จุดสุดท้ายถือเป็นวงปิด
360
+
361
+ // text.utils (DXF ไม่มีข้อมูลฟอนต์ — เป็นค่าประมาณ)
362
+ visibleCharCount(text) // ไม่นับสระ/วรรณยุกต์ไทยที่อยู่บน/ล่าง
363
+ estimateTextWidth(text, height, widthFactor?=0.75)
364
+ ```
365
+
366
+ ---
367
+
368
+ ## 6. ชนิดข้อมูล (Types)
369
+
370
+ ```typescript
371
+ type Coordinate = [number, number];
372
+ type Coordinate3D = [number, number, number];
373
+
374
+ interface OnfarmLine { coordinate: Coordinate3D[]; type: string; width: number; color: number }
375
+
376
+ type HatchPatternLine = [angleDeg, [baseX, baseY], [offsetX, offsetY], dashes[]];
377
+ const HATCH_PATTERNS: Record<string, HatchPatternLine[]>;
378
+ ```
379
+
380
+ ยัง export: `CalloutOptions`, `ImageOptions`, ชนิดจาก `core/dxf-entities` (`DxfEntity`, `TableCellDef`, `LayoutDef`, `LayoutViewportDef`, `GeoReferenceDef`, `TextEntityAlignment` …), template handling (`readTemplate`, `serializeDocument`, `listLayoutNames`, `BLOCK_REGISTRY`) และ parser (หัวข้อ 7) — `serializeWithTemplate` ยังมีอยู่แต่ deprecated
381
+
382
+ ---
383
+
384
+ ## 7. DXF Parser ในตัว
385
+
386
+ TypeScript port ของ `dxf-parser` (MIT) ทำงานได้ทั้ง Node และ Browser และ **fail-fast บนไฟล์ผิดรูปแทนการค้าง**
387
+
388
+ ```typescript
389
+ import { DxfParser, ParsedDxf } from 'ofs-dxf-ts';
390
+
391
+ const parsed: ParsedDxf | null = new DxfParser().parseSync(dxfText);
392
+ parsed?.entities; // ParsedDxfEntity[]
393
+ parsed?.blocks; // Record<string, ParsedDxfBlock>
394
+ parsed?.tables; // layer / lineType / style / dimstyle / viewPort
395
+ parsed?.header; // $ACADVER, $EXTMIN, ...
396
+ ```
397
+
398
+ - ใช้ `parseSync(source: string)` เท่านั้น — ฝั่ง browser อ่านไฟล์อัปโหลดด้วย `await file.text()` ก่อน
399
+ - ชนิดฝั่ง parser export ในชื่อ `ParsedDxfEntity` / `ParsedDxfPoint` / `ParsedDxfBlock`
400
+ - ไฟล์ที่ parse ไม่ได้จะ `throw` — ครอบ `try/catch` เมื่อรับไฟล์จากผู้ใช้
401
+
402
+ ---
403
+
404
+ ## 8. การเปลี่ยนแปลงที่มีผลกับโค้ดเดิม
405
+
406
+ - **ไฟล์ที่สร้างจากศูนย์** เป็น DXF R2018 โครงสร้างครบ (เดิมเป็นไฟล์ย่อที่ไม่มี CLASSES/OBJECTS)
407
+ - **`addMLeader`** deprecated — วาดเป็น callout แทน MULTILEADER (ของเดิมไม่ถูกต้องสำหรับ AutoCAD)
408
+ - **`addHatch`** ที่ใช้ชื่อลายที่ไม่รู้จักจะ `throw` (เดิมเขียนไฟล์ที่ลายว่าง)
409
+ - **`addLayout`** สร้าง layout จริงแล้ว (เดิมไม่ถูกเขียนลงไฟล์); `LayoutViewportDef.viewHeight` เป็น optional เมื่อระบุ `scale`
410
+ - **Modelspace `add_image`** ตีความ `clipBoundary` เป็นพิกัดโลก
411
+ - **ตาราง** เซลล์ที่ข้อความว่างจะไม่สร้าง TEXT entity
package/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ รูปแบบตาม [Keep a Changelog](https://keepachangelog.com/) และใช้ [Semantic Versioning](https://semver.org/)
4
+
5
+ ## [1.0.0] — 2026-09-21
6
+
7
+ เวอร์ชันแรกที่เผยแพร่บน npm ในชื่อ **`ofs-dxf-ts`** (ESM อย่างเดียว, ไม่มี runtime dependency)
8
+
9
+ ### Added
10
+
11
+ - `DxfBuilder` / `DxfDocument` / `Modelspace` — สร้าง DXF R2018 จากศูนย์ หรือวาดบน DXF template โดยคงทุก section ของ template ไว้ และแจก handle ต่อจาก `$HANDSEED`
12
+ - Entity: Line, Polyline (2D/3D), Circle, Arc, Ellipse, Spline, Text, MText, Hatch, Dimension, Leader, Image, Viewport, Solid, Mesh, Insert
13
+ - `defineBlock()` / `addBlock()` — block definition และการวางซ้ำ
14
+ - Hatch: `SOLID` + ลาย ISO ในตัว 32 ลาย หรือกำหนด `patternLines` เอง รองรับรูใน
15
+ - Raster image พร้อม IMAGEDEF/REACTOR/`ACAD_IMAGE_DICT` และ clip ด้วยพิกัดโลก
16
+ - Paper space layout: `addLayout()` พร้อม viewport ตามมาตราส่วน, freeze layer เฉพาะ viewport, ล็อก viewport และ `onLayout()` สำหรับวาดลง layout (ของใหม่หรือของ template)
17
+ - `addCallout()` (กล่องข้อความพร้อมเส้นชี้) และ `addTable()` ที่รวมเซลล์ได้ (`colSpan` / `rowSpan`)
18
+ - `addLineType()` / `addTextStyle()` / `addLayer()`
19
+ - DXF parser ในตัว (TypeScript port ของ `dxf-parser`, ไม่มี dependency ภายนอก)
20
+ - เรขาคณิตบริสุทธิ์: `getLineOffset()` / `offsetPolyline()` (miter + bevel, รองรับเส้นปิด), azimuth, การแบ่ง polyline, ประมาณความกว้างข้อความ
21
+ - สัญลักษณ์งานชลประทานแบบพารามิเตอร์ (culvert, flume, siphon, check ฯลฯ)
22
+
23
+ ### Notes
24
+
25
+ - ต้องใช้ **Node.js ≥ 20.16** สำหรับ `saveas()` / `readFile()` (เข้าถึง `fs` ผ่าน `process.getBuiltinModule`) — runtime อื่นใช้ `saveString()` ได้
26
+ - ตรวจไฟล์ผลลัพธ์ด้วย ezdxf (strict read + audit) ได้ 0 errors / 0 fixes — ยังไม่ได้ทดสอบเปิดใน AutoCAD จริง
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 On-Farm Solution Co.,Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # ofs-dxf-ts
2
+
3
+ ไลบรารี TypeScript สำหรับสร้างไฟล์ DXF (Drawing Exchange Format) สำหรับงาน GIS และวิศวกรรม พัฒนาต่อยอดจากแนวคิดของ `ezdxf` (Python) ทำงานได้ทั้งบน **Node.js** และ **Browser (Angular/React)**
4
+
5
+ ไลบรารี **บริสุทธิ์และไม่ผูกกับ API ใด ๆ** — รับเฉพาะ coordinate, label และ options ทั่วไป แล้ววาดลงบน DXF template หรือสร้างไฟล์จากศูนย์ ผู้เรียกเป็นผู้เตรียมข้อมูลของตัวเองให้อยู่ในรูป coordinate/options ก่อน
6
+
7
+ ## 🌟 คุณสมบัติ
8
+
9
+ - **Template-aware**: วาดลงบนไฟล์ DXF ต้นแบบจาก AutoCAD/Civil 3D โดยคงทุก section เดิมไว้ (Layers, Blocks, Styles, Layouts, CLASSES, OBJECTS …) และแจก handle ใหม่ต่อจาก `$HANDSEED` ของ template จึงไม่ชนกับของเดิม
10
+ - **สร้างจากศูนย์ได้ไฟล์ครบโครงสร้าง**: ใช้เอกสาร DXF R2018 เปล่าที่ฝังในตัว lib เป็นฐาน ทั้งสองโหมดจึงได้โครงสร้างแบบเดียวกัน
11
+ - **Block definition**: สร้างสัญลักษณ์เองด้วย `defineBlock()` แล้ววางซ้ำด้วย `addBlock()` (INSERT)
12
+ - **Hatch**: เติมทึบ (`SOLID`) และลาย ISO ในตัว 32 ลาย (ANSI31, DOTS, GRASS, EARTH, AR-SAND …) หรือกำหนดลายเอง รองรับรูใน (hole)
13
+ - **Raster image**: อ้างอิงไฟล์ภาพเป็นพื้นหลัง (เช่นแผนที่ภูมิประเทศ) พร้อม clip ด้วยพิกัดโลก
14
+ - **Paper space layout**: สร้าง layout พร้อม viewport ตามมาตราส่วน (เช่น 1:12,500) ซ่อน layer เฉพาะ viewport ล็อก viewport และวาดลง paper space ได้ทั้ง layout ใหม่และของ template
15
+ - **Annotation**: กล่องข้อความพร้อมเส้นชี้ (`addCallout`) และตารางที่รวมเซลล์ได้ (`colSpan` / `rowSpan`)
16
+ - **Linetype / Text style**: เพิ่ม linetype และ text style (เช่นฟอนต์ไทย TrueType) ได้ทั้งสองโหมด
17
+ - **Entities**: Line, Polyline, Circle, Arc, Ellipse, Spline, Text, MText, Hatch, Dimension, Leader, Image, Viewport, Solid, Mesh ฯลฯ
18
+ - **Layer & view**: Freeze / Lock / On-Off, Zoom Extents / Zoom Window
19
+ - **Zero runtime dependencies**: parser DXF (TypeScript port ของ `dxf-parser`, fail-fast บนไฟล์ผิดรูปแทนการค้าง) และเรขาคณิต (เช่น offset เส้นขอบคลอง) เขียนเองทั้งหมด คำนวณบนระนาบในหน่วยของแบบ (UTM เมตร)
20
+ - **Universal**: Node.js อ่าน/เขียนไฟล์ผ่าน `fs`; Browser/Angular โหลด template จาก string และดาวน์โหลดผลลัพธ์ได้โดยไม่ต้องใช้ `fs`
21
+
22
+ ## 🚀 การติดตั้ง
23
+
24
+ ```bash
25
+ npm install ofs-dxf-ts
26
+ ```
27
+
28
+ **ความต้องการ**
29
+
30
+ | | |
31
+ |---|---|
32
+ | รูปแบบโมดูล | **ESM เท่านั้น** (ไม่มี CommonJS build) พร้อม type declaration ในตัว |
33
+ | Node.js | ≥ 20.16 — `saveas()` / `readFile()` เข้าถึง `fs` ผ่าน `process.getBuiltinModule` เวอร์ชันเก่ากว่านี้ยังใช้ `saveString()` ได้ปกติ |
34
+ | Browser / Angular | bundler ใดก็ได้ ไม่มีการ import `fs`/`path` แบบ static จึงไม่ต้องตั้ง polyfill |
35
+
36
+ ## 💡 ตัวอย่างการใช้งาน
37
+
38
+ ### 1. วาดบน Template (แนะนำ)
39
+
40
+ ```typescript
41
+ import { DxfBuilder } from 'ofs-dxf-ts';
42
+
43
+ // Node.js — โหลดไฟล์ผ่าน constructor
44
+ const builder = new DxfBuilder('./template2018.dxf');
45
+ // Browser/Angular — DxfBuilder.fromString(dxfText)
46
+
47
+ builder.addPolyline([[518600, 1033350], [519000, 1033380]], 'CANAL', 1);
48
+
49
+ builder.saveas('output.dxf'); // Node.js
50
+ // builder.downloadAs('output.dxf'); // Browser
51
+ // const dxf = builder.saveString(); // string (ทุก environment)
52
+ ```
53
+
54
+ ### 2. สัญลักษณ์ กล่องข้อความ และตาราง
55
+
56
+ ```typescript
57
+ // linetype / text style / layer
58
+ builder.addTextStyle('THAI', 'THSarabunNew.ttf');
59
+ builder.addLineType('BOUNDARY', '____ . ____', [40, -10, 0, -10]); // หน่วยเดียวกับแบบ (เมตร)
60
+ builder.addLayer('ขอบเขต', 6, 'BOUNDARY');
61
+
62
+ // สัญลักษณ์หมุด BM: วาดในพิกัดของ block แล้ววางด้วย addBlock
63
+ builder.defineBlock('BM', b => {
64
+ b.addPolyline([[-1, -1], [1, -1], [1, 1], [-1, 1]], '0', undefined, undefined, true);
65
+ b.addHatch('SOLID', [[[0, 0], [1, 0], [1, 1], [0, 1]]], '0');
66
+ });
67
+ builder.addBlock('BM', 519395.838, 1034018.584, 20, 0, 'SYMBOL');
68
+
69
+ // กล่องข้อความพร้อมเส้นชี้ (มุมบนซ้ายของกล่องอยู่ที่ boxAt)
70
+ builder.addCallout(
71
+ [519480, 1034100], // จุดที่ชี้
72
+ ['ก่อสร้างอาคารทางน้ำเข้า', 'ขนาด 2-1.75x1.75 ม.'],
73
+ [519700, 1035050], // มุมบนซ้ายของกล่อง
74
+ { layer: 'NOTE', textHeight: 25, style: 'THAI', arrow: 'dot' },
75
+ );
76
+
77
+ // ตารางที่รวมเซลล์
78
+ builder.addTable([650, 300], [14, 26, 42, 42], 7, [
79
+ [{ text: 'ลำดับ', rowSpan: 2 }, { text: 'หมุด', rowSpan: 2 }, { text: 'พิกัด', colSpan: 2 }],
80
+ [{ text: 'North' }, { text: 'East' }],
81
+ [{ text: '1' }, { text: 'BM.07' }, { text: '1,034,018.584' }, { text: '519,395.838' }],
82
+ ], 'TABLE', 3);
83
+ ```
84
+
85
+ ### 3. Hatch และภาพพื้นหลัง
86
+
87
+ ```typescript
88
+ // ลายจุด (scale คูณกับหน่วยลาย ISO ที่เป็น mm) และลายเส้นเฉียงหมุน 45°
89
+ builder.addHatch('DOTS', [pondOutline], 'POND', 8, { scale: 8 });
90
+ builder.addHatch('ANSI31', [outer, hole], 'AREA', 7, { scale: 10, angle: 45 });
91
+
92
+ // ภาพพื้นหลัง: DXF เก็บเพียงการอ้างอิงไฟล์ — วางไฟล์ภาพไว้ข้าง .dxf
93
+ builder.addImage('topo_background.jpg', [3000, 2000], [516000, 1031500], [6000, 4000], 'RASTER', { fade: 40 });
94
+ ```
95
+
96
+ ### 4. Layout สำหรับพิมพ์ (Paper Space)
97
+
98
+ ```typescript
99
+ // A1 (มม.) — viewport แสดง model space ที่ 1:12,500 (model เป็นเมตร)
100
+ builder.addLayout('BM-A1', 841, 594, [0, 0, 0, 0], [
101
+ { center: [330, 330], width: 600, height: 440, viewCenter: [519000, 1033500], scale: 12500, locked: true },
102
+ { center: [110, 480], width: 150, height: 100, viewCenter: [519000, 1033500], scale: 100000,
103
+ frozenLayers: ['NOTE', 'GRID'] }, // key map
104
+ ]);
105
+
106
+ // ทุกคำสั่งภายใน callback วาดลง paper space ของ layout นั้น (ใช้กับ 'Layout1' ของ template ได้ด้วย)
107
+ builder.onLayout('BM-A1', s => {
108
+ s.addText('แผนที่แสดงตำแหน่งหมุดหลักฐาน', 250, 70, 9, 0, 'SHEET');
109
+ s.addBlock('NORTH', 800, 530, 2, 0, 'SHEET');
110
+ });
111
+ ```
112
+
113
+ > 🅰️ **Angular**: โหลด template ผ่าน `HttpClient` แบบ `responseType: 'text'` แล้วใช้ `DxfBuilder.fromString()` และดาวน์โหลดด้วย `downloadAs()` — ไม่มีการใช้ `fs`
114
+
115
+ ---
116
+
117
+ - 📖 **API ทั้งหมด**: [API.md](./API.md) (แนบมากับแพ็กเกจ)
118
+ - 📝 **ประวัติเวอร์ชัน**: [CHANGELOG.md](./CHANGELOG.md)
119
+ - 📄 สัญญาอนุญาต: MIT