Files
dwg-dxf-viewer-sample/index.html
T
minsungandClaude Opus 5 ce3859c487 docs: ship the acadrust MPL-2.0 notice, license text and local patch
The deployed site serves a .wasm with acadrust compiled into it, and our
vendored copy carries one modified MPL file
(src/io/dwg/dwg_stream_readers/object_reader/entities.rs, the read_mesh
array-count bounds fix). That is Executable Form distribution of modified
Covered Software, so MPL-2.0 3.2 requires the Source Code Form to be
available and recipients to be told how to get it. Nothing shipped said
so: no license text, no notice, no obtainable source - the upstream
mirror repos are private.

Add THIRD-PARTY-NOTICES.md plus public/licenses/ (MPL-2.0 text, the
read_mesh patch against the pristine 0.4.1 crate, and a served copy of
the notice), and link them from the page so recipients can actually find
them. Original crate download plus the patch reproduces the exact source
compiled into the wasm.

MPL does not require upstreaming, and the lineweight work did not touch
any MPL file - dwg-wasm/src/lib.rs is our MIT wrapper. docs/license-mpl2-
acadrust.md records the analysis and the checklist for future changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 17:59:10 +09:00

289 lines
13 KiB
HTML

<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DWG/DXF · 모듈 조합 샘플</title>
<style>
:root {
--bg: #0d1117; --panel: #161b22; --ink: #e6edf3; --muted: #8b949e;
--accent: #3fb950; --wasm: #f0a03c; --line: #30363d; --danger: #f85149;
--mono: ui-monospace, "Cascadia Code", Consolas, monospace;
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Malgun Gothic", sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink); font-family: var(--sans); }
#app { position: fixed; inset: 0; }
/* ── 상단 로딩 프로그레스 바 (src/topProgress.ts 가 구동) ───────────────
.bar : 실제 진행률. width 트랜지션 = 메인 스레드 의존.
.sweep : WASM 파싱 / Viewer2D.load() 같은 동기 블로킹 구간용. transform
키프레임은 컴포지터 스레드에서 돌기 때문에 메인 스레드가 멈춰
있어도 계속 흐른다 — 그 구간에 줄 수 있는 유일한 피드백. */
#progress {
position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 100;
pointer-events: none; overflow: hidden;
opacity: 0; transition: opacity .3s ease .15s;
}
#progress.on { opacity: 1; transition-delay: 0s; }
#progress .bar {
position: absolute; top: 0; bottom: 0; left: 0; width: 0;
background: linear-gradient(90deg, var(--accent), #58a6ff 55%, var(--wasm));
box-shadow: 0 0 10px rgba(63,185,80,.65), 0 0 4px rgba(88,166,255,.5);
transition: width .28s cubic-bezier(.25,.8,.25,1);
}
#progress.err .bar {
background: var(--danger);
box-shadow: 0 0 10px rgba(248,81,73,.7);
}
#progress .sweep {
position: absolute; top: 0; bottom: 0; left: 0; width: 34%;
background: linear-gradient(90deg, transparent, rgba(230,237,243,.9), transparent);
transform: translateX(-105%); will-change: transform; opacity: 0;
}
#progress.busy .sweep {
opacity: 1;
animation: pr-sweep 1.15s cubic-bezier(.4,0,.2,1) infinite;
}
@keyframes pr-sweep {
from { transform: translateX(-105%); }
to { transform: translateX(400%); }
}
@media (prefers-reduced-motion: reduce) {
#progress .sweep { animation: none; }
#progress .bar { transition: none; }
}
#toolbar {
position: fixed; top: 12px; left: 12px; right: 12px; z-index: 10;
display: flex; flex-wrap: wrap; gap: 8px; align-items: center; pointer-events: none;
}
#toolbar > * { pointer-events: auto; }
button, label.btn {
background: var(--panel); color: var(--ink); border: 1px solid var(--line);
border-radius: 8px; padding: 8px 12px; font: 13px var(--sans); cursor: pointer;
}
button:hover, label.btn:hover { border-color: var(--accent); }
button.primary { background: #238636; border-color: #2ea043; }
button.wasm { border-color: var(--wasm); color: var(--wasm); }
label.btn input { display: none; }
#status {
margin-left: auto; font: 12px var(--mono); color: var(--muted);
background: rgba(22,27,34,.85); border: 1px solid var(--line);
border-radius: 8px; padding: 8px 12px; max-width: min(48vw, 480px);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#status.err { color: var(--danger); }
#recipe {
position: fixed; bottom: 14px; left: 14px; z-index: 10; max-width: 340px;
background: rgba(22,27,34,.94); border: 1px solid var(--line); border-radius: 10px;
padding: 12px 14px; font-size: 12px; line-height: 1.45; color: var(--muted);
box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
#recipe h3 {
margin: 0 0 8px; font: 600 11px var(--mono); letter-spacing: .1em;
text-transform: uppercase; color: var(--accent);
}
#recipe ol { margin: 0; padding-left: 18px; }
#recipe li { margin-bottom: 4px; }
#recipe code { font-family: var(--mono); font-size: 11px; color: var(--ink); }
#recipe .mod-w { color: var(--wasm); }
#recipe .mod-j { color: var(--accent); }
#recipe a { color: var(--accent); font-family: var(--mono); font-size: 11px; }
#layers {
position: fixed; top: 56px; right: 12px; width: 200px; max-height: 50vh;
overflow: auto; z-index: 10; background: rgba(22,27,34,.95);
border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
font-size: 12px; display: none;
}
#layers h3 { margin: 0 0 8px; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
#layers label { display: flex; gap: 8px; align-items: center; margin: 4px 0; cursor: pointer; }
#layers .swatch { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
#spaces {
display: none; align-items: center; gap: 0;
background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
overflow: hidden;
}
#spaces button {
border: none; border-radius: 0; border-right: 1px solid var(--line);
background: transparent; padding: 8px 12px; margin: 0;
}
#spaces button:last-child { border-right: none; }
#spaces button.active {
background: #238636; border-color: #2ea043; color: #fff;
}
#spaces button:hover:not(.active) { border-color: transparent; color: var(--accent); }
/* CAD 좌표 — 항상 우측 하단 (툴바 status 와 별개) */
#coords {
position: fixed;
right: 12px;
bottom: 12px;
left: auto;
top: auto;
z-index: 20;
font: 13px/1.35 var(--mono);
color: var(--ink);
background: rgba(13,17,23,.95);
border: 1px solid var(--line);
border-radius: 8px;
padding: 10px 14px;
min-width: 240px;
text-align: left;
pointer-events: none;
letter-spacing: 0.02em;
box-shadow: 0 8px 28px rgba(0,0,0,.45);
user-select: none;
}
#coords .lbl { color: var(--accent); margin-right: 6px; font-weight: 600; }
#coords .sep { color: var(--line); margin: 0 12px; }
/* 공통 패널 윈도우 스타일 (Properties & Layers - Draggable & Resizable) */
.cad-panel {
position: fixed; top: 56px; width: 320px; height: 480px;
min-width: 250px; min-height: 140px; max-width: 90vw; max-height: 90vh;
z-index: 15; background: rgba(22,27,34,.96); backdrop-filter: blur(12px);
border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.5);
display: flex; flex-direction: column; overflow: hidden; resize: both;
transition: border-color .15s;
}
.cad-panel.collapsed {
height: 42px !important;
min-height: 42px !important;
max-height: 42px !important;
overflow: hidden !important;
resize: none !important;
}
.cad-panel .hdr {
padding: 10px 14px; border-bottom: 1px solid var(--line);
display: flex; align-items: center; justify-content: space-between;
background: rgba(13,17,23,.7); position: sticky; top: 0; z-index: 5;
cursor: grab; user-select: none; flex-shrink: 0;
}
.cad-panel .hdr:active { cursor: grabbing; }
.cad-panel .hdr h3 { margin: 0; font-size: 13px; font-weight: 700; color: var(--ink); display: flex; align-items: center; gap: 6px; }
.cad-panel .hdr-actions { display: flex; gap: 4px; align-items: center; }
.cad-panel .hdr-btn {
background: none; border: none; color: var(--muted); font-size: 13px;
cursor: pointer; padding: 2px 6px; border-radius: 4px; display: inline-flex;
align-items: center; justify-content: center; transition: all .15s;
}
.cad-panel .hdr-btn:hover { color: var(--ink); background: rgba(255,255,255,.1); }
#properties { right: 12px; }
#layers { right: 344px; display: none; }
#propBody { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
#layerBody { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
#layerList { flex: 1; overflow-y: auto; }
#zoomSelBtn {
font: 11px var(--sans); padding: 3px 8px; border-radius: 6px;
border: 1px solid var(--accent); background: rgba(63,185,80,.15); color: var(--accent);
cursor: pointer; display: none; align-items: center; gap: 4px; transition: all .15s;
}
#zoomSelBtn:hover { background: var(--accent); color: #fff; }
.layer-search-wrap { padding: 8px 10px; border-bottom: 1px solid var(--line); background: rgba(13,17,23,.3); }
.layer-search-input {
width: 100%; background: var(--bg); border: 1px solid var(--line);
border-radius: 6px; padding: 6px 10px; color: var(--ink); font-size: 12px;
outline: none; transition: border-color .15s;
}
.layer-search-input:focus { border-color: var(--accent); }
.layer-sub-actions {
display: flex; gap: 6px; padding: 6px 10px; border-bottom: 1px solid var(--line);
background: rgba(22,27,34,.5);
}
.layer-sub-btn {
font-size: 11px; padding: 2px 8px; border-radius: 4px; border: 1px solid var(--line);
background: transparent; color: var(--muted); cursor: pointer; transition: all .15s;
}
.layer-sub-btn:hover { color: var(--ink); border-color: var(--accent); background: rgba(63,185,80,.1); }
</style>
</head>
<body>
<div id="app"></div>
<div id="toolbar">
<label class="btn">
Open file
<input id="file" type="file" accept=".dwg,.dxf" />
</label>
<button id="sample" class="primary wasm" type="button" title="acadrust-dwg WASM">Sample DWG</button>
<button id="sampleCivil" class="primary wasm" type="button" title="Civil Plan & Profile DWG">평면및종단면도.dwg</button>
<button id="sampleDxf" class="primary" type="button" title="dxf-parser">Sample DXF</button>
<div id="spaces" title="Model Space / Paper Space (Layout)"></div>
<button id="fit" type="button">Fit (F)</button>
<button id="theme" type="button">Theme</button>
<button id="lwt" type="button" title="선가중치 표시 (AutoCAD LWDISPLAY)">선가중치</button>
<button id="layersBtn" type="button">Layers</button>
<button id="propsBtn" type="button">Props (속성)</button>
<div id="status">ready</div>
</div>
<div id="coords" title="마우스 위치 CAD 좌표 (Model=UCS, Layout=Paper 도면단위)">
<span class="lbl">X</span><span id="coordX"></span>
<span class="sep">|</span>
<span class="lbl">Y</span><span id="coordY"></span>
</div>
<!-- 레이어 (Layers) 관리 패널 -->
<div id="layers" class="cad-panel">
<div class="hdr">
<h3>
<span>🎨</span> 레이어 (Layers)
<span id="layerCount" style="font-family:var(--mono);font-size:11px;color:var(--muted);background:rgba(255,255,255,.08);padding:1px 6px;border-radius:4px;font-weight:400"></span>
</h3>
<div class="hdr-actions">
<button id="collapseLayersBtn" class="hdr-btn collapse-btn" type="button" title="접기"></button>
<button id="closeLayersBtn" class="hdr-btn" type="button" title="닫기"></button>
</div>
</div>
<div id="layerBody">
<div class="layer-search-wrap">
<input id="layerSearch" class="layer-search-input" type="text" placeholder="🔍 레이어 검색..." />
</div>
<div class="layer-sub-actions">
<button id="layerShowAll" class="layer-sub-btn" type="button">전체 켜기</button>
<button id="layerHideAll" class="layer-sub-btn" type="button">전체 끄기</button>
</div>
<div id="layerList"></div>
</div>
</div>
<!-- 속성 (Properties) 패널 -->
<div id="properties" class="cad-panel">
<div class="hdr">
<h3><span>📋</span> 속성 (Properties)</h3>
<div class="hdr-actions">
<button id="zoomSelBtn" type="button" title="선택 요소 위치로 확대">🔍 선택 확대</button>
<button id="collapsePropsBtn" class="hdr-btn collapse-btn" type="button" title="접기"></button>
<button id="closePropsBtn" class="hdr-btn" type="button" title="닫기"></button>
</div>
</div>
<div id="propBody"></div>
</div>
<aside id="recipe">
<h3>Module recipe</h3>
<ol>
<li><code class="mod-j">ext2d</code> — .dwg / .dxf 분기</li>
<li><code class="mod-w">acadrust-dwg</code> 또는 <code class="mod-j">dxf-parser</code></li>
<li><code class="mod-j">dxfAdapter</code> — DXF → 공통 스키마</li>
<li><code class="mod-j">Viewer2D</code> + <code class="mod-j">three</code> 렌더</li>
<li><code class="mod-j">slugText</code> + TTF 폰트</li>
</ol>
<p style="margin:10px 0 0">
코드: <code>src/main.ts</code> ·
지도: <a href="/docs/modules-dwg-dxf.html" target="_blank">docs/modules-dwg-dxf.html</a>
</p>
<!-- MPL-2.0 §3.2(b): recipients of the wasm must be told where the
acadrust source (with our local patch) can be obtained. -->
<p style="margin:6px 0 0">
오픈소스 고지: <a href="/licenses/THIRD-PARTY-NOTICES.md" target="_blank">THIRD-PARTY-NOTICES</a>
(acadrust · MPL-2.0 · <a href="/licenses/acadrust-0.4.1-read_mesh.patch" target="_blank">local patch</a>)
</p>
</aside>
<script type="module" src="/src/main.ts"></script>
</body>
</html>