@vitrosoftware/common-ui-ts 1.1.122 → 1.1.124

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 (124) hide show
  1. package/css/std/controls/checkbox/checkbox.css +4 -0
  2. package/css/std/controls/checkbox/img/checkbox-indeterminate.svg +4 -0
  3. package/css/std/controls/date-picker/date-picker.css +1 -25
  4. package/css/std/controls/dxf-viewer/annotation.css +85 -0
  5. package/css/std/controls/dxf-viewer/common.css +24 -0
  6. package/css/std/controls/dxf-viewer/dxf-viewer-index.css +14081 -0
  7. package/css/std/controls/dxf-viewer/dxf-viewer.css +194 -0
  8. package/css/std/controls/dxf-viewer/img/cancel-dark-grey.svg +5 -0
  9. package/css/std/controls/dxf-viewer/img/collapse-bottom.svg +5 -0
  10. package/css/std/controls/dxf-viewer/img/collapse-up-blue.svg +5 -0
  11. package/css/std/controls/dxf-viewer/img/delete-active.svg +11 -0
  12. package/css/std/controls/dxf-viewer/img/delete.svg +11 -0
  13. package/css/std/controls/dxf-viewer/img/draw-annotation.svg +3 -0
  14. package/css/std/controls/dxf-viewer/img/invisible-eye.svg +4 -0
  15. package/css/std/controls/dxf-viewer/img/show-annotation.svg +3 -0
  16. package/css/std/controls/dxf-viewer/img/sidebar-layers-toggle.svg +6 -0
  17. package/css/std/controls/dxf-viewer/img/sidebar-notes-toggle.svg +5 -0
  18. package/css/std/controls/dxf-viewer/img/sidebar-resizer.svg +6 -0
  19. package/css/std/controls/dxf-viewer/img/sidebar-toggle.svg +7 -0
  20. package/css/std/controls/dxf-viewer/img/visible-eye.svg +4 -0
  21. package/css/std/controls/dxf-viewer/img/zoom-in.svg +6 -0
  22. package/css/std/controls/dxf-viewer/img/zoom-out.svg +5 -0
  23. package/css/std/controls/dxf-viewer/layer-list.css +104 -0
  24. package/css/std/controls/dxf-viewer/panel.css +34 -0
  25. package/css/std/controls/dxf-viewer/prop-inspector.css +102 -0
  26. package/css/std/controls/dxf-viewer/select.css +111 -0
  27. package/css/std/controls/dxf-viewer/sidebar.css +190 -0
  28. package/css/std/controls/dxf-viewer/thumbnail-list.css +65 -0
  29. package/css/std/controls/dxf-viewer/toolbar.css +117 -0
  30. package/css/std/controls/dxf-viewer/treeview.css +3 -0
  31. package/css/std/controls/dxf-viewer/treeview.panel.css +108 -0
  32. package/css/std/controls/error-message/error-message.css +22 -0
  33. package/css/std/controls/image-picker/image-picker.css +0 -26
  34. package/css/std/controls/input/input.css +1 -24
  35. package/css/std/controls/issue-tile/issue-tile-header.css +1 -0
  36. package/css/std/controls/login/ntlm-authentication-form.css +9 -12
  37. package/css/std/controls/lookup-picker/lookup-picker-value-list.css +38 -2
  38. package/css/std/controls/lookup-picker/lookup-picker.css +1 -25
  39. package/css/std/controls/table-view/treegrid-context-menu.css +44 -18
  40. package/css/std/controls/table-view/treegrid-message.css +4 -4
  41. package/css/std/controls/time-picker/time-picker.css +1 -25
  42. package/dist/index.css +81 -143
  43. package/dist/index.js +15137 -489
  44. package/dist/index.js.map +1 -1
  45. package/dist/src/controls/Checkbox/Checkbox.d.ts +1 -0
  46. package/dist/src/controls/DxfViewer/DxfViewer.d.ts +6 -0
  47. package/dist/src/controls/DxfViewer/DxfViewerContext.d.ts +31 -0
  48. package/dist/src/controls/DxfViewer/Layer.d.ts +9 -0
  49. package/dist/src/controls/DxfViewer/LayerList.d.ts +11 -0
  50. package/dist/src/controls/DxfViewer/Thumbnail.d.ts +7 -0
  51. package/dist/src/controls/DxfViewer/ThumbnailList.d.ts +6 -0
  52. package/dist/src/controls/DxfViewer/Viewer.d.ts +6 -0
  53. package/dist/src/controls/ErrorMessage/ErrorMessage.d.ts +6 -0
  54. package/dist/src/controls/Login/FormRef.d.ts +3 -0
  55. package/dist/src/controls/Login/LoginConstants.d.ts +2 -1
  56. package/dist/src/controls/Login/LoginFormRef.d.ts +2 -2
  57. package/dist/src/controls/Login/NTLMAuthenticationForm.d.ts +5 -2
  58. package/dist/src/controls/LookupPicker/LookupPicker.d.ts +2 -0
  59. package/dist/src/controls/LookupPicker/ValueList.d.ts +2 -0
  60. package/dist/src/controls/TableView/TableViewConstants.d.ts +11 -0
  61. package/dist/src/controls/TableView/TreeGridTableViewContextImpl.d.ts +1 -0
  62. package/dist/src/controls/TreeView/TreeView.d.ts +4 -0
  63. package/dist/src/controls/TreeView/TreeViewConfig.d.ts +3 -0
  64. package/dist/src/controls/TreeView/TreeViewConstants.d.ts +2 -1
  65. package/dist/src/index.d.ts +7 -1
  66. package/lib/dxf-viewer/BatchingKey.js +91 -0
  67. package/lib/dxf-viewer/DxfFetcher.js +39 -0
  68. package/lib/dxf-viewer/DxfScene.js +2695 -0
  69. package/lib/dxf-viewer/DxfViewer.js +1056 -0
  70. package/lib/dxf-viewer/DxfWorker.js +229 -0
  71. package/lib/dxf-viewer/DynamicBuffer.js +100 -0
  72. package/lib/dxf-viewer/HatchCalculator.js +345 -0
  73. package/lib/dxf-viewer/LinearDimension.js +323 -0
  74. package/lib/dxf-viewer/MTextFormatParser.js +211 -0
  75. package/lib/dxf-viewer/MaterialKey.js +37 -0
  76. package/lib/dxf-viewer/OrbitControls.js +1253 -0
  77. package/lib/dxf-viewer/Pattern.js +94 -0
  78. package/lib/dxf-viewer/RBTree.js +471 -0
  79. package/lib/dxf-viewer/TextRenderer.js +1038 -0
  80. package/lib/dxf-viewer/index.js +42 -0
  81. package/lib/dxf-viewer/math/Matrix2.js +77 -0
  82. package/lib/dxf-viewer/math/utils.js +59 -0
  83. package/lib/dxf-viewer/parser/AutoCadColorIndex.js +265 -0
  84. package/lib/dxf-viewer/parser/DimStyleCodes.js +33 -0
  85. package/lib/dxf-viewer/parser/DxfArrayScanner.js +143 -0
  86. package/lib/dxf-viewer/parser/DxfParser.js +980 -0
  87. package/lib/dxf-viewer/parser/ExtendedDataParse-My.js +91 -0
  88. package/lib/dxf-viewer/parser/ExtendedDataParser.js +123 -0
  89. package/lib/dxf-viewer/parser/ParseHelpers.js +142 -0
  90. package/lib/dxf-viewer/parser/entities/3dface.js +83 -0
  91. package/lib/dxf-viewer/parser/entities/arc.js +38 -0
  92. package/lib/dxf-viewer/parser/entities/attdef.js +89 -0
  93. package/lib/dxf-viewer/parser/entities/attrib.js +34 -0
  94. package/lib/dxf-viewer/parser/entities/attribute.js +109 -0
  95. package/lib/dxf-viewer/parser/entities/circle.js +43 -0
  96. package/lib/dxf-viewer/parser/entities/dimension.js +72 -0
  97. package/lib/dxf-viewer/parser/entities/ellipse.js +46 -0
  98. package/lib/dxf-viewer/parser/entities/hatch.js +343 -0
  99. package/lib/dxf-viewer/parser/entities/insert.js +62 -0
  100. package/lib/dxf-viewer/parser/entities/leader.js +84 -0
  101. package/lib/dxf-viewer/parser/entities/line.js +34 -0
  102. package/lib/dxf-viewer/parser/entities/lwpolyline.js +100 -0
  103. package/lib/dxf-viewer/parser/entities/mtext.js +54 -0
  104. package/lib/dxf-viewer/parser/entities/point.js +35 -0
  105. package/lib/dxf-viewer/parser/entities/polyline.js +92 -0
  106. package/lib/dxf-viewer/parser/entities/solid.js +40 -0
  107. package/lib/dxf-viewer/parser/entities/spline.js +70 -0
  108. package/lib/dxf-viewer/parser/entities/text.js +47 -0
  109. package/lib/dxf-viewer/parser/entities/vertex.js +62 -0
  110. package/lib/dxf-viewer/parser/entities/viewport.js +56 -0
  111. package/lib/dxf-viewer/parser/objects/dictionary.js +29 -0
  112. package/lib/dxf-viewer/parser/objects/layout.js +35 -0
  113. package/lib/dxf-viewer/parser/objects/xrecord.js +29 -0
  114. package/lib/opentype/opentype.module.js +14571 -0
  115. package/lib/three/CSS2DRenderer.js +235 -0
  116. package/lib/three/three.module.js +49912 -0
  117. package/package.json +12 -10
  118. package/src/controls/BimViewer/js/bim-viewer.js +2 -2
  119. package/src/controls/DxfViewer/js/dxf-viewer.js +3580 -0
  120. package/src/controls/PdfViewer/js/pdf-viewer.js +1 -1
  121. package/css/std/controls/input/img/error-message.svg +0 -6
  122. package/css/std/controls/lookup-picker/img/error-message.svg +0 -6
  123. package/css/std/controls/time-picker/img/error-message.svg +0 -6
  124. /package/css/std/controls/{date-picker → error-message}/img/error-message.svg +0 -0
@@ -0,0 +1,1056 @@
1
+ import * as three from "/resource/dxfViewer/js/three/three.module.js"
2
+ import { BatchingKey } from "./BatchingKey.js"
3
+ import { DxfWorker } from "./DxfWorker.js"
4
+ import { MaterialKey } from "./MaterialKey.js"
5
+ import { ColorCode, DxfScene } from "./DxfScene.js"
6
+ import { OrbitControls } from "./OrbitControls.js"
7
+ import { RBTree } from "./RBTree.js"
8
+
9
+ export { Batch, Layer, Block };
10
+
11
+ /** Level in "message" events. */
12
+ const MessageLevel = Object.freeze({
13
+ INFO: "info",
14
+ WARN: "warn",
15
+ ERROR: "error"
16
+ })
17
+
18
+ /** The representation class for the viewer, based on Three.js WebGL renderer. */
19
+ export class DxfViewer {
20
+
21
+ /** @param domContainer Container element to create the canvas in. Usually empty div. Should not
22
+ * have padding if auto-resize feature is used.
23
+ * @param options Some options can be overridden if specified. See DxfViewer.DefaultOptions.
24
+ */
25
+ constructor(domContainer, options = null) {
26
+ this.domContainer = domContainer
27
+ this.options = Object.create(DxfViewer.DefaultOptions)
28
+ if (options) {
29
+ Object.assign(this.options, options)
30
+ }
31
+ options = this.options
32
+
33
+ this.clearColor = this.options.clearColor.getHex()
34
+
35
+ this.scene = new three.Scene()
36
+
37
+ try {
38
+ this.renderer = new three.WebGLRenderer({
39
+ alpha: options.canvasAlpha,
40
+ premultipliedAlpha: options.canvasPremultipliedAlpha,
41
+ antialias: options.antialias,
42
+ depth: false,
43
+ preserveDrawingBuffer: true
44
+ })
45
+ } catch (e) {
46
+ console.log("Failed to create renderer: " + e)
47
+ this.renderer = null
48
+ return
49
+ }
50
+ const renderer = this.renderer
51
+ renderer.setPixelRatio(window.devicePixelRatio)
52
+
53
+ const camera = this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0.1, 2);
54
+ camera.position.z = 1
55
+ camera.position.x = 0
56
+ camera.position.y = 0
57
+
58
+ this.simpleColorMaterial = []
59
+ this.simplePointMaterial = []
60
+ for (let i = 0; i < InstanceType.MAX; i++) {
61
+ this.simpleColorMaterial[i] = this._CreateSimpleColorMaterial(i)
62
+ this.simplePointMaterial[i] = this._CreateSimplePointMaterial(i)
63
+ }
64
+
65
+ renderer.setClearColor(options.clearColor, options.clearAlpha)
66
+
67
+ if (options.autoResize) {
68
+ this.canvasWidth = domContainer.clientWidth
69
+ this.canvasHeight = domContainer.clientHeight
70
+ domContainer.style.position = "relative"
71
+ } else {
72
+ this.canvasWidth = options.canvasWidth
73
+ this.canvasHeight = options.canvasHeight
74
+ this.resizeObserver = null
75
+ }
76
+ renderer.setSize(this.canvasWidth, this.canvasHeight)
77
+
78
+ this.canvas = renderer.domElement
79
+ domContainer.style.display = "block"
80
+ if (options.autoResize) {
81
+ this.canvas.style.position = "absolute"
82
+ this.resizeObserver = new ResizeObserver(entries => this._OnResize(entries[0]))
83
+ this.resizeObserver.observe(domContainer)
84
+ }
85
+ domContainer.appendChild(this.canvas)
86
+
87
+ this.canvas.addEventListener("pointerdown", this._OnPointerEvent.bind(this))
88
+ this.canvas.addEventListener("pointerup", this._OnPointerEvent.bind(this))
89
+
90
+ this.Render()
91
+
92
+ /* Indexed by MaterialKey, value is {key, material}. */
93
+ this.materials = new RBTree((m1, m2) => m1.key.Compare(m2.key))
94
+ /* Indexed by layer name, value is Layer instance. */
95
+ this.layers = new Map()
96
+ /* Indexed by block name, value is Block instance. */
97
+ this.blocks = new Map()
98
+
99
+ /** Set during data loading. */
100
+ this.worker = null
101
+ }
102
+
103
+ /** @return {boolean} True if renderer exists. May be false in case when WebGL context is lost
104
+ * (e.g. after wake up from sleep). In such case page should be reloaded.
105
+ */
106
+ HasRenderer() {
107
+ return Boolean(this.renderer)
108
+ }
109
+
110
+ /**
111
+ * @returns {three.WebGLRenderer | null} Returns the created Three.js renderer.
112
+ */
113
+ GetRenderer(){
114
+ return this.renderer;
115
+ }
116
+
117
+ GetCanvas() {
118
+ return this.canvas
119
+ }
120
+
121
+ GetDxf() {
122
+ return this.parsedDxf
123
+ }
124
+
125
+ SetSize(width, height) {
126
+ this._EnsureRenderer()
127
+
128
+ const hScale = width / this.canvasWidth
129
+ const vScale = height / this.canvasHeight
130
+
131
+ const cam = this.camera
132
+ const centerX = (cam.left + cam.right) / 2
133
+ const centerY = (cam.bottom + cam.top) / 2
134
+ const camWidth = cam.right - cam.left
135
+ const camHeight = cam.top - cam.bottom
136
+ cam.left = centerX - hScale * camWidth / 2
137
+ cam.right = centerX + hScale * camWidth / 2
138
+ cam.bottom = centerY - vScale * camHeight / 2
139
+ cam.top = centerY + vScale * camHeight / 2
140
+ cam.updateProjectionMatrix()
141
+
142
+ this.canvasWidth = width
143
+ this.canvasHeight = height
144
+ this.renderer.setSize(width, height)
145
+ if (this.controls) {
146
+ this.controls.update()
147
+ }
148
+ this._Emit("resized", {width, height})
149
+ this._Emit("viewChanged")
150
+ this.Render()
151
+ }
152
+
153
+ /** Load DXF into the viewer. Old content is discarded, state is reset.
154
+ * @param url {string} DXF file URL.
155
+ * @param fonts {?string[]} List of font URLs. Files should have typeface.js format. Fonts are
156
+ * used in the specified order, each one is checked until necessary glyph is found. Text is not
157
+ * rendered if fonts are not specified.
158
+ * @param progressCbk {?Function} (phase, processedSize, totalSize)
159
+ * Possible phase values:
160
+ * * "font"
161
+ * * "fetch"
162
+ * * "parse"
163
+ * * "prepare"
164
+ * @param workerFactory {?Function} Factory for worker creation. The worker script should
165
+ * invoke DxfViewer.SetupWorker() function.
166
+ */
167
+ async Load({url, fonts = null, progressCbk = null, workerFactory = null}) {
168
+ if (url === null || url === undefined) {
169
+ throw new Error("`url` parameter is not specified")
170
+ }
171
+
172
+ this._EnsureRenderer()
173
+
174
+ this.Clear()
175
+
176
+ this.worker = new DxfWorker(workerFactory ? workerFactory() : null)
177
+ const {scene, dxf} = await this.worker.Load(url, fonts, this.options, progressCbk)
178
+ await this.worker.Destroy()
179
+ this.worker = null
180
+ this.parsedDxf = dxf
181
+
182
+ this.origin = scene.origin
183
+ this.bounds = scene.bounds
184
+ this.hasMissingChars = scene.hasMissingChars
185
+
186
+ for (const layer of scene.layers) {
187
+ this.layers.set(layer.name, new Layer(layer.name, layer.displayName, layer.color))
188
+ }
189
+
190
+ /* Load all blocks on the first pass. */
191
+ for (const batch of scene.batches) {
192
+ if (batch.key.blockName !== null &&
193
+ batch.key.geometryType !== BatchingKey.GeometryType.BLOCK_INSTANCE &&
194
+ batch.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE) {
195
+
196
+ let block = this.blocks.get(batch.key.blockName)
197
+ if (!block) {
198
+ block = new Block()
199
+ this.blocks.set(batch.key.blockName, block)
200
+ }
201
+ block.PushBatch(new Batch(this, scene, batch))
202
+ }
203
+ }
204
+
205
+ console.log(`DXF scene:
206
+ ${scene.batches.length} batches,
207
+ ${this.layers.size} layers,
208
+ ${this.blocks.size} blocks,
209
+ vertices ${scene.vertices.byteLength} B,
210
+ indices ${scene.indices.byteLength} B
211
+ transforms ${scene.transforms.byteLength} B`)
212
+
213
+ /* Instantiate all entities. */
214
+ for (const batch of scene.batches) {
215
+ this._LoadBatch(scene, batch)
216
+ }
217
+
218
+ this._Emit("loaded")
219
+
220
+ if (scene.bounds) {
221
+ this.FitView(scene.bounds.minX - scene.origin.x, scene.bounds.maxX - scene.origin.x,
222
+ scene.bounds.minY - scene.origin.y, scene.bounds.maxY - scene.origin.y)
223
+ } else {
224
+ this._Message("Empty document", MessageLevel.WARN)
225
+ }
226
+
227
+ if (this.hasMissingChars) {
228
+ this._Message("Some characters cannot be properly displayed due to missing fonts",
229
+ MessageLevel.WARN)
230
+ }
231
+
232
+ this._CreateControls()
233
+ this.Render()
234
+ }
235
+
236
+ Render() {
237
+ this._EnsureRenderer()
238
+ this.renderer.render(this.scene, this.camera)
239
+ }
240
+
241
+ /** @return {Iterable<{name:String, color:number}>} List of layer names. */
242
+ GetLayers() {
243
+ const result = []
244
+ for (const lyr of this.layers.values()) {
245
+ result.push({
246
+ name: lyr.name,
247
+ displayName: lyr.displayName,
248
+ color: this._TransformColor(lyr.color)
249
+ })
250
+ }
251
+ return result
252
+ }
253
+
254
+ ShowLayer(name, show) {
255
+ this._EnsureRenderer()
256
+ const layer = this.layers.get(name)
257
+ if (!layer) {
258
+ return
259
+ }
260
+ for (const obj of layer.objects) {
261
+ obj.visible = show
262
+ }
263
+ this.Render()
264
+ }
265
+
266
+ /** Reset the viewer state. */
267
+ Clear() {
268
+ this._EnsureRenderer()
269
+ if (this.worker) {
270
+ this.worker.Destroy(true)
271
+ this.worker = null
272
+ }
273
+ if (this.controls) {
274
+ this.controls.dispose()
275
+ this.controls = null
276
+ }
277
+ this.scene.clear()
278
+ for (const layer of this.layers.values()) {
279
+ layer.Dispose()
280
+ }
281
+ this.layers.clear()
282
+ this.blocks.clear()
283
+ this.materials.each(e => e.material.dispose())
284
+ this.materials.clear()
285
+ this.SetView({x: 0, y: 0}, 2)
286
+ this._Emit("cleared")
287
+ this.Render()
288
+ }
289
+
290
+ /** Free all resources. The viewer object should not be used after this method was called. */
291
+ Destroy() {
292
+ if (!this.HasRenderer()) {
293
+ return
294
+ }
295
+ if (this.resizeObserver) {
296
+ this.resizeObserver.disconnect()
297
+ }
298
+ this.Clear()
299
+ this._Emit("destroyed")
300
+ for (const m of this.simplePointMaterial) {
301
+ m.dispose()
302
+ }
303
+ for (const m of this.simpleColorMaterial) {
304
+ m.dispose()
305
+ }
306
+ this.simplePointMaterial = null
307
+ this.simpleColorMaterial = null
308
+ this.renderer.dispose()
309
+ this.renderer = null
310
+ }
311
+
312
+ SetView(center, width) {
313
+ const aspect = this.canvasWidth / this.canvasHeight
314
+ const height = width / aspect
315
+ const cam = this.camera
316
+ cam.left = -width / 2
317
+ cam.right = width / 2
318
+ cam.top = height / 2
319
+ cam.bottom = -height / 2
320
+ cam.zoom = 1
321
+ cam.position.set(center.x, center.y, 1)
322
+ cam.rotation.set(0, 0, 0)
323
+ cam.updateMatrix()
324
+ cam.updateProjectionMatrix()
325
+ this._Emit("viewChanged")
326
+ }
327
+
328
+ /** Set view to fit the specified bounds. */
329
+ FitView(minX, maxX, minY, maxY, padding = 0.1) {
330
+ const aspect = this.canvasWidth / this.canvasHeight
331
+ let width = maxX - minX
332
+ const height = maxY - minY
333
+ const center = {x: minX + width / 2, y: minY + height / 2}
334
+ if (height * aspect > width) {
335
+ width = height * aspect
336
+ }
337
+ if (width <= Number.MIN_VALUE * 2) {
338
+ width = 1
339
+ }
340
+ this.SetView(center, width * (1 + padding))
341
+ }
342
+
343
+ /** @return {Scene} three.js scene for the viewer. Can be used to add custom entities on the
344
+ * scene. Remember to apply scene origin available via GetOrigin() method.
345
+ */
346
+ GetScene() {
347
+ return this.scene
348
+ }
349
+
350
+ /** @return {Camera} three.js camera for the viewer. */
351
+ GetCamera() {
352
+ return this.camera
353
+ }
354
+
355
+ /** @return {Vector2} Scene origin in global drawing coordinates. */
356
+ GetOrigin() {
357
+ return this.origin
358
+ }
359
+
360
+ /** Subscribe to the specified event. The following events are defined:
361
+ * * "loaded" - new scene loaded.
362
+ * * "cleared" - current scene cleared.
363
+ * * "destroyed" - viewer instance destroyed.
364
+ * * "resized" - viewport size changed. Details: {width, height}
365
+ * * "pointerdown" - Details: {domEvent, position:{x,y}}, position is in scene coordinates.
366
+ * * "pointerup"
367
+ * * "viewChanged"
368
+ * * "message" - Some message from the viewer. {message: string, level: string}.
369
+ *
370
+ * @param eventName {string}
371
+ * @param eventHandler {function} Accepts event object.
372
+ */
373
+ Subscribe(eventName, eventHandler) {
374
+ this._EnsureRenderer()
375
+ this.canvas.addEventListener(EVENT_NAME_PREFIX + eventName, eventHandler)
376
+ }
377
+
378
+ /** Unsubscribe from previously subscribed event. The arguments should match previous
379
+ * Subscribe() call.
380
+ *
381
+ * @param eventName {string}
382
+ * @param eventHandler {function}
383
+ */
384
+ Unsubscribe(eventName, eventHandler) {
385
+ this._EnsureRenderer()
386
+ this.canvas.removeEventListener(EVENT_NAME_PREFIX + eventName, eventHandler)
387
+ }
388
+
389
+ // /////////////////////////////////////////////////////////////////////////////////////////////
390
+
391
+ _EnsureRenderer() {
392
+ if (!this.HasRenderer()) {
393
+ throw new Error("WebGL renderer not available. " +
394
+ "Probable WebGL context loss, try refreshing the page.")
395
+ }
396
+ }
397
+
398
+ _CreateControls() {
399
+ const controls = this.controls = new OrbitControls(this.camera, this.canvas)
400
+ controls.enableRotate = false
401
+ controls.mouseButtons = {
402
+ LEFT: three.MOUSE.PAN,
403
+ MIDDLE: three.MOUSE.DOLLY
404
+ }
405
+ controls.touches = {
406
+ ONE: three.TOUCH.PAN,
407
+ TWO: three.TOUCH.DOLLY_PAN
408
+ }
409
+ controls.zoomSpeed = 3
410
+ controls.mouseZoomSpeedFactor = 0.05
411
+ controls.target = new three.Vector3(this.camera.position.x, this.camera.position.y, 0)
412
+ controls.addEventListener("change", () => {
413
+ this._Emit("viewChanged")
414
+ this.Render()
415
+ })
416
+ controls.update()
417
+ }
418
+
419
+ _Emit(eventName, data = null) {
420
+ this.canvas.dispatchEvent(new CustomEvent(EVENT_NAME_PREFIX + eventName, { detail: data }))
421
+ }
422
+
423
+ _Message(message, level = MessageLevel.INFO) {
424
+ this._Emit("message", {message, level})
425
+ }
426
+
427
+ _OnPointerEvent(e) {
428
+ const canvasRect = e.target.getBoundingClientRect()
429
+ const canvasCoord = {x: e.clientX - canvasRect.left, y: e.clientY - canvasRect.top}
430
+ this._Emit(e.type, {
431
+ domEvent: e,
432
+ canvasCoord,
433
+ position: this._CanvasToSceneCoord(canvasCoord.x, canvasCoord.y)
434
+ })
435
+ }
436
+
437
+ /** @return {{x,y}} Scene coordinate corresponding to the specified canvas pixel coordinates. */
438
+ _CanvasToSceneCoord(x, y) {
439
+ const v = new three.Vector3(x * 2 / this.canvasWidth - 1,
440
+ -y * 2 / this.canvasHeight + 1,
441
+ 1).unproject(this.camera)
442
+ return {x: v.x, y: v.y}
443
+ }
444
+
445
+ _OnResize(entry) {
446
+ this.SetSize(Math.floor(entry.contentRect.width), Math.floor(entry.contentRect.height))
447
+ }
448
+
449
+ _LoadBatch(scene, batch) {
450
+ if (batch.key.blockName !== null &&
451
+ batch.key.geometryType !== BatchingKey.GeometryType.BLOCK_INSTANCE &&
452
+ batch.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE) {
453
+ /* Block definition. */
454
+ return
455
+ }
456
+ const objects = new Batch(this, scene, batch).CreateObjects()
457
+
458
+ const layer = this.layers.get(batch.key.layerName)
459
+
460
+ for (const obj of objects) {
461
+ this.scene.add(obj)
462
+ if (layer) {
463
+ layer.PushObject(obj)
464
+ }
465
+ }
466
+ }
467
+
468
+ _GetSimpleColorMaterial(color, instanceType = InstanceType.NONE) {
469
+ const key = new MaterialKey(instanceType, null, color, 0)
470
+ let entry = this.materials.find({key})
471
+ if (entry !== null) {
472
+ return entry.material
473
+ }
474
+ entry = {
475
+ key,
476
+ material: this._CreateSimpleColorMaterialInstance(color, instanceType)
477
+ }
478
+ this.materials.insert(entry)
479
+ return entry.material
480
+ }
481
+
482
+ _CreateSimpleColorMaterial(instanceType = InstanceType.NONE) {
483
+ const shaders = this._GenerateShaders(instanceType, false)
484
+ return new three.RawShaderMaterial({
485
+ uniforms: {
486
+ color: {
487
+ value: new three.Color(0xff00ff)
488
+ }
489
+ },
490
+ vertexShader: shaders.vertex,
491
+ fragmentShader: shaders.fragment,
492
+ depthTest: false,
493
+ depthWrite: false,
494
+ glslVersion: three.GLSL3,
495
+ side: three.DoubleSide
496
+ })
497
+ }
498
+
499
+ /** @param color {number} Color RGB numeric value.
500
+ * @param instanceType {number}
501
+ */
502
+ _CreateSimpleColorMaterialInstance(color, instanceType = InstanceType.NONE) {
503
+ const src = this.simpleColorMaterial[instanceType]
504
+ /* Should reuse compiled shaders. */
505
+ const m = src.clone()
506
+ m.uniforms.color = { value: new three.Color(color) }
507
+ return m
508
+ }
509
+
510
+ _GetSimplePointMaterial(color, instanceType = InstanceType.NONE) {
511
+ const key = new MaterialKey(instanceType, BatchingKey.GeometryType.POINTS, color, 0)
512
+ let entry = this.materials.find({key})
513
+ if (entry !== null) {
514
+ return entry.material
515
+ }
516
+ entry = {
517
+ key,
518
+ material: this._CreateSimplePointMaterialInstance(color, this.options.pointSize,
519
+ instanceType)
520
+ }
521
+ this.materials.insert(entry)
522
+ return entry.material
523
+ }
524
+
525
+ _CreateSimplePointMaterial(instanceType = InstanceType.NONE) {
526
+ const shaders = this._GenerateShaders(instanceType, true)
527
+ return new three.RawShaderMaterial({
528
+ uniforms: {
529
+ color: {
530
+ value: new three.Color(0xff00ff)
531
+ },
532
+ pointSize: {
533
+ value: 2
534
+ }
535
+ },
536
+ vertexShader: shaders.vertex,
537
+ fragmentShader: shaders.fragment,
538
+ depthTest: false,
539
+ depthWrite: false,
540
+ glslVersion: three.GLSL3
541
+ })
542
+ }
543
+
544
+ /** @param color {number} Color RGB numeric value.
545
+ * @param size {number} Rasterized point size in pixels.
546
+ * @param instanceType {number}
547
+ */
548
+ _CreateSimplePointMaterialInstance(color, size = 2, instanceType = InstanceType.NONE) {
549
+ const src = this.simplePointMaterial[instanceType]
550
+ /* Should reuse compiled shaders. */
551
+ const m = src.clone()
552
+ m.uniforms.color = { value: new three.Color(color) }
553
+ m.uniforms.size = { value: size }
554
+ return m
555
+ }
556
+
557
+ _GenerateShaders(instanceType, pointSize) {
558
+ const fullInstanceAttr = instanceType === InstanceType.FULL ?
559
+ `
560
+ /* First row. */
561
+ in vec3 instanceTransform0;
562
+ /* Second row. */
563
+ in vec3 instanceTransform1;
564
+ ` : ""
565
+ const fullInstanceTransform = instanceType === InstanceType.FULL ?
566
+ `
567
+ pos.xy = mat2(instanceTransform0[0], instanceTransform1[0],
568
+ instanceTransform0[1], instanceTransform1[1]) * pos.xy +
569
+ vec2(instanceTransform0[2], instanceTransform1[2]);
570
+ ` : ""
571
+
572
+ const pointInstanceAttr = instanceType === InstanceType.POINT ?
573
+ `
574
+ in vec2 instanceTransform;
575
+ ` : ""
576
+ const pointInstanceTransform = instanceType === InstanceType.POINT ?
577
+ `
578
+ pos.xy += instanceTransform;
579
+ ` : ""
580
+
581
+ const pointSizeUniform = pointSize ? "uniform float pointSize;" : ""
582
+ const pointSizeAssigment = pointSize ? "gl_PointSize = pointSize;" : ""
583
+
584
+ return {
585
+ vertex: `
586
+
587
+ precision highp float;
588
+ precision highp int;
589
+ in vec2 position;
590
+ ${fullInstanceAttr}
591
+ ${pointInstanceAttr}
592
+ uniform mat4 modelViewMatrix;
593
+ uniform mat4 projectionMatrix;
594
+ ${pointSizeUniform}
595
+
596
+ void main() {
597
+ vec4 pos = vec4(position, 0.0, 1.0);
598
+ ${fullInstanceTransform}
599
+ ${pointInstanceTransform}
600
+ gl_Position = projectionMatrix * modelViewMatrix * pos;
601
+ ${pointSizeAssigment}
602
+ }
603
+ `,
604
+ fragment: `
605
+
606
+ precision highp float;
607
+ precision highp int;
608
+ uniform vec3 color;
609
+ out vec4 fragColor;
610
+
611
+ void main() {
612
+ fragColor = vec4(color, 1.0);
613
+ }
614
+ `
615
+ }
616
+ }
617
+
618
+ /** Ensure the color is contrast enough with current background color.
619
+ * @param color {number} RGB value.
620
+ * @return {number} RGB value to use for rendering.
621
+ */
622
+ _TransformColor(color) {
623
+ if (!this.options.colorCorrection && !this.options.blackWhiteInversion) {
624
+ return color
625
+ }
626
+ /* Just black and white inversion. */
627
+ const bkgLum = Luminance(this.clearColor)
628
+ if (color === 0xffffff && bkgLum >= 0.8) {
629
+ return 0
630
+ }
631
+ if (color === 0 && bkgLum <= 0.2) {
632
+ return 0xffffff
633
+ }
634
+ if (!this.options.colorCorrection) {
635
+ return color
636
+ }
637
+ const fgLum = Luminance(color)
638
+ const MIN_TARGET_RATIO = 1.5
639
+ const contrast = ContrastRatio(color, this.clearColor)
640
+ const diff = contrast >= 1 ? contrast : 1 / contrast
641
+ if (diff < MIN_TARGET_RATIO) {
642
+ let targetLum
643
+ if (bkgLum > 0.5) {
644
+ targetLum = bkgLum / 2
645
+ } else {
646
+ targetLum = bkgLum * 2
647
+ }
648
+ if (targetLum > fgLum) {
649
+ color = Lighten(color, targetLum / fgLum)
650
+ } else {
651
+ color = Darken(color, fgLum / targetLum)
652
+ }
653
+ }
654
+ return color
655
+ }
656
+ }
657
+
658
+ DxfViewer.MessageLevel = MessageLevel
659
+
660
+ DxfViewer.DefaultOptions = {
661
+ canvasWidth: 400,
662
+ canvasHeight: 300,
663
+ /** Automatically resize canvas when the container is resized. This options
664
+ * utilizes ResizeObserver API which is still not fully standardized. The specified canvas size
665
+ * is ignored if the option is enabled.
666
+ */
667
+ autoResize: false,
668
+ /** Frame buffer clear color. */
669
+ clearColor: new three.Color("#000"),
670
+ /** Frame buffer clear color alpha value. */
671
+ clearAlpha: 1.0,
672
+ /** Use alpha channel in a framebuffer. */
673
+ canvasAlpha: false,
674
+ /** Assume premultiplied alpha in a framebuffer. */
675
+ canvasPremultipliedAlpha: true,
676
+ /** Use antialiasing. May degrade performance on poor hardware. */
677
+ antialias: true,
678
+ /** Correct entities colors to ensure that they are always visible with the current background
679
+ * color.
680
+ */
681
+ colorCorrection: false,
682
+ /** Simpler version of colorCorrection - just invert pure white or black entities if they are
683
+ * invisible on current background color.
684
+ */
685
+ blackWhiteInversion: true,
686
+ /** Size in pixels for rasterized points (dot mark). */
687
+ pointSize: 2,
688
+ /** Scene generation options. */
689
+ sceneOptions: DxfScene.DefaultOptions,
690
+ /** Retain the simple object representing the parsed DXF - will consume a lot of additional memory */
691
+ retainParsedDxf: false
692
+ }
693
+
694
+ DxfViewer.SetupWorker = function () {
695
+ new DxfWorker(self, true)
696
+ }
697
+
698
+ const InstanceType = Object.freeze({
699
+ /** Not instanced. */
700
+ NONE: 0,
701
+ /** Full affine transform per instance. */
702
+ FULL: 1,
703
+ /** Point instances, 2D-translation vector per instance. */
704
+ POINT: 2,
705
+
706
+ /** Number of types. */
707
+ MAX: 3
708
+ })
709
+
710
+ class Batch {
711
+ /**
712
+ * @param viewer {DxfViewer}
713
+ * @param scene Serialized scene.
714
+ * @param batch Serialized scene batch.
715
+ */
716
+ constructor(viewer, scene, batch) {
717
+ this.viewer = viewer
718
+ this.key = batch.key
719
+
720
+ if (batch.hasOwnProperty("verticesOffset")) {
721
+ const verticesArray =
722
+ new Float32Array(scene.vertices,
723
+ batch.verticesOffset * Float32Array.BYTES_PER_ELEMENT,
724
+ batch.verticesSize)
725
+ if (this.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE ||
726
+ scene.pointShapeHasDot) {
727
+ this.vertices = new three.BufferAttribute(verticesArray, 2)
728
+ }
729
+ if (this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) {
730
+ this.transforms = new three.InstancedBufferAttribute(verticesArray, 2)
731
+ }
732
+ }
733
+
734
+ if (batch.hasOwnProperty("chunks")) {
735
+ this.chunks = []
736
+ for (const rawChunk of batch.chunks) {
737
+
738
+ const verticesArray =
739
+ new Float32Array(scene.vertices,
740
+ rawChunk.verticesOffset * Float32Array.BYTES_PER_ELEMENT,
741
+ rawChunk.verticesSize)
742
+ const indicesArray =
743
+ new Uint16Array(scene.indices,
744
+ rawChunk.indicesOffset * Uint16Array.BYTES_PER_ELEMENT,
745
+ rawChunk.indicesSize)
746
+ this.chunks.push({
747
+ vertices: new three.BufferAttribute(verticesArray, 2),
748
+ indices: new three.BufferAttribute(indicesArray, 1)
749
+ })
750
+ }
751
+ }
752
+
753
+ if (batch.hasOwnProperty("transformsOffset")) {
754
+ const transformsArray =
755
+ new Float32Array(scene.transforms,
756
+ batch.transformsOffset * Float32Array.BYTES_PER_ELEMENT,
757
+ batch.transformsSize)
758
+ /* Each transform is 3x2 matrix which is split into two 3D vectors which will occupy two
759
+ * attribute slots.
760
+ */
761
+ const buf = new three.InstancedInterleavedBuffer(transformsArray, 6)
762
+ this.transforms0 = new three.InterleavedBufferAttribute(buf, 3, 0)
763
+ this.transforms1 = new three.InterleavedBufferAttribute(buf, 3, 3)
764
+ }
765
+
766
+ if (this.key.geometryType === BatchingKey.GeometryType.BLOCK_INSTANCE ||
767
+ this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) {
768
+
769
+ const layer = this.viewer.layers.get(this.key.layerName)
770
+ if (layer) {
771
+ this.layerColor = layer.color
772
+ } else {
773
+ this.layerColor = 0
774
+ }
775
+ }
776
+ }
777
+
778
+ GetInstanceType() {
779
+ switch (this.key.geometryType) {
780
+ case BatchingKey.GeometryType.BLOCK_INSTANCE:
781
+ return InstanceType.FULL
782
+ case BatchingKey.GeometryType.POINT_INSTANCE:
783
+ return InstanceType.POINT
784
+ default:
785
+ return InstanceType.NONE
786
+ }
787
+ }
788
+
789
+ /** Create scene objects corresponding to batch data.
790
+ * @param instanceBatch {?Batch} Batch with instance transform. Null for non-instanced object.
791
+ */
792
+ *CreateObjects(instanceBatch = null) {
793
+ if (this.key.geometryType === BatchingKey.GeometryType.BLOCK_INSTANCE ||
794
+ this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) {
795
+
796
+ if (instanceBatch !== null) {
797
+ throw new Error("Unexpected instance batch specified for instance batch")
798
+ }
799
+ yield* this._CreateBlockInstanceObjects()
800
+ return
801
+ }
802
+ yield* this._CreateObjects(instanceBatch)
803
+ }
804
+
805
+ *_CreateObjects(instanceBatch) {
806
+ const color = instanceBatch ?
807
+ instanceBatch._GetInstanceColor(this.key.color) : this.key.color
808
+
809
+ //XXX line type
810
+ const materialFactory =
811
+ this.key.geometryType === BatchingKey.GeometryType.POINTS ||
812
+ this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE ?
813
+ this.viewer._GetSimplePointMaterial : this.viewer._GetSimpleColorMaterial
814
+
815
+ const material = materialFactory.call(this.viewer, this.viewer._TransformColor(color),
816
+ instanceBatch?.GetInstanceType() ?? InstanceType.NONE)
817
+
818
+ let objConstructor
819
+ switch (this.key.geometryType) {
820
+ case BatchingKey.GeometryType.POINTS:
821
+ /* This method also called for creating dots for shaped point instances. */
822
+ case BatchingKey.GeometryType.POINT_INSTANCE:
823
+ objConstructor = three.Points
824
+ break
825
+ case BatchingKey.GeometryType.LINES:
826
+ case BatchingKey.GeometryType.INDEXED_LINES:
827
+ objConstructor = three.LineSegments
828
+ break
829
+ case BatchingKey.GeometryType.TRIANGLES:
830
+ case BatchingKey.GeometryType.INDEXED_TRIANGLES:
831
+ objConstructor = three.Mesh
832
+ break
833
+ default:
834
+ throw new Error("Unexpected geometry type:" + this.key.geometryType)
835
+ }
836
+
837
+ function CreateObject(vertices, indices) {
838
+ const geometry = instanceBatch ?
839
+ new three.InstancedBufferGeometry() : new three.BufferGeometry()
840
+ geometry.setAttribute("position", vertices)
841
+ instanceBatch?._SetInstanceTransformAttribute(geometry)
842
+ if (indices) {
843
+ geometry.setIndex(indices)
844
+ }
845
+ const obj = new objConstructor(geometry, material)
846
+ obj.frustumCulled = false
847
+ obj.matrixAutoUpdate = false
848
+ return obj
849
+ }
850
+
851
+ if (this.chunks) {
852
+ for (const chunk of this.chunks) {
853
+ yield CreateObject(chunk.vertices, chunk.indices)
854
+ }
855
+ } else {
856
+ yield CreateObject(this.vertices)
857
+ }
858
+ }
859
+
860
+ /**
861
+ * @param geometry {InstancedBufferGeometry}
862
+ */
863
+ _SetInstanceTransformAttribute(geometry) {
864
+ if (!geometry.isInstancedBufferGeometry) {
865
+ throw new Error("InstancedBufferGeometry expected")
866
+ }
867
+ if (this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) {
868
+ geometry.setAttribute("instanceTransform", this.transforms)
869
+ } else {
870
+ geometry.setAttribute("instanceTransform0", this.transforms0)
871
+ geometry.setAttribute("instanceTransform1", this.transforms1)
872
+ }
873
+ }
874
+
875
+ *_CreateBlockInstanceObjects() {
876
+ const block = this.viewer.blocks.get(this.key.blockName)
877
+ if (!block) {
878
+ return
879
+ }
880
+ for (const batch of block.batches) {
881
+ yield* batch.CreateObjects(this)
882
+ }
883
+ if (this.hasOwnProperty("vertices")) {
884
+ /* Dots for point shapes. */
885
+ yield* this._CreateObjects()
886
+ }
887
+ }
888
+
889
+ /**
890
+ * @param defColor {number} Color value for block definition batch.
891
+ * @return {number} RGB color value for a block instance.
892
+ */
893
+ _GetInstanceColor(defColor) {
894
+ if (defColor === ColorCode.BY_BLOCK) {
895
+ return this.key.color
896
+ } else if (defColor === ColorCode.BY_LAYER) {
897
+ return this.layerColor
898
+ } else {
899
+ return defColor
900
+ }
901
+ }
902
+ }
903
+
904
+ class Layer {
905
+ constructor(name, displayName, color) {
906
+ this.name = name
907
+ this.displayName = displayName
908
+ this.color = color
909
+ this.objects = []
910
+ }
911
+
912
+ PushObject(obj) {
913
+ this.objects.push(obj)
914
+ }
915
+
916
+ Dispose() {
917
+ for (const obj of this.objects) {
918
+ obj.geometry.dispose()
919
+ }
920
+ this.objects = null
921
+ }
922
+ }
923
+
924
+ class Block {
925
+ constructor() {
926
+ this.batches = []
927
+ }
928
+
929
+ /** @param batch {Batch} */
930
+ PushBatch(batch) {
931
+ this.batches.push(batch)
932
+ }
933
+ }
934
+
935
+ /** Custom viewer event names are prefixed with this string. */
936
+ const EVENT_NAME_PREFIX = "__dxf_"
937
+
938
+ /** Transform sRGB color component to linear color space. */
939
+ function LinearColor(c) {
940
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
941
+ }
942
+
943
+ /** Transform linear color component to sRGB color space. */
944
+ function SRgbColor(c) {
945
+ return c < 0.003 ? c * 12.92 : Math.pow(c, 1 / 2.4) * 1.055 - 0.055
946
+ }
947
+
948
+ /** Get relative luminance value for a color.
949
+ * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
950
+ * @param color {number} RGB color value.
951
+ * @return {number} Luminance value in range [0; 1].
952
+ */
953
+ function Luminance(color) {
954
+ const r = LinearColor(((color & 0xff0000) >>> 16) / 255)
955
+ const g = LinearColor(((color & 0xff00) >>> 8) / 255)
956
+ const b = LinearColor((color & 0xff) / 255)
957
+
958
+ return r * 0.2126 + g * 0.7152 + b * 0.0722
959
+ }
960
+
961
+ /**
962
+ * Get contrast ratio for a color pair.
963
+ * https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
964
+ * @param c1
965
+ * @param c2
966
+ * @return {number} Contrast ratio between the colors. Greater than one if the first color color is
967
+ * brighter than the second one.
968
+ */
969
+ function ContrastRatio(c1, c2) {
970
+ return (Luminance(c1) + 0.05) / (Luminance(c2) + 0.05)
971
+ }
972
+
973
+ function HlsToRgb({h, l, s}) {
974
+ let r, g, b
975
+ if (s === 0) {
976
+ /* Achromatic */
977
+ r = g = b = l
978
+ } else {
979
+ function hue2rgb(p, q, t) {
980
+ if (t < 0) {
981
+ t += 1
982
+ }
983
+ if (t > 1) {
984
+ t -= 1
985
+ }
986
+ if (t < 1/6) {
987
+ return p + (q - p) * 6 * t
988
+ }
989
+ if (t < 1/2) {
990
+ return q
991
+ }
992
+ if (t < 2/3) {
993
+ return p + (q - p) * (2/3 - t) * 6
994
+ }
995
+ return p
996
+ }
997
+
998
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s
999
+ const p = 2 * l - q
1000
+ r = hue2rgb(p, q, h + 1/3)
1001
+ g = hue2rgb(p, q, h)
1002
+ b = hue2rgb(p, q, h - 1/3)
1003
+ }
1004
+
1005
+ return (Math.min(Math.floor(SRgbColor(r) * 256), 255) << 16) |
1006
+ (Math.min(Math.floor(SRgbColor(g) * 256), 255) << 8) |
1007
+ Math.min(Math.floor(SRgbColor(b) * 256), 255)
1008
+ }
1009
+
1010
+ function RgbToHls(color) {
1011
+ const r = LinearColor(((color & 0xff0000) >>> 16) / 255)
1012
+ const g = LinearColor(((color & 0xff00) >>> 8) / 255)
1013
+ const b = LinearColor((color & 0xff) / 255)
1014
+
1015
+ const max = Math.max(r, g, b)
1016
+ const min = Math.min(r, g, b)
1017
+ let h, s
1018
+ const l = (max + min) / 2
1019
+
1020
+ if (max === min) {
1021
+ /* Achromatic */
1022
+ h = s = 0
1023
+ } else {
1024
+ const d = max - min
1025
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
1026
+ switch (max) {
1027
+ case r:
1028
+ h = (g - b) / d + (g < b ? 6 : 0)
1029
+ break;
1030
+ case g:
1031
+ h = (b - r) / d + 2
1032
+ break
1033
+ case b:
1034
+ h = (r - g) / d + 4
1035
+ break
1036
+ }
1037
+ h /= 6
1038
+ }
1039
+
1040
+ return {h, l, s}
1041
+ }
1042
+
1043
+ function Lighten(color, factor) {
1044
+ const hls = RgbToHls(color)
1045
+ hls.l *= factor
1046
+ if (hls.l > 1) {
1047
+ hls.l = 1
1048
+ }
1049
+ return HlsToRgb(hls)
1050
+ }
1051
+
1052
+ function Darken(color, factor) {
1053
+ const hls = RgbToHls(color)
1054
+ hls.l /= factor
1055
+ return HlsToRgb(hls)
1056
+ }