📦 Initialize Geulbeot structure and merge Prompts & test projects
This commit is contained in:
0
03. Code/geulbeot_10th/domain/__init__.py
Normal file
0
03. Code/geulbeot_10th/domain/__init__.py
Normal file
0
03. Code/geulbeot_10th/domain/civil/dx.txt
Normal file
0
03. Code/geulbeot_10th/domain/civil/dx.txt
Normal file
1
03. Code/geulbeot_10th/domain/civil/general.txt
Normal file
1
03. Code/geulbeot_10th/domain/civil/general.txt
Normal file
@@ -0,0 +1 @@
|
||||
도레미파솔라시도
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/bim.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/bim.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/bridge.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/bridge.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/planning.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/planning.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/road.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/road.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/safety.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/safety.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/survey.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/survey.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
27
03. Code/geulbeot_10th/domain/civil/specialties/tunnel.txt
Normal file
27
03. Code/geulbeot_10th/domain/civil/specialties/tunnel.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
시추조사, 지반조사, 지질조사, 보링조사, 현장조사,
|
||||
시추공, 시추공별, 시추계획, 시추위치, 시추심도, 시추기,
|
||||
주상도, 지층주상도, 시추주상도, 토질주상도,
|
||||
표준관입시험, SPT, N값, 관입시험, 현장시험,
|
||||
지층, 지층정보, 지층구성, 지층분류, 지층경계,
|
||||
토사, 점토, 실트, 모래, 자갈, 호박돌, 전석,
|
||||
풍화토, 풍화암, 연암, 보통암, 경암, 극경암,
|
||||
지하수, 지하수위, 수위측정, 투수시험, 현장투수시험,
|
||||
토질, 토질시험, 토질역학, 토질조사,
|
||||
샘플링, 불교란시료, 교란시료, 코어, 코어채취,
|
||||
지반, 지반정보, 지반특성, 지반강도, 지반침하,
|
||||
기초, 기초설계, 말뚝기초, 직접기초, 깊은기초,
|
||||
측량, GPS측량, GNSS, RTK, 토탈스테이션, 레벨측량,
|
||||
수치지형도, 정사영상, 수치표고모델, DEM, DSM, DTM,
|
||||
좌표, 좌표계, TM좌표, UTM좌표, 경위도, 지오이드,
|
||||
드론, UAV, 무인항공기, 항공측량, 드론측량,
|
||||
DJI, Terra, Pix4D, 포인트클라우드, 정밀도,
|
||||
성과품, 성과도, 보고서, 조사보고서, 지반조사보고서,
|
||||
위치도, 평면도, 종단면도, 횡단면도, 단면도,
|
||||
현장, 현장조사, 현장시험, 현장작업, 현장지시,
|
||||
IoT센서, 센서, 자동입력, 실시간, 웹기반,
|
||||
토공, 절토, 성토, 물량산출, 체적계산,
|
||||
도로, 도로노선, 노선측량, 중심선, 기준점,
|
||||
신규, 기존, 추가, 수정, 검토, 승인,
|
||||
주상도, 시추주상도, 토질주상도, 지층주상도,
|
||||
신규, 신규의, 기존의,
|
||||
지반조사, 지반조사업무
|
||||
0
03. Code/geulbeot_10th/domain/hwpx/__init__.py
Normal file
0
03. Code/geulbeot_10th/domain/hwpx/__init__.py
Normal file
769
03. Code/geulbeot_10th/domain/hwpx/hwpx_domain_guide.md
Normal file
769
03. Code/geulbeot_10th/domain/hwpx/hwpx_domain_guide.md
Normal file
@@ -0,0 +1,769 @@
|
||||
# HWP/HWPX ↔ HTML/CSS 도메인 가이드
|
||||
|
||||
> **목적**: HWPX에서 문서 유형·스타일·템플릿을 추출하거나, HTML → HWPX → HWP 변환 시
|
||||
> 하드코딩 없이 이 가이드를 참조하여 정확한 매핑을 수행한다.
|
||||
> **출처**: 한글과컴퓨터 공식 "글 문서 파일 구조 5.0" (revision 1.3, 2018-11-08)
|
||||
> **범위**: HWP 5.0 바이너리 스펙의 개념 체계 + HWPX XML 태그 + HTML/CSS 매핑
|
||||
|
||||
---
|
||||
|
||||
## 0. 문서 형식 관계
|
||||
|
||||
```
|
||||
HWP (바이너리) HWPX (XML) HTML/CSS
|
||||
───────────────── ───────────────────── ─────────────────
|
||||
Compound File ZIP Archive 단일 HTML 파일
|
||||
├─ FileHeader ├─ META-INF/ ├─ <head>
|
||||
├─ DocInfo │ └─ manifest.xml │ ├─ <meta>
|
||||
│ (글꼴, 스타일, ├─ Contents/ │ └─ <style>
|
||||
│ 테두리/배경, │ ├─ header.xml └─ <body>
|
||||
│ 글자모양 등) │ │ (DocInfo 대응) ├─ 헤더 영역
|
||||
├─ BodyText/ │ ├─ section0.xml │ ├─ 본문
|
||||
│ └─ Section0 │ │ (본문 대응) │ └─ 푸터 영역
|
||||
├─ BinData/ │ └─ section1.xml └─ @page CSS
|
||||
│ └─ 이미지 등 ├─ BinData/
|
||||
└─ PrvImage │ └─ 이미지 파일
|
||||
└─ version.xml
|
||||
```
|
||||
|
||||
**핵심**: HWP 바이너리의 레코드 구조와 HWPX XML의 엘리먼트는 1:1 대응한다.
|
||||
이 가이드는 두 형식의 공통 개념 체계를 기준으로, CSS 변환까지 연결한다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 단위 체계
|
||||
|
||||
### 1.1 HWPUNIT (글 내부 단위)
|
||||
|
||||
HWP는 1/7200 인치를 기본 단위로 사용한다.
|
||||
|
||||
| 변환 대상 | 공식 | 예시 |
|
||||
|-----------|------|------|
|
||||
| HWPUNIT → mm | `hwpunit / 7200 * 25.4` | 7200 → 25.4mm (= 1인치) |
|
||||
| HWPUNIT → pt | `hwpunit / 7200 * 72` | 7200 → 72pt |
|
||||
| HWPUNIT → px (96dpi) | `hwpunit / 7200 * 96` | 7200 → 96px |
|
||||
| mm → HWPUNIT | `mm / 25.4 * 7200` | 25.4mm → 7200 |
|
||||
| pt → HWPUNIT | `pt / 72 * 7200` | 10pt → 1000 |
|
||||
|
||||
```python
|
||||
def hwpunit_to_mm(hwpunit): return hwpunit / 7200 * 25.4
|
||||
def hwpunit_to_pt(hwpunit): return hwpunit / 7200 * 72
|
||||
def hwpunit_to_px(hwpunit): return hwpunit / 7200 * 96
|
||||
def mm_to_hwpunit(mm): return mm / 25.4 * 7200
|
||||
```
|
||||
|
||||
### 1.2 글자 크기 (CharShape)
|
||||
|
||||
HWP의 글자 크기는 HWPUNIT 단위이지만 100배 스케일이 적용되어 있다.
|
||||
|
||||
| HWP 값 | 실제 크기 | CSS |
|
||||
|--------|----------|-----|
|
||||
| 1000 | 10pt | `font-size: 10pt` |
|
||||
| 1200 | 12pt | `font-size: 12pt` |
|
||||
| 2400 | 24pt | `font-size: 24pt` |
|
||||
|
||||
```python
|
||||
def charsize_to_pt(hwp_size): return hwp_size / 100 # 1000 → 10pt
|
||||
```
|
||||
|
||||
### 1.3 COLORREF (색상)
|
||||
|
||||
HWP는 0x00BBGGRR 형식(리틀 엔디안 BGR). CSS는 #RRGGBB.
|
||||
|
||||
| HWP COLORREF | 분해 | CSS |
|
||||
|-------------|------|-----|
|
||||
| 0x00000000 | R=0, G=0, B=0 | `#000000` (검정) |
|
||||
| 0x00FF0000 | R=0, G=0, B=255 | `#0000ff` (파랑) |
|
||||
| 0x0000FF00 | R=0, G=255, B=0 | `#00ff00` (초록) |
|
||||
| 0x000000FF | R=255, G=0, B=0 | `#ff0000` (빨강) |
|
||||
| 0x00FFFFFF | R=255, G=255, B=255 | `#ffffff` (흰색) |
|
||||
|
||||
```python
|
||||
def colorref_to_css(colorref):
|
||||
r = colorref & 0xFF
|
||||
g = (colorref >> 8) & 0xFF
|
||||
b = (colorref >> 16) & 0xFF
|
||||
return f'#{r:02x}{g:02x}{b:02x}'
|
||||
```
|
||||
|
||||
**HWPX XML에서의 색상**: `#RRGGBB` 형식으로 직접 기록됨 (변환 불필요).
|
||||
|
||||
---
|
||||
|
||||
## 2. 테두리/배경 (BorderFill)
|
||||
|
||||
> HWP: `HWPTAG_BORDER_FILL` (DocInfo 레코드)
|
||||
> HWPX: `<hh:borderFill>` (header.xml 내)
|
||||
> 용도: 표 셀, 문단, 쪽 테두리/배경에 공통 적용
|
||||
|
||||
### 2.1 테두리선 종류
|
||||
|
||||
| HWP 값 | 이름 | HWPX type 속성 | CSS border-style |
|
||||
|--------|------|---------------|-----------------|
|
||||
| 0 | 실선 | `SOLID` | `solid` |
|
||||
| 1 | 긴 점선 | `DASH` | `dashed` |
|
||||
| 2 | 점선 | `DOT` | `dotted` |
|
||||
| 3 | -.-.-. | `DASH_DOT` | `dashed` (근사) |
|
||||
| 4 | -..-.. | `DASH_DOT_DOT` | `dashed` (근사) |
|
||||
| 5 | 긴 Dash | `LONG_DASH` | `dashed` |
|
||||
| 6 | 큰 동그라미 | `CIRCLE` | `dotted` (근사) |
|
||||
| 7 | 2중선 | `DOUBLE` | `double` |
|
||||
| 8 | 가는선+굵은선 | `THIN_THICK` | `double` (근사) |
|
||||
| 9 | 굵은선+가는선 | `THICK_THIN` | `double` (근사) |
|
||||
| 10 | 가는+굵은+가는 | `THIN_THICK_THIN` | `double` (근사) |
|
||||
| 11 | 물결 | `WAVE` | `solid` (근사) |
|
||||
| 12 | 물결 2중선 | `DOUBLE_WAVE` | `double` (근사) |
|
||||
| 13 | 두꺼운 3D | `THICK_3D` | `ridge` |
|
||||
| 14 | 두꺼운 3D(역) | `THICK_3D_REV` | `groove` |
|
||||
| 15 | 3D 단선 | `3D` | `outset` |
|
||||
| 16 | 3D 단선(역) | `3D_REV` | `inset` |
|
||||
| — | 없음 | `NONE` | `none` |
|
||||
|
||||
### 2.2 테두리선 굵기
|
||||
|
||||
| HWP 값 | 실제 굵기 | HWPX width 속성 | CSS border-width |
|
||||
|--------|----------|----------------|-----------------|
|
||||
| 0 | 0.1 mm | `0.1mm` | `0.1mm` ≈ `0.4px` |
|
||||
| 1 | 0.12 mm | `0.12mm` | `0.12mm` ≈ `0.5px` |
|
||||
| 2 | 0.15 mm | `0.15mm` | `0.15mm` ≈ `0.6px` |
|
||||
| 3 | 0.2 mm | `0.2mm` | `0.2mm` ≈ `0.8px` |
|
||||
| 4 | 0.25 mm | `0.25mm` | `0.25mm` ≈ `1px` |
|
||||
| 5 | 0.3 mm | `0.3mm` | `0.3mm` ≈ `1.1px` |
|
||||
| 6 | 0.4 mm | `0.4mm` | `0.4mm` ≈ `1.5px` |
|
||||
| 7 | 0.5 mm | `0.5mm` | `0.5mm` ≈ `1.9px` |
|
||||
| 8 | 0.6 mm | `0.6mm` | `0.6mm` ≈ `2.3px` |
|
||||
| 9 | 0.7 mm | `0.7mm` | `0.7mm` ≈ `2.6px` |
|
||||
| 10 | 1.0 mm | `1.0mm` | `1mm` ≈ `3.8px` |
|
||||
| 11 | 1.5 mm | `1.5mm` | `1.5mm` ≈ `5.7px` |
|
||||
| 12 | 2.0 mm | `2.0mm` | `2mm` ≈ `7.6px` |
|
||||
| 13 | 3.0 mm | `3.0mm` | `3mm` ≈ `11.3px` |
|
||||
| 14 | 4.0 mm | `4.0mm` | `4mm` ≈ `15.1px` |
|
||||
| 15 | 5.0 mm | `5.0mm` | `5mm` ≈ `18.9px` |
|
||||
|
||||
```python
|
||||
BORDER_WIDTH_MAP = {
|
||||
0: 0.1, 1: 0.12, 2: 0.15, 3: 0.2, 4: 0.25, 5: 0.3,
|
||||
6: 0.4, 7: 0.5, 8: 0.6, 9: 0.7, 10: 1.0, 11: 1.5,
|
||||
12: 2.0, 13: 3.0, 14: 4.0, 15: 5.0
|
||||
}
|
||||
def border_width_to_css(hwp_val):
|
||||
mm = BORDER_WIDTH_MAP.get(hwp_val, 0.12)
|
||||
return f'{mm}mm' # 또는 mm * 3.7795px
|
||||
```
|
||||
|
||||
### 2.3 테두리 4방향 순서
|
||||
|
||||
| HWP 배열 인덱스 | HWPX 속성 | CSS 대응 |
|
||||
|:---:|:---:|:---:|
|
||||
| [0] | `<left>` / `<hh:left>` | `border-left` |
|
||||
| [1] | `<right>` / `<hh:right>` | `border-right` |
|
||||
| [2] | `<top>` / `<hh:top>` | `border-top` |
|
||||
| [3] | `<bottom>` / `<hh:bottom>` | `border-bottom` |
|
||||
|
||||
### 2.4 채우기 (Fill) 정보
|
||||
|
||||
| 채우기 종류 (type 비트) | HWPX 엘리먼트 | CSS 대응 |
|
||||
|:---:|:---:|:---:|
|
||||
| 0x00 — 없음 | (없음) | `background: none` |
|
||||
| 0x01 — 단색 | `<hh:windowBrush>` 또는 `<hh:colorFill>` | `background-color: #...` |
|
||||
| 0x02 — 이미지 | `<hh:imgBrush>` | `background-image: url(...)` |
|
||||
| 0x04 — 그러데이션 | `<hh:gradation>` | `background: linear-gradient(...)` |
|
||||
|
||||
**단색 채우기 구조** (가장 빈번):
|
||||
```xml
|
||||
<!-- HWPX header.xml -->
|
||||
<hh:borderFill id="4">
|
||||
<hh:slash .../>
|
||||
<hh:backSlash .../>
|
||||
<hh:left type="SOLID" width="0.12mm" color="#000000"/>
|
||||
<hh:right type="SOLID" width="0.12mm" color="#000000"/>
|
||||
<hh:top type="SOLID" width="0.12mm" color="#000000"/>
|
||||
<hh:bottom type="SOLID" width="0.12mm" color="#000000"/>
|
||||
<hh:diagonal .../>
|
||||
<hh:fillBrush>
|
||||
<hh:windowBrush faceColor="#E8F5E9" hatchColor="none" .../>
|
||||
</hh:fillBrush>
|
||||
</hh:borderFill>
|
||||
```
|
||||
|
||||
```css
|
||||
/* CSS 대응 */
|
||||
.cell-bf4 {
|
||||
border-left: 0.12mm solid #000000;
|
||||
border-right: 0.12mm solid #000000;
|
||||
border-top: 0.12mm solid #000000;
|
||||
border-bottom: 0.12mm solid #000000;
|
||||
background-color: #E8F5E9;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 HWPX borderFill → CSS 변환 함수 (의사 코드)
|
||||
|
||||
```python
|
||||
def borderfill_to_css(bf_element):
|
||||
"""HWPX <hh:borderFill> 엘리먼트 → CSS 딕셔너리"""
|
||||
css = {}
|
||||
|
||||
for side in ['left', 'right', 'top', 'bottom']:
|
||||
el = bf_element.find(f'hh:{side}')
|
||||
if el is None:
|
||||
css[f'border-{side}'] = 'none'
|
||||
continue
|
||||
|
||||
btype = el.get('type', 'NONE')
|
||||
width = el.get('width', '0.12mm')
|
||||
color = el.get('color', '#000000')
|
||||
|
||||
if btype == 'NONE':
|
||||
css[f'border-{side}'] = 'none'
|
||||
else:
|
||||
css_style = BORDER_TYPE_MAP.get(btype, 'solid')
|
||||
css[f'border-{side}'] = f'{width} {css_style} {color}'
|
||||
|
||||
# 배경
|
||||
fill = bf_element.find('.//hh:windowBrush')
|
||||
if fill is not None:
|
||||
face = fill.get('faceColor', 'none')
|
||||
if face and face != 'none':
|
||||
css['background-color'] = face
|
||||
|
||||
return css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 글꼴 (FaceName)
|
||||
|
||||
> HWP: `HWPTAG_FACE_NAME` (DocInfo)
|
||||
> HWPX: `<hh:fontface>` → `<hh:font>` (header.xml)
|
||||
> CSS: `font-family`
|
||||
|
||||
### 3.1 언어별 글꼴 시스템
|
||||
|
||||
HWP는 한글·영문·한자·일어·기타·기호·사용자 총 7개 언어 슬롯에 각각 다른 글꼴을 지정한다.
|
||||
|
||||
| 언어 인덱스 | HWPX lang 속성 | 주요 글꼴 예시 |
|
||||
|:---:|:---:|:---:|
|
||||
| 0 | `HANGUL` | 맑은 고딕, 나눔고딕 |
|
||||
| 1 | `LATIN` | Arial, Times New Roman |
|
||||
| 2 | `HANJA` | (한글 글꼴 공유) |
|
||||
| 3 | `JAPANESE` | MS Mincho |
|
||||
| 4 | `OTHER` | — |
|
||||
| 5 | `SYMBOL` | Symbol, Wingdings |
|
||||
| 6 | `USER` | — |
|
||||
|
||||
**CSS 매핑**: 일반적으로 한글(0)과 영문(1) 글꼴을 `font-family` 스택으로 결합.
|
||||
|
||||
```css
|
||||
/* HWPX: hangul="맑은 고딕" latin="Arial" */
|
||||
font-family: "맑은 고딕", Arial, sans-serif;
|
||||
```
|
||||
|
||||
### 3.2 글꼴 관련 HWPX 구조
|
||||
|
||||
```xml
|
||||
<!-- header.xml -->
|
||||
<hh:fontfaces>
|
||||
<hh:fontface lang="HANGUL">
|
||||
<hh:font face="맑은 고딕" type="TTF" id="0"/>
|
||||
</hh:fontface>
|
||||
<hh:fontface lang="LATIN">
|
||||
<hh:font face="Arial" type="TTF" id="0"/>
|
||||
</hh:fontface>
|
||||
...
|
||||
</hh:fontfaces>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 글자 모양 (CharShape)
|
||||
|
||||
> HWP: `HWPTAG_CHAR_SHAPE` (DocInfo, 72바이트)
|
||||
> HWPX: `<hh:charPr>` (header.xml charProperties 내)
|
||||
> CSS: font-*, color, text-decoration 등
|
||||
|
||||
### 4.1 주요 속성 매핑
|
||||
|
||||
| HWP 필드 | HWPX 속성 | CSS 속성 | 비고 |
|
||||
|----------|----------|---------|------|
|
||||
| 글꼴 ID [7] | `fontRef` | `font-family` | 언어별 참조 |
|
||||
| 장평 [7] | `ratio` | `font-stretch` | 50%~200% |
|
||||
| 자간 [7] | `spacing` | `letter-spacing` | -50%~50%, pt 변환 필요 |
|
||||
| 기준 크기 | `height` | `font-size` | 값/100 = pt |
|
||||
| 글자 색 | `color` 속성 | `color` | COLORREF → #RRGGBB |
|
||||
| 밑줄 색 | `underline color` | `text-decoration-color` | |
|
||||
| 진하게(bit 1) | `bold="true"` | `font-weight: bold` | |
|
||||
| 기울임(bit 0) | `italic="true"` | `font-style: italic` | |
|
||||
| 밑줄(bit 2-3) | `underline type` | `text-decoration: underline` | |
|
||||
| 취소선(bit 18-20) | `strikeout type` | `text-decoration: line-through` | |
|
||||
| 위첨자(bit 15) | `supscript` | `vertical-align: super; font-size: 70%` | |
|
||||
| 아래첨자(bit 16) | `subscript` | `vertical-align: sub; font-size: 70%` | |
|
||||
|
||||
### 4.2 HWPX charPr 구조 예시
|
||||
|
||||
```xml
|
||||
<hh:charPr id="1" height="1000" bold="false" italic="false"
|
||||
underline="NONE" strikeout="NONE" color="#000000">
|
||||
<hh:fontRef hangul="0" latin="0" hanja="0" japanese="0"
|
||||
other="0" symbol="0" user="0"/>
|
||||
<hh:ratio hangul="100" latin="100" .../>
|
||||
<hh:spacing hangul="0" latin="0" .../>
|
||||
<hh:relSz hangul="100" latin="100" .../>
|
||||
<hh:offset hangul="0" latin="0" .../>
|
||||
</hh:charPr>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 문단 모양 (ParaShape)
|
||||
|
||||
> HWP: `HWPTAG_PARA_SHAPE` (DocInfo, 54바이트)
|
||||
> HWPX: `<hh:paraPr>` (header.xml paraProperties 내)
|
||||
> CSS: text-align, margin, line-height, text-indent 등
|
||||
|
||||
### 5.1 정렬 방식
|
||||
|
||||
| HWP 값 (bit 2-4) | HWPX 속성값 | CSS text-align |
|
||||
|:---:|:---:|:---:|
|
||||
| 0 | `JUSTIFY` | `justify` |
|
||||
| 1 | `LEFT` | `left` |
|
||||
| 2 | `RIGHT` | `right` |
|
||||
| 3 | `CENTER` | `center` |
|
||||
| 4 | `DISTRIBUTE` | `justify` (근사) |
|
||||
| 5 | `DISTRIBUTE_SPACE` | `justify` (근사) |
|
||||
|
||||
### 5.2 줄 간격 종류
|
||||
|
||||
| HWP 값 | HWPX 속성값 | CSS line-height | 비고 |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| 0 | `PERCENT` | `160%` (예) | 글자 크기 기준 % |
|
||||
| 1 | `FIXED` | `24pt` (예) | 고정 pt |
|
||||
| 2 | `BETWEEN_LINES` | — | 여백만 지정 |
|
||||
| 3 | `AT_LEAST` | — | 최소값 |
|
||||
|
||||
### 5.3 주요 속성 매핑
|
||||
|
||||
| HWP 필드 | HWPX 속성 | CSS 속성 | 단위 |
|
||||
|----------|----------|---------|------|
|
||||
| 왼쪽 여백 | `margin left` | `margin-left` / `padding-left` | HWPUNIT → mm |
|
||||
| 오른쪽 여백 | `margin right` | `margin-right` / `padding-right` | HWPUNIT → mm |
|
||||
| 들여쓰기 | `indent` | `text-indent` | HWPUNIT → mm |
|
||||
| 문단 간격 위 | `spacing before` | `margin-top` | HWPUNIT → mm |
|
||||
| 문단 간격 아래 | `spacing after` | `margin-bottom` | HWPUNIT → mm |
|
||||
| 줄 간격 | `lineSpacing` | `line-height` | 종류에 따라 다름 |
|
||||
| BorderFill ID | `borderFillIDRef` | border + background | ID로 참조 |
|
||||
|
||||
### 5.4 HWPX paraPr 구조 예시
|
||||
|
||||
```xml
|
||||
<hh:paraPr id="0" align="JUSTIFY">
|
||||
<hh:margin left="0" right="0" indent="0"/>
|
||||
<hh:spacing before="0" after="0"
|
||||
lineSpacingType="PERCENT" lineSpacing="160"/>
|
||||
<hh:border borderFillIDRef="1"
|
||||
left="0" right="0" top="0" bottom="0"/>
|
||||
<hh:autoSpacing eAsianEng="false" eAsianNum="false"/>
|
||||
</hh:paraPr>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 표 (Table) 구조
|
||||
|
||||
> HWP: `HWPTAG_TABLE` (본문 레코드)
|
||||
> HWPX: `<hp:tbl>` (section*.xml 내)
|
||||
> HTML: `<table>`, `<tr>`, `<td>`/`<th>`
|
||||
|
||||
### 6.1 표 속성 매핑
|
||||
|
||||
| HWP 필드 | HWPX 속성 | HTML/CSS 대응 | 비고 |
|
||||
|----------|----------|-------------|------|
|
||||
| RowCount | `rowCnt` | (행 수) | |
|
||||
| nCols | `colCnt` | (열 수) | `<colgroup>` 참조 |
|
||||
| CellSpacing | `cellSpacing` | `border-spacing` | HWPUNIT16 |
|
||||
| 안쪽 여백 | `cellMargin` left/right/top/bottom | `padding` | |
|
||||
| BorderFill ID | `borderFillIDRef` | 표 전체 테두리 | |
|
||||
| 쪽나눔(bit 0-1) | `pageBreak` | `page-break-inside` | 0=avoid, 1=auto |
|
||||
| 제목줄 반복(bit 2) | `repeatHeader` | `<thead>` 출력 | |
|
||||
|
||||
### 6.2 열 너비
|
||||
|
||||
```xml
|
||||
<!-- HWPX -->
|
||||
<hp:tbl colCnt="3" rowCnt="5" ...>
|
||||
<hp:colSz>
|
||||
<hp:widthList>8504 8504 8504</hp:widthList> <!-- HWPUNIT -->
|
||||
</hp:colSz>
|
||||
...
|
||||
</hp:tbl>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- HTML 변환 -->
|
||||
<colgroup>
|
||||
<col style="width: 33.33%"> <!-- 8504 / 총합 * 100 -->
|
||||
<col style="width: 33.33%">
|
||||
<col style="width: 33.33%">
|
||||
</colgroup>
|
||||
```
|
||||
|
||||
### 6.3 셀 (Cell) 속성
|
||||
|
||||
| HWP 필드 | HWPX 속성 | HTML 속성 | 비고 |
|
||||
|----------|----------|----------|------|
|
||||
| Column 주소 | `colAddr` | — | 0부터 시작 |
|
||||
| Row 주소 | `rowAddr` | — | 0부터 시작 |
|
||||
| 열 병합 개수 | `colSpan` | `colspan` | 1 = 병합 없음 |
|
||||
| 행 병합 개수 | `rowSpan` | `rowspan` | 1 = 병합 없음 |
|
||||
| 셀 폭 | `width` | `width` | HWPUNIT |
|
||||
| 셀 높이 | `height` | `height` | HWPUNIT |
|
||||
| 셀 여백 [4] | `cellMargin` | `padding` | HWPUNIT16 → mm |
|
||||
| BorderFill ID | `borderFillIDRef` | `border` + `background` | 셀별 스타일 |
|
||||
|
||||
### 6.4 HWPX 셀 구조 예시
|
||||
|
||||
```xml
|
||||
<hp:tc colAddr="0" rowAddr="0" colSpan="2" rowSpan="1"
|
||||
width="17008" height="2400" borderFillIDRef="4">
|
||||
<hp:cellMargin left="510" right="510" top="142" bottom="142"/>
|
||||
<hp:cellAddr colAddr="0" rowAddr="0"/>
|
||||
<hp:subList ...>
|
||||
<hp:p ...>
|
||||
<!-- 셀 내용 -->
|
||||
</hp:p>
|
||||
</hp:subList>
|
||||
</hp:tc>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- HTML 변환 -->
|
||||
<td colspan="2" style="
|
||||
width: 60mm;
|
||||
height: 8.5mm;
|
||||
padding: 0.5mm 1.8mm;
|
||||
border: 0.12mm solid #000;
|
||||
background-color: #E8F5E9;
|
||||
">셀 내용</td>
|
||||
```
|
||||
|
||||
### 6.5 병합 셀 처리 규칙
|
||||
|
||||
HWP/HWPX에서 병합된 셀은 **왼쪽 위 셀만 존재**하고, 병합에 포함된 다른 셀은 아예 없다.
|
||||
HTML에서는 colspan/rowspan으로 표현하고, 병합된 위치의 `<td>`를 생략한다.
|
||||
|
||||
```
|
||||
HWPX: colSpan="2", rowSpan="3" at (col=0, row=0)
|
||||
→ 이 셀이 col 0~1, row 0~2를 차지
|
||||
→ col=1/row=0, col=0/row=1, col=1/row=1, col=0/row=2, col=1/row=2 셀은 없음
|
||||
|
||||
HTML: <td colspan="2" rowspan="3">...</td>
|
||||
→ 해당 행/열 위치에서 <td> 생략
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 용지 설정 (PageDef / SecPr)
|
||||
|
||||
> HWP: `HWPTAG_PAGE_DEF` (구역 정의 하위)
|
||||
> HWPX: `<hp:secPr>` → `<hp:pageDef>` (section*.xml 내)
|
||||
> CSS: `@page`, `@media print`
|
||||
|
||||
### 7.1 용지 크기 사전 정의
|
||||
|
||||
| 용지 이름 | 가로 (mm) | 세로 (mm) | HWPUNIT (가로×세로) |
|
||||
|----------|----------|----------|:---:|
|
||||
| A4 | 210 | 297 | 59528 × 84188 |
|
||||
| A3 | 297 | 420 | 84188 × 119055 |
|
||||
| B5 | 176 | 250 | 49896 × 70866 |
|
||||
| Letter | 215.9 | 279.4 | 61200 × 79200 |
|
||||
| Legal | 215.9 | 355.6 | 61200 × 100800 |
|
||||
|
||||
### 7.2 여백 매핑
|
||||
|
||||
```xml
|
||||
<!-- HWPX section0.xml -->
|
||||
<hp:secPr>
|
||||
<hp:pageDef width="59528" height="84188"
|
||||
landscape="NARROWLY"> <!-- 좁게 = 세로 -->
|
||||
<hp:margin left="8504" right="8504"
|
||||
top="5668" bottom="4252"
|
||||
header="4252" footer="4252"
|
||||
gutter="0"/>
|
||||
</hp:pageDef>
|
||||
</hp:secPr>
|
||||
```
|
||||
|
||||
```css
|
||||
/* CSS 변환 */
|
||||
@page {
|
||||
size: A4 portrait; /* 210mm × 297mm */
|
||||
margin-top: 20mm; /* 5668 / 7200 * 25.4 ≈ 20mm */
|
||||
margin-bottom: 15mm; /* 4252 → 15mm */
|
||||
margin-left: 30mm; /* 8504 → 30mm */
|
||||
margin-right: 30mm; /* 8504 → 30mm */
|
||||
}
|
||||
/* 머리말/꼬리말 여백은 CSS에서 body padding으로 근사 */
|
||||
```
|
||||
|
||||
### 7.3 용지 방향
|
||||
|
||||
| HWP 값 (bit 0) | HWPX 속성값 | CSS |
|
||||
|:---:|:---:|:---:|
|
||||
| 0 | `NARROWLY` (좁게) | `portrait` |
|
||||
| 1 | `WIDELY` (넓게) | `landscape` |
|
||||
|
||||
---
|
||||
|
||||
## 8. 머리말/꼬리말 (Header/Footer)
|
||||
|
||||
> HWP: `HWPTAG_CTRL_HEADER` → 컨트롤 ID `head` / `foot`
|
||||
> HWPX: `<hp:headerFooter>` (section*.xml 내, 또는 별도 header/footer 영역)
|
||||
> HTML: 페이지 상단/하단 고정 영역
|
||||
|
||||
### 8.1 머리말/꼬리말 적용 범위
|
||||
|
||||
| HWP/HWPX 설정 | 의미 |
|
||||
|:---:|:---:|
|
||||
| 양쪽 | 모든 쪽에 동일 |
|
||||
| 짝수쪽만 | 짝수 페이지 |
|
||||
| 홀수쪽만 | 홀수 페이지 |
|
||||
|
||||
### 8.2 HTML 근사 표현
|
||||
|
||||
```html
|
||||
<!-- 머리말 -->
|
||||
<div class="page-header" style="
|
||||
position: absolute; top: 0; left: 0; right: 0;
|
||||
height: 15mm; /* header margin 값 */
|
||||
padding: 0 30mm; /* 좌우 본문 여백 */
|
||||
">
|
||||
<table class="header-table">...</table>
|
||||
</div>
|
||||
|
||||
<!-- 꼬리말 -->
|
||||
<div class="page-footer" style="
|
||||
position: absolute; bottom: 0; left: 0; right: 0;
|
||||
height: 15mm; /* footer margin 값 */
|
||||
padding: 0 30mm;
|
||||
">
|
||||
<span class="footer-text">페이지 번호</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 구역 정의 (Section)
|
||||
|
||||
> HWP: 구역 정의 컨트롤 (`secd`)
|
||||
> HWPX: `<hp:secPr>` (section*.xml 최상위)
|
||||
|
||||
### 9.1 구역 속성
|
||||
|
||||
| 속성 | HWPX | CSS/HTML 대응 | 비고 |
|
||||
|------|------|-------------|------|
|
||||
| 머리말 감춤 | `hideHeader` | header 영역 display:none | |
|
||||
| 꼬리말 감춤 | `hideFooter` | footer 영역 display:none | |
|
||||
| 텍스트 방향 | `textDirection` | `writing-mode` | 0=가로, 1=세로 |
|
||||
| 단 정의 | `<hp:colDef>` | CSS `columns` / `column-count` | |
|
||||
| 쪽 번호 | `pageStartNo` | 쪽 번호 출력 값 | 0=이어서 |
|
||||
|
||||
---
|
||||
|
||||
## 10. HTML → HWPX → HWP 변환 파이프라인
|
||||
|
||||
### 10.1 전체 흐름
|
||||
|
||||
```
|
||||
[HTML (우리 출력)]
|
||||
↓ (1) HTML 파싱 → CSS 속성 추출
|
||||
[중간 표현 (JSON)]
|
||||
↓ (2) 이 가이드의 역방향 매핑
|
||||
[HWPX (XML ZIP)]
|
||||
↓ (3) 한컴오피스 변환 도구
|
||||
[HWP (바이너리)]
|
||||
```
|
||||
|
||||
### 10.2 단계별 매핑 방향
|
||||
|
||||
| 단계 | 입력 | 출력 | 참조할 가이드 섹션 |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| HTML → HWPX | CSS border | `<hh:borderFill>` 생성 | §2 역방향 |
|
||||
| HTML → HWPX | CSS font | `<hh:charPr>` + `<hh:fontface>` | §3, §4 역방향 |
|
||||
| HTML → HWPX | CSS text-align 등 | `<hh:paraPr>` | §5 역방향 |
|
||||
| HTML → HWPX | `<table>` | `<hp:tbl>` + `<hp:tc>` | §6 역방향 |
|
||||
| HTML → HWPX | @page CSS | `<hp:pageDef>` | §7 역방향 |
|
||||
| HTML → HWPX | header/footer div | `<hp:headerFooter>` | §8 역방향 |
|
||||
|
||||
### 10.3 CSS → HWPX 역변환 예시
|
||||
|
||||
```python
|
||||
def css_border_to_hwpx(css_border):
|
||||
"""'0.12mm solid #000000' → HWPX 속성"""
|
||||
parts = css_border.split()
|
||||
width = parts[0] # '0.12mm'
|
||||
style = parts[1] # 'solid'
|
||||
color = parts[2] # '#000000'
|
||||
|
||||
hwpx_type = CSS_TO_BORDER_TYPE.get(style, 'SOLID')
|
||||
return {
|
||||
'type': hwpx_type,
|
||||
'width': width,
|
||||
'color': color
|
||||
}
|
||||
|
||||
CSS_TO_BORDER_TYPE = {
|
||||
'solid': 'SOLID', 'dashed': 'DASH', 'dotted': 'DOT',
|
||||
'double': 'DOUBLE', 'ridge': 'THICK_3D', 'groove': 'THICK_3D_REV',
|
||||
'outset': '3D', 'inset': '3D_REV', 'none': 'NONE'
|
||||
}
|
||||
```
|
||||
|
||||
### 10.4 HWPX ZIP 구조 생성
|
||||
|
||||
```
|
||||
output.hwpx (ZIP)
|
||||
├── META-INF/
|
||||
│ └── manifest.xml ← 파일 목록
|
||||
├── Contents/
|
||||
│ ├── header.xml ← DocInfo (글꼴, 스타일, borderFill)
|
||||
│ ├── section0.xml ← 본문 (문단, 표, 머리말/꼬리말)
|
||||
│ └── content.hpf ← 콘텐츠 메타
|
||||
├── BinData/ ← 이미지 등
|
||||
├── Preview/
|
||||
│ └── PrvImage.png ← 미리보기
|
||||
└── version.xml ← 버전 정보
|
||||
```
|
||||
|
||||
**header.xml 필수 구조**:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hh:head xmlns:hh="...">
|
||||
<hh:beginNum .../>
|
||||
<hh:refList>
|
||||
<hh:fontfaces>...</hh:fontfaces> <!-- §3 -->
|
||||
<hh:borderFills>...</hh:borderFills> <!-- §2 -->
|
||||
<hh:charProperties>...</hh:charProperties> <!-- §4 -->
|
||||
<hh:tabProperties>...</hh:tabProperties>
|
||||
<hh:numberingProperties>...</hh:numberingProperties>
|
||||
<hh:bulletProperties>...</hh:bulletProperties>
|
||||
<hh:paraProperties>...</hh:paraProperties> <!-- §5 -->
|
||||
<hh:styles>...</hh:styles>
|
||||
</hh:refList>
|
||||
</hh:head>
|
||||
```
|
||||
|
||||
**section0.xml 필수 구조**:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<hp:sec xmlns:hp="...">
|
||||
<hp:secPr>
|
||||
<hp:pageDef .../> <!-- §7 -->
|
||||
<hp:headerFooter .../> <!-- §8 -->
|
||||
</hp:secPr>
|
||||
<hp:p paraPrIDRef="0" styleIDRef="0"> <!-- 문단 -->
|
||||
<hp:run charPrIDRef="0">
|
||||
<hp:t>텍스트</hp:t>
|
||||
</hp:run>
|
||||
</hp:p>
|
||||
<hp:p ...>
|
||||
<hp:ctrl>
|
||||
<hp:tbl ...>...</hp:tbl> <!-- §6 -->
|
||||
</hp:ctrl>
|
||||
</hp:p>
|
||||
</hp:sec>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 시스템 적용 가이드
|
||||
|
||||
### 11.1 적용 대상 모듈
|
||||
|
||||
| 모듈 | 파일 | 이 가이드 활용 방식 |
|
||||
|------|------|:---:|
|
||||
| **doc_template_analyzer.py** | HWPX → HTML 템플릿 추출 | §2,6,7,8 정방향 (HWPX→CSS) |
|
||||
| **template_manager.py** | 추출된 스타일 저장/로드 | §2 borderFill ID 매핑 |
|
||||
| **custom_doc_type.py** | HTML 문서 생성 | §2,4,5 CSS 값 참조 |
|
||||
| **hwpx_converter.py** (예정) | HTML → HWPX 변환 | §2~8 역방향 (CSS→HWPX) |
|
||||
| **hwp_converter.py** (예정) | HWPX → HWP 변환 | §1 단위 변환 |
|
||||
|
||||
### 11.2 하드코딩 제거 전략
|
||||
|
||||
**현재 문제 (AS-IS)**:
|
||||
```python
|
||||
# doc_template_analyzer.py에 하드코딩됨
|
||||
border_css = "2px solid var(--primary)"
|
||||
header_bg = "#E8F5E9"
|
||||
```
|
||||
|
||||
**해결 방향 (TO-BE)**:
|
||||
```python
|
||||
# style.json에서 추출된 borderFill 참조
|
||||
bf = style['border_fills']['3'] # id=3
|
||||
border_css = f"{bf['top']['width']} {bf['top']['css_style']} {bf['top']['color']}"
|
||||
# → "0.12mm solid #000000"
|
||||
|
||||
header_bf = style['border_fills']['4'] # id=4 (헤더 배경 포함)
|
||||
header_bg = header_bf.get('background', 'none')
|
||||
# → "#E8F5E9"
|
||||
```
|
||||
|
||||
### 11.3 이 가이드를 코드에서 참조하는 방식
|
||||
|
||||
이 문서(`hwpx_domain_guide.md`)는 다음과 같이 활용한다:
|
||||
|
||||
1. **변환 테이블을 JSON으로 추출** → `hwpx_mappings.json`
|
||||
- 테두리선 종류, 굵기, 색상 변환 등의 룩업 테이블
|
||||
|
||||
2. **변환 함수 라이브러리** → `hwpx_utils.py`
|
||||
- `hwpunit_to_mm()`, `borderfill_to_css()`, `css_border_to_hwpx()` 등
|
||||
|
||||
3. **AI 프롬프트 컨텍스트** → 문서 유형/구조 분석 시 참조
|
||||
- "이 HWPX의 borderFill id=3은 실선 0.12mm 검정이므로 표 일반 셀에 해당"
|
||||
|
||||
4. **검증 기준** → 변환 결과물 검증 시 정확성 확인
|
||||
- 추출된 CSS가 원본 HWPX의 스펙과 일치하는지
|
||||
|
||||
---
|
||||
|
||||
## 부록 A. 빠른 참조 — HWPX XML 태그 ↔ HWP 레코드 대응
|
||||
|
||||
| HWP 레코드 (Tag ID) | HWPX XML 엘리먼트 | 위치 |
|
||||
|---------------------|------------------|------|
|
||||
| HWPTAG_DOCUMENT_PROPERTIES | `<hh:beginNum>` 등 | header.xml |
|
||||
| HWPTAG_ID_MAPPINGS | (암묵적) | header.xml |
|
||||
| HWPTAG_FACE_NAME | `<hh:font>` | header.xml > fontfaces |
|
||||
| HWPTAG_BORDER_FILL | `<hh:borderFill>` | header.xml > borderFills |
|
||||
| HWPTAG_CHAR_SHAPE | `<hh:charPr>` | header.xml > charProperties |
|
||||
| HWPTAG_TAB_DEF | `<hh:tabPr>` | header.xml > tabProperties |
|
||||
| HWPTAG_NUMBERING | `<hh:numbering>` | header.xml > numberingProperties |
|
||||
| HWPTAG_BULLET | `<hh:bullet>` | header.xml > bulletProperties |
|
||||
| HWPTAG_PARA_SHAPE | `<hh:paraPr>` | header.xml > paraProperties |
|
||||
| HWPTAG_STYLE | `<hh:style>` | header.xml > styles |
|
||||
| HWPTAG_PARA_HEADER | `<hp:p>` | section*.xml |
|
||||
| HWPTAG_TABLE | `<hp:tbl>` | section*.xml > p > ctrl |
|
||||
| (셀 속성) | `<hp:tc>` | section*.xml > tbl > tr > tc |
|
||||
| HWPTAG_PAGE_DEF | `<hp:pageDef>` | section*.xml > secPr |
|
||||
| (머리말/꼬리말) | `<hp:headerFooter>` | section*.xml > secPr |
|
||||
|
||||
## 부록 B. 빠른 참조 — CSS → HWPX 역변환
|
||||
|
||||
| CSS 속성 | HWPX 대응 | 변환 공식 |
|
||||
|----------|----------|----------|
|
||||
| `font-family` | `<hh:font face="...">` | 첫 번째 값 → hangul, 두 번째 → latin |
|
||||
| `font-size: 10pt` | `<hh:charPr height="1000">` | pt × 100 |
|
||||
| `font-weight: bold` | `bold="true"` | |
|
||||
| `font-style: italic` | `italic="true"` | |
|
||||
| `color: #1a365d` | `color="#1a365d"` | 동일 |
|
||||
| `text-align: center` | `align="CENTER"` | 대문자 |
|
||||
| `margin-left: 30mm` | `left="8504"` | mm → HWPUNIT |
|
||||
| `line-height: 160%` | `lineSpacing="160"` + `type="PERCENT"` | |
|
||||
| `border: 1px solid #000` | `<hh:borderFill>` 내 각 방향 | §2 참조 |
|
||||
| `background-color: #E8F5E9` | `<hh:windowBrush faceColor="...">` | |
|
||||
| `padding: 2mm 5mm` | `<hp:cellMargin top="567" left="1417">` | mm → HWPUNIT |
|
||||
| `width: 210mm` | `width="59528"` | mm → HWPUNIT |
|
||||
| `@page { size: A4 }` | `<hp:pageDef width="59528" height="84188">` | |
|
||||
|
||||
---
|
||||
|
||||
*이 가이드는 한글과컴퓨터의 "글 문서 파일 구조 5.0 (revision 1.3)"을 참고하여 작성되었습니다.*
|
||||
323
03. Code/geulbeot_10th/domain/hwpx/hwpx_utils.py
Normal file
323
03. Code/geulbeot_10th/domain/hwpx/hwpx_utils.py
Normal file
@@ -0,0 +1,323 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
HWP/HWPX ↔ HTML/CSS 변환 유틸리티
|
||||
|
||||
hwpx_domain_guide.md의 매핑 테이블을 코드화.
|
||||
하드코딩 없이 이 모듈의 함수/상수를 참조하여 정확한 변환을 수행한다.
|
||||
|
||||
참조: 한글과컴퓨터 "글 문서 파일 구조 5.0" (revision 1.3, 2018-11-08)
|
||||
"""
|
||||
|
||||
|
||||
# ================================================================
|
||||
# §1. 단위 변환
|
||||
# ================================================================
|
||||
|
||||
def hwpunit_to_mm(hwpunit):
|
||||
"""HWPUNIT → mm (1 HWPUNIT = 1/7200 inch)"""
|
||||
return hwpunit / 7200 * 25.4
|
||||
|
||||
def hwpunit_to_pt(hwpunit):
|
||||
"""HWPUNIT → pt"""
|
||||
return hwpunit / 7200 * 72
|
||||
|
||||
def hwpunit_to_px(hwpunit, dpi=96):
|
||||
"""HWPUNIT → px (기본 96dpi)"""
|
||||
return hwpunit / 7200 * dpi
|
||||
|
||||
def mm_to_hwpunit(mm):
|
||||
"""mm → HWPUNIT"""
|
||||
return mm / 25.4 * 7200
|
||||
|
||||
def pt_to_hwpunit(pt):
|
||||
"""pt → HWPUNIT"""
|
||||
return pt / 72 * 7200
|
||||
|
||||
def px_to_hwpunit(px, dpi=96):
|
||||
"""px → HWPUNIT"""
|
||||
return px / dpi * 7200
|
||||
|
||||
def charsize_to_pt(hwp_size):
|
||||
"""HWP 글자 크기 → pt (100 스케일 제거)
|
||||
예: 1000 → 10pt, 2400 → 24pt
|
||||
"""
|
||||
return hwp_size / 100
|
||||
|
||||
def pt_to_charsize(pt):
|
||||
"""pt → HWP 글자 크기
|
||||
예: 10pt → 1000, 24pt → 2400
|
||||
"""
|
||||
return int(pt * 100)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# §1.3 색상 변환
|
||||
# ================================================================
|
||||
|
||||
def colorref_to_css(colorref):
|
||||
"""HWP COLORREF (0x00BBGGRR) → CSS #RRGGBB
|
||||
|
||||
HWP는 리틀 엔디안 BGR 순서:
|
||||
- 0x00FF0000 → B=255,G=0,R=0 → #0000ff (파랑)
|
||||
- 0x000000FF → B=0,G=0,R=255 → #ff0000 (빨강)
|
||||
"""
|
||||
r = colorref & 0xFF
|
||||
g = (colorref >> 8) & 0xFF
|
||||
b = (colorref >> 16) & 0xFF
|
||||
return f'#{r:02x}{g:02x}{b:02x}'
|
||||
|
||||
def css_to_colorref(css_color):
|
||||
"""CSS #RRGGBB → HWP COLORREF (0x00BBGGRR)"""
|
||||
css_color = css_color.lstrip('#')
|
||||
if len(css_color) == 3: # 단축형 #rgb → #rrggbb
|
||||
css_color = ''.join(c * 2 for c in css_color)
|
||||
r = int(css_color[0:2], 16)
|
||||
g = int(css_color[2:4], 16)
|
||||
b = int(css_color[4:6], 16)
|
||||
return (b << 16) | (g << 8) | r
|
||||
|
||||
|
||||
# ================================================================
|
||||
# §2. 테두리/배경 (BorderFill) 매핑
|
||||
# ================================================================
|
||||
|
||||
# §2.1 테두리선 종류: HWPX type → CSS border-style
|
||||
BORDER_TYPE_TO_CSS = {
|
||||
'NONE': 'none',
|
||||
'SOLID': 'solid',
|
||||
'DASH': 'dashed',
|
||||
'DOT': 'dotted',
|
||||
'DASH_DOT': 'dashed', # CSS 근사
|
||||
'DASH_DOT_DOT': 'dashed', # CSS 근사
|
||||
'LONG_DASH': 'dashed',
|
||||
'CIRCLE': 'dotted', # CSS 근사 (큰 동그라미 → dot)
|
||||
'DOUBLE': 'double',
|
||||
'THIN_THICK': 'double', # CSS 근사
|
||||
'THICK_THIN': 'double', # CSS 근사
|
||||
'THIN_THICK_THIN':'double', # CSS 근사
|
||||
'WAVE': 'solid', # CSS 근사 (물결 → 실선)
|
||||
'DOUBLE_WAVE': 'double', # CSS 근사
|
||||
'THICK_3D': 'ridge',
|
||||
'THICK_3D_REV': 'groove',
|
||||
'3D': 'outset',
|
||||
'3D_REV': 'inset',
|
||||
}
|
||||
|
||||
# CSS border-style → HWPX type (역방향)
|
||||
CSS_TO_BORDER_TYPE = {
|
||||
'none': 'NONE',
|
||||
'solid': 'SOLID',
|
||||
'dashed': 'DASH',
|
||||
'dotted': 'DOT',
|
||||
'double': 'DOUBLE',
|
||||
'ridge': 'THICK_3D',
|
||||
'groove': 'THICK_3D_REV',
|
||||
'outset': '3D',
|
||||
'inset': '3D_REV',
|
||||
}
|
||||
|
||||
# §2.2 HWP 바이너리 테두리 굵기 값 → 실제 mm
|
||||
BORDER_WIDTH_HWP_TO_MM = {
|
||||
0: 0.1, 1: 0.12, 2: 0.15, 3: 0.2, 4: 0.25, 5: 0.3,
|
||||
6: 0.4, 7: 0.5, 8: 0.6, 9: 0.7, 10: 1.0, 11: 1.5,
|
||||
12: 2.0, 13: 3.0, 14: 4.0, 15: 5.0,
|
||||
}
|
||||
|
||||
|
||||
# ================================================================
|
||||
# §5. 정렬 매핑
|
||||
# ================================================================
|
||||
|
||||
# §5.1 HWPX align → CSS text-align
|
||||
ALIGN_TO_CSS = {
|
||||
'JUSTIFY': 'justify',
|
||||
'LEFT': 'left',
|
||||
'RIGHT': 'right',
|
||||
'CENTER': 'center',
|
||||
'DISTRIBUTE': 'justify', # CSS 근사
|
||||
'DISTRIBUTE_SPACE': 'justify', # CSS 근사
|
||||
}
|
||||
|
||||
# CSS text-align → HWPX align (역방향)
|
||||
CSS_TO_ALIGN = {
|
||||
'justify': 'JUSTIFY',
|
||||
'left': 'LEFT',
|
||||
'right': 'RIGHT',
|
||||
'center': 'CENTER',
|
||||
}
|
||||
|
||||
|
||||
# ================================================================
|
||||
# §5.2 줄 간격 매핑
|
||||
# ================================================================
|
||||
|
||||
LINE_SPACING_TYPE_TO_CSS = {
|
||||
'PERCENT': 'percent', # 글자에 따라 (%) → line-height: 160%
|
||||
'FIXED': 'fixed', # 고정값 → line-height: 24pt
|
||||
'BETWEEN_LINES': 'between', # 여백만 지정
|
||||
'AT_LEAST': 'at_least', # 최소
|
||||
}
|
||||
|
||||
|
||||
# ================================================================
|
||||
# 종합 변환 함수
|
||||
# ================================================================
|
||||
|
||||
def hwpx_border_to_css(border_attrs):
|
||||
"""HWPX 테두리 속성 dict → CSS border 문자열
|
||||
|
||||
Args:
|
||||
border_attrs: {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'}
|
||||
|
||||
Returns:
|
||||
'0.12mm solid #000000' 또는 'none'
|
||||
"""
|
||||
btype = border_attrs.get('type', 'NONE')
|
||||
if btype == 'NONE' or btype is None:
|
||||
return 'none'
|
||||
|
||||
width = border_attrs.get('width', '0.12mm')
|
||||
color = border_attrs.get('color', '#000000')
|
||||
css_style = BORDER_TYPE_TO_CSS.get(btype, 'solid')
|
||||
|
||||
return f'{width} {css_style} {color}'
|
||||
|
||||
|
||||
def css_border_to_hwpx(css_border):
|
||||
"""CSS border 문자열 → HWPX 속성 dict
|
||||
|
||||
Args:
|
||||
css_border: '0.12mm solid #000000' 또는 'none'
|
||||
|
||||
Returns:
|
||||
{'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'}
|
||||
"""
|
||||
if not css_border or css_border.strip() == 'none':
|
||||
return {'type': 'NONE', 'width': '0mm', 'color': '#000000'}
|
||||
|
||||
parts = css_border.strip().split()
|
||||
width = parts[0] if len(parts) > 0 else '0.12mm'
|
||||
style = parts[1] if len(parts) > 1 else 'solid'
|
||||
color = parts[2] if len(parts) > 2 else '#000000'
|
||||
|
||||
return {
|
||||
'type': CSS_TO_BORDER_TYPE.get(style, 'SOLID'),
|
||||
'width': width,
|
||||
'color': color,
|
||||
}
|
||||
|
||||
|
||||
def hwpx_borderfill_to_css(bf_element_attrs):
|
||||
"""HWPX <hh:borderFill> 전체 속성 → CSS dict
|
||||
|
||||
Args:
|
||||
bf_element_attrs: {
|
||||
'left': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'},
|
||||
'right': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'},
|
||||
'top': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'},
|
||||
'bottom': {'type': 'SOLID', 'width': '0.12mm', 'color': '#000000'},
|
||||
'background': '#E8F5E9' or None,
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
'border-left': '0.12mm solid #000000',
|
||||
'border-right': '0.12mm solid #000000',
|
||||
'border-top': '0.12mm solid #000000',
|
||||
'border-bottom': '0.12mm solid #000000',
|
||||
'background-color': '#E8F5E9',
|
||||
}
|
||||
"""
|
||||
css = {}
|
||||
|
||||
for side in ['left', 'right', 'top', 'bottom']:
|
||||
border = bf_element_attrs.get(side, {})
|
||||
css[f'border-{side}'] = hwpx_border_to_css(border)
|
||||
|
||||
bg = bf_element_attrs.get('background')
|
||||
if bg and bg != 'none':
|
||||
css['background-color'] = bg
|
||||
|
||||
return css
|
||||
|
||||
|
||||
def hwpx_align_to_css(hwpx_align):
|
||||
"""HWPX 정렬 값 → CSS text-align"""
|
||||
return ALIGN_TO_CSS.get(hwpx_align, 'left')
|
||||
|
||||
|
||||
def css_align_to_hwpx(css_align):
|
||||
"""CSS text-align → HWPX 정렬 값"""
|
||||
return CSS_TO_ALIGN.get(css_align, 'LEFT')
|
||||
|
||||
|
||||
def hwpx_line_spacing_to_css(spacing_type, spacing_value):
|
||||
"""HWPX 줄 간격 → CSS line-height
|
||||
|
||||
Args:
|
||||
spacing_type: 'PERCENT' | 'FIXED' | 'BETWEEN_LINES' | 'AT_LEAST'
|
||||
spacing_value: 숫자값
|
||||
|
||||
Returns:
|
||||
CSS line-height 문자열 (예: '160%', '24pt')
|
||||
"""
|
||||
if spacing_type == 'PERCENT':
|
||||
return f'{spacing_value}%'
|
||||
elif spacing_type == 'FIXED':
|
||||
pt = hwpunit_to_pt(spacing_value)
|
||||
return f'{pt:.1f}pt'
|
||||
else:
|
||||
return f'{spacing_value}%' # 기본 근사
|
||||
|
||||
|
||||
# ================================================================
|
||||
# 용지 크기 사전 정의 (§7.1)
|
||||
# ================================================================
|
||||
|
||||
PAPER_SIZES = {
|
||||
'A4': {'width_mm': 210, 'height_mm': 297, 'width_hu': 59528, 'height_hu': 84188},
|
||||
'A3': {'width_mm': 297, 'height_mm': 420, 'width_hu': 84188, 'height_hu': 119055},
|
||||
'B5': {'width_mm': 176, 'height_mm': 250, 'width_hu': 49896, 'height_hu': 70866},
|
||||
'Letter': {'width_mm': 215.9, 'height_mm': 279.4, 'width_hu': 61200, 'height_hu': 79200},
|
||||
'Legal': {'width_mm': 215.9, 'height_mm': 355.6, 'width_hu': 61200, 'height_hu': 100800},
|
||||
}
|
||||
|
||||
def detect_paper_size(width_hu, height_hu, tolerance=200):
|
||||
"""HWPUNIT 용지 크기 → 용지 이름 추정
|
||||
|
||||
Args:
|
||||
width_hu: 가로 크기 (HWPUNIT)
|
||||
height_hu: 세로 크기 (HWPUNIT)
|
||||
tolerance: 허용 오차 (HWPUNIT)
|
||||
|
||||
Returns:
|
||||
'A4', 'A3', 'Letter' 등 또는 'custom'
|
||||
"""
|
||||
for name, size in PAPER_SIZES.items():
|
||||
if (abs(width_hu - size['width_hu']) <= tolerance and
|
||||
abs(height_hu - size['height_hu']) <= tolerance):
|
||||
return name
|
||||
# landscape 체크
|
||||
if (abs(width_hu - size['height_hu']) <= tolerance and
|
||||
abs(height_hu - size['width_hu']) <= tolerance):
|
||||
return f'{name}_landscape'
|
||||
return 'custom'
|
||||
|
||||
|
||||
# ================================================================
|
||||
# 편의 함수
|
||||
# ================================================================
|
||||
|
||||
def css_style_string(css_dict):
|
||||
"""CSS dict → CSS style 문자열
|
||||
|
||||
예: {'border-left': '1px solid #000', 'padding': '5mm'}
|
||||
→ 'border-left: 1px solid #000; padding: 5mm;'
|
||||
"""
|
||||
return ' '.join(f'{k}: {v};' for k, v in css_dict.items() if v)
|
||||
|
||||
|
||||
def mm_format(hwpunit, decimal=1):
|
||||
"""HWPUNIT → 'Xmm' 포맷 문자열"""
|
||||
mm = hwpunit_to_mm(hwpunit)
|
||||
return f'{mm:.{decimal}f}mm'
|
||||
22
03. Code/geulbeot_10th/domain/report_guide/domain_prompt.txt
Normal file
22
03. Code/geulbeot_10th/domain/report_guide/domain_prompt.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
이 문서 묶음은 건설/토목 분야의 측량과 디지털 전환(DX)에 초점을 둔 자료로, 드론(UAV) 사진측량, GIS, 지형·지반(terrain/geotech) 정보 모델의 구축·활용을 다룬다.
|
||||
시공 단계별 측량 절차와 성과품, 기준점 체계, 드론 운용 학습자료, 내부 솔루션(GAIA, GIS Solutions, Terrain Information Model, Map v2.0, GSIM 등)과 워크플로가 포함된다.
|
||||
핵심 키워드: 측량, 드론/UAV, 사진측량, GIS, 디지털 트윈, 지형지반 모델, GAIA, GSIM, 수치지도 2.0, 배수·유역 분석, 성과품·절차.
|
||||
|
||||
너는 건설/토목 측량·드론·GIS·지형지반 모델링 분야의 기술 교육콘텐츠 기획자이자, 자사 솔루션 홍보를 위한 기술 전문가이다.
|
||||
|
||||
나는 이 문서 묶음을 근거로 측량에 대한 기본 개념부터 동향, 건설사업의 디지털 전환(DX)을 위한 측량 변화와 절차, 드론 운용·사진측량 워크플로, GIS/지형지반 모델의 구축·활용과 함께 사례를 정확히 이해하고 적용하고 싶다.
|
||||
우리는 이 자료를 기반으로 CEL(기술발신력 강화) 콘텐츠를 제작하고자 한다.
|
||||
|
||||
목표는 다음과 같다:
|
||||
|
||||
1) 분야에 대한 개념과 관련 기초 개념, 용어에 대한 정의를 명확하게 설명한다.
|
||||
2) 기존 방식에 대한 한계와 분야별 디지털 전환(DX)에 대한 변화, 요구사항 등에 대하여 명확히 설명한다.
|
||||
3) 기존 시장 솔루션들의 방향성과 함께 한계를 최대한 객관적으로 제시한다
|
||||
4) 이러한 문제를 해결하는 방향과 솔루션을 제시하고, 이를 만족하는 자사 솔루션(GAIA, GSIM, Map v2.0 등)의 기술적 강점과 차별점을 자연스럽게 부각한다
|
||||
5) 적용된 사례를 이미지와 함게 이해하기 쉽게 정리한다
|
||||
과업수행 절차와 성과품 기준, 데이터 품질·정합성, 시스템 구성(예: GAIA, GSIM, Map v2.0 등)과 관련된 문제를 분석하여 정리한다.
|
||||
너의 답변은 문서 기반 사실에 한정하고 추측을 금지하며, 가능한 경우 문서명/페이지/그림 참조 등 근거를 명시하라.
|
||||
자료의 공백이나 모호함이 있으면 필요한 가정을 분리해 표시하거나 추가 질문으로 명확히 하라; 외부 일반지식은 참고로만 제시하고 출처 구분을 유지하라.
|
||||
콘텐츠는 내부 직원 교육 및 외부 고객/파트너 대상 기술 세미나에 활용될 예정이므로, 전문성과 신뢰성을 유지하되 이해하기 쉬운 스토리 흐름으로 구성하라.
|
||||
|
||||
이후 청킹, 요약, 용어정의, RAG 검색·인용, 비교표 작성, 분석·보고서 작성, 체크리스트·절차서 도출 등 다양한 작업을 네가 주도적으로 구조화해 지원하라.
|
||||
@@ -0,0 +1,20 @@
|
||||
건설·토목 측량 DX 실무지침: 드론/UAV·GIS·지형/지반 모델 기반 전주기 표준과 품질관리
|
||||
|
||||
1. DX 개요와 기본 개념·기준점 체계
|
||||
1.1 측량 DX 프레임과 기초 용어
|
||||
1.1.1 측량 DX 발전 단계
|
||||
- Digitization→Digitalization→DX 정의·사례 | #DX진화 #정책기조 | [인사이트형] | 03 p.62–67 근거 문구 수집, 단계-산출물 매트릭스 표 작성
|
||||
- UAV/3D Mesh/DSM/LiDAR 전환 | #UAV #3D모델 | [기술형] | 03 p.62–68에서 제품유형·데이터모델 비교표와 예시 이미지
|
||||
1.1.2 핵심 용어·원리 정리
|
||||
- GNSS(RTK/VRS/Static)·TS·LiDAR | #측량센싱 | [기술형] | 03 p.64–65,68 용어정의·정확도·용도 표 구성
|
||||
- GSD/DSM/DEM/DTM/TIN·맵핑 vs 모델 | #데이터모델 | [비교형] | 03 p.68 정의/산출물/활용 비교표와 주석
|
||||
1.1.3 수치지형도 2.0 vs 정밀도로지도(HD Map)
|
||||
- 형식·정확도·객체 차이 | #수치지도2.0 #HDMap | [비교형] | 수치지도2.0(SHP 구성) vs HD Map(±0.25m) 비교표(파일·속성·정확도)
|
||||
- SOC 활용 한계·보완 | #활용성 #한계 | [인사이트형] | 정밀도로지도 외측 결손·역설계 필요 사례 정리(매뉴얼 2023.07)
|
||||
1.2 기준점 체계와 국가 수직망 정정
|
||||
1.2.1 기준점 현황·재구축 필요성
|
||||
- 설계기준점 상태 통계 | #기준점점검 | [인사이트형] | 1·2·4공구 정상/망실 수량표·지도 핀맵 작성
|
||||
- 수직망 정정(Z −39~−63mm) 영향 | #수직망정정 | [기술형] | 고시 2023-3084 변화량 표·적용 체크리스트(01/05/08 인용)
|
||||
1.2.2 행정·규정·품질 기준
|
||||
- 공공측량 준용규정·검사기준 | #준용규정 | [절차형] | 서산–명천 문서 내 준용규정 항목 추출, 준수 체크리스트 표
|
||||
- 성과품 품질·미수령 항목 | #품질관리 | [인사이트형] | 01/05/08 미수령 목록 대조표(원본 Pile·정사영상·망조정 등)
|
||||
848
03. Code/geulbeot_10th/domain/report_guide/report_guide.txt
Normal file
848
03. Code/geulbeot_10th/domain/report_guide/report_guide.txt
Normal file
@@ -0,0 +1,848 @@
|
||||
🏛️ A4 보고서 퍼블리싱 마스터 가이드 (v82.0 Intelligent Flow)
|
||||
당신은 **'지능형 퍼블리싱 아키텍트'**입니다. 원본의 **[스타일 독소]**를 제거하고, A4 규격에 맞춰 콘텐츠를 재조립하되, 단순 나열이 아닌 **[최적화된 배치]**를 수행하십시오.
|
||||
텍스트는 **[복사기]**처럼 있는 그대로 보존하고, 레이아웃은 **[강박증]** 수준으로 맞추십시오.
|
||||
|
||||
🚨 0. 최우선 절대 원칙 (Data Integrity)
|
||||
복사기 모드: 원본 텍스트를 절대 요약, 생략(...), 수정하지 마십시오. 무조건 전부 출력하십시오.
|
||||
데이터 무결성: 표의 수치, 본문의 문장은 토씨 하나 바꾸지 않고 보존합니다.
|
||||
|
||||
🚨 1. 핵심 렌더링 원칙 (The 6 Commandments)
|
||||
Deep Sanitization (심층 세탁): 모든 class, style을 삭제하되, 차트/그림 내부의 제목 텍스트는 캡션과 중복되므로 제거하십시오.
|
||||
H1 Only Break: 오직 대목차(H1) 태그에서만 무조건 페이지를 나눕니다.
|
||||
Orphan Control (고아 방지): 중목차(H2), 소목차(H3)가 페이지 하단에 홀로 남을 경우, 통째로 다음 페이지로 넘기십시오.
|
||||
Smart Fit (지능형 맞춤): 표나 그림이 페이지를 넘어가는데 그 양이 적다면(15% 이내), 최대 85%까지 축소하여 현재 페이지에 넣으십시오.
|
||||
Gap Filling (공백 채우기): 그림이 다음 장으로 넘어가 현재 페이지 하단에 큰 공백이 생긴다면, 뒤에 있는 텍스트 문단을 당겨와 그 빈공간을 채우십시오.
|
||||
Visual Standard:
|
||||
여백: 상하좌우 20mm를 시각적으로 고정하십시오.
|
||||
캡션: 모든 그림/표의 제목은 하단 중앙 정렬하십시오.
|
||||
|
||||
|
||||
🛠️ 제작 가이드 (Technical Specs)
|
||||
아래 코드는 렌더링 엔진입니다. 이 구조를 기반으로 사용자 데이터를 raw-container에 주입하여 출력하십시오.
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>A4 Report v83.0 Template</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap');
|
||||
|
||||
:root {
|
||||
--primary: #006400;
|
||||
--accent: #228B22;
|
||||
--light-green: #E8F5E9;
|
||||
--bg: #525659;
|
||||
}
|
||||
body { margin: 0; background: var(--bg); font-family: 'Noto Sans KR', sans-serif; }
|
||||
|
||||
/* [A4 용지 규격] */
|
||||
.sheet {
|
||||
width: 210mm; height: 297mm;
|
||||
background: white; margin: 20px auto;
|
||||
position: relative; overflow: hidden; box-sizing: border-box;
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
@media print {
|
||||
.sheet { margin: 0; break-after: page; box-shadow: none; }
|
||||
body { background: white; }
|
||||
}
|
||||
|
||||
/* [헤더/푸터: 여백 20mm 영역 내 배치] */
|
||||
.page-header {
|
||||
position: absolute; top: 10mm; left: 20mm; right: 20mm;
|
||||
font-size: 9pt; color: #000000; font-weight: bold;
|
||||
text-align: right; border-bottom: none !important; padding-bottom: 5px;
|
||||
}
|
||||
.page-footer {
|
||||
position: absolute; bottom: 10mm; left: 20mm; right: 20mm;
|
||||
display: flex; justify-content: space-between; align-items: flex-end;
|
||||
font-size: 9pt; color: #555; border-top: 1px solid #eee; padding-top: 5px;
|
||||
}
|
||||
|
||||
/* [본문 영역: 상하좌우 20mm 고정] */
|
||||
.body-content {
|
||||
position: absolute;
|
||||
top: 20mm; left: 20mm; right: 20mm;
|
||||
bottom: auto; /* 높이는 JS가 제어 */
|
||||
}
|
||||
|
||||
/* [타이포그래피] */
|
||||
h1, h2, h3 {
|
||||
white-space: nowrap; overflow: hidden; word-break: keep-all; color: var(--primary);
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20pt; /* H2와 동일하게 변경 (기존 24pt -> 18pt) */
|
||||
font-weight: 900;
|
||||
color: var(--primary);
|
||||
border-bottom: 2px solid var(--primary);
|
||||
margin-bottom: 20px;
|
||||
margin-top: 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18pt;
|
||||
border-left: 5px solid var(--accent);
|
||||
padding-left: 10px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
color: #03581dff;
|
||||
}
|
||||
h3 { font-size: 14pt; margin-top: 20px; margin-bottom: 5px; color: var(--accent); font-weight: 700; }
|
||||
p, li { font-size: 12pt !important; line-height: 1.6 !important; text-align: justify; word-break: keep-all; margin-bottom: 5px; }
|
||||
|
||||
/* [목차 스타일 수정: lvl-1 강조 및 간격 추가] */
|
||||
.toc-item { line-height: 1.8; list-style: none; border-bottom: 1px dotted #eee; }
|
||||
|
||||
.toc-lvl-1 {
|
||||
color: #006400; /* 녹색 */
|
||||
font-weight: 900; /* 볼드 */
|
||||
font-size: 13.5pt; /* lvl-2(10.5pt)보다 3pt 크게 */
|
||||
margin-top: 15px; /* 위쪽 간격 */
|
||||
margin-bottom: 5px; /* 아래쪽 3pt 정도의 간격 */
|
||||
border-bottom: 2px solid #ccc;
|
||||
}
|
||||
.toc-lvl-2 { font-size: 10.5pt; color: #333; margin-left: 20px; font-weight: normal; }
|
||||
.toc-lvl-3 { font-size: 10.5pt; color: #666; margin-left: 40px; }
|
||||
|
||||
|
||||
/* [표/이미지 스타일] */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15px 0;
|
||||
font-size: 9.5pt;
|
||||
table-layout: auto;
|
||||
border-top: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 6px 5px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
/* ▼▼▼ [핵심 수정] 단어 단위 줄바꿈 적용 ▼▼▼ */
|
||||
word-break: keep-all; /* 한글 단어 중간 끊김 방지 (필수) */
|
||||
word-wrap: break-word; /* 아주 긴 영단어는 줄바꿈 허용 (안전장치) */
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--light-green);
|
||||
color: var(--primary);
|
||||
font-weight: 900;
|
||||
white-space: nowrap; /* 제목 셀은 무조건 한 줄 유지 */
|
||||
letter-spacing: -0.05em;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
/* [캡션 및 그림 스타일] */
|
||||
figure { display: block; margin: 20px auto; text-align: center; width: 100%; }
|
||||
img, svg { max-width: 95% !important; height: auto !important; display: block; margin: 0 auto; border: 1px solid #eee; }
|
||||
figcaption {
|
||||
display: block; text-align: center; margin-top: 10px;
|
||||
font-size: 9.5pt; color: #666; font-weight: 600;
|
||||
}
|
||||
|
||||
.atomic-block { break-inside: avoid; page-break-inside: avoid; }
|
||||
#raw-container { display: none; }
|
||||
|
||||
/* [하이라이트 박스 표준] */
|
||||
.highlight-box {
|
||||
background-color: rgb(226, 236, 226);
|
||||
border: 1px solid #2a2c2aff;
|
||||
padding: 5px; margin: 1.5px 1.5px 2px 0px; border-radius: 3px;
|
||||
/* 여기 있는 font-size는 li 태그 때문에 무시됩니다. 아래 코드로 제어하세요. */
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.highlight-box li,
|
||||
.highlight-box p {
|
||||
font-size: 11pt !important; /* 글자 크기 (원하는 대로 수정: 예 9pt, 10pt) */
|
||||
line-height: 1.2; /* 줄 간격 (숫자가 클수록 넓어짐: 예 1.4, 1.6) */
|
||||
letter-spacing: -0.6px; /* 자간 (음수면 좁아짐: 예 -0.5px) */
|
||||
margin-bottom: 3px; /* 항목 간 간격 */
|
||||
color: #1a1919ff; /* 글자 색상 */
|
||||
}
|
||||
|
||||
.highlight-box h3, .highlight-box strong, .highlight-box b {
|
||||
font-size: 12pt !important; color: rgba(2, 37, 2, 1) !important;
|
||||
font-weight: bold; margin: 0; display: block; margin-bottom: 5px;
|
||||
}
|
||||
/* 수정 4 목차 스타일 : 대제목 녹색+크게, 그룹 단위 묶음 */
|
||||
.toc-group {
|
||||
margin-bottom: 12px; /* 기존 간격 유지 */
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/* [수정] 점(Bullet) 제거를 위한 핵심 코드 */
|
||||
.toc-lvl-1, .toc-lvl-2, .toc-lvl-3 {
|
||||
list-style: none !important;
|
||||
}
|
||||
|
||||
.toc-item {
|
||||
line-height: 1.8;
|
||||
list-style: none; /* 안전장치 */
|
||||
border-bottom: 1px dotted #f3e1e1ff; /* 기존 점선 스타일 유지 */
|
||||
}
|
||||
|
||||
.toc-lvl-1 {
|
||||
color: #006400; /* 기존 녹색 유지 */
|
||||
font-weight: 900;
|
||||
font-size: 13.5pt; /* 기존 폰트 크기 유지 */
|
||||
margin-top: 15px; /* 기존 상단 여백 유지 */
|
||||
margin-bottom: 5px; /* 기존 하단 여백 유지 */
|
||||
border-bottom: 2px solid #ccc;
|
||||
}
|
||||
.toc-lvl-2 {
|
||||
font-size: 10.5pt;
|
||||
color: #333;
|
||||
margin-left: 20px; /* 기존 들여쓰기 유지 */
|
||||
font-weight: normal;
|
||||
}
|
||||
.toc-lvl-3 {
|
||||
font-size: 10.5pt;
|
||||
color: #666;
|
||||
margin-left: 40px; /* 기존 들여쓰기 유지 */
|
||||
}
|
||||
|
||||
/* [대목차 내부 스타일 보존] */
|
||||
.toc-lvl-1 .toc-number,
|
||||
.toc-lvl-1 .toc-text {
|
||||
font-weight: 900;
|
||||
font-size: 1.2em;
|
||||
color: #006400;
|
||||
}
|
||||
|
||||
.toc-lvl-1 .toc-number {
|
||||
float: left;
|
||||
margin-right: 14px; /* 기존 간격 유지 */
|
||||
}
|
||||
.toc-lvl-1 .toc-text {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* [소목차 내부 스타일 보존] */
|
||||
.toc-lvl-2 .toc-number, .toc-lvl-3 .toc-number {
|
||||
font-weight: bold;
|
||||
color: #2c5282;
|
||||
margin-right: 11px; /* 기존 간격 유지 */
|
||||
}
|
||||
.toc-lvl-2 .toc-text, .toc-lvl-3 .toc-text {
|
||||
color: #4a5568;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* [요약 페이지 전용 스타일 미세 조정] */
|
||||
.squeeze {
|
||||
line-height: 1.35 !important;
|
||||
letter-spacing: -0.5px !important;
|
||||
margin-bottom: 2px !important;
|
||||
}
|
||||
.squeeze-title {
|
||||
margin-bottom: 5px !important;
|
||||
padding-bottom: 2px !important;
|
||||
}
|
||||
|
||||
|
||||
/* 요약 페이지 안의 모든 P 태그에 대해 자간/행간을 좁힘 */
|
||||
#box-summary p,
|
||||
#box-summary li {
|
||||
font-size: 10pt !important; /* 본문보다 0.5pt~1pt 정도 작게 */
|
||||
line-height: 1.45 !important; /* 줄 간격을 조금 더 촘촘하게 (기존 1.6) */
|
||||
letter-spacing: -0.04em !important; /* 자간을 미세하게 좁힘 */
|
||||
margin-bottom: 3px !important; /* 문단 간 격을 줄임 */
|
||||
text-align: justify; /* 양쪽 정렬 유지 */
|
||||
}
|
||||
|
||||
/* 요약 페이지 제목 아래 간격도 조금 줄임 */
|
||||
#box-summary h1 {
|
||||
margin-bottom: 10px !important;
|
||||
padding-bottom: 5px !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="raw-container">
|
||||
<div id="box-cover"></div>
|
||||
<div id="box-toc"></div>
|
||||
<div id="box-summary"></div>
|
||||
<div id="box-content"></div>
|
||||
</div>
|
||||
|
||||
<template id="page-template">
|
||||
<div class="sheet">
|
||||
<div class="page-header"></div>
|
||||
<div class="body-content"></div>
|
||||
<div class="page-footer">
|
||||
<span class="rpt-title"></span>
|
||||
<span class="pg-num"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
window.addEventListener("load", async () => {
|
||||
await document.fonts.ready; // 웹폰트 로딩 대기 (필수)
|
||||
// [Config] 297mm - 20mm(상) - 20mm(하) = 257mm ≈ 970px
|
||||
const CONFIG = { maxHeight: 970 };
|
||||
|
||||
const rawContainer = document.getElementById('raw-container');
|
||||
if (rawContainer) {
|
||||
rawContainer.innerHTML = rawContainer.innerHTML.replace(
|
||||
/(<rect[^>]*?)\s+y="[^"]*"\s+([^>]*?y="[^"]*")/gi,
|
||||
"$1 $2"
|
||||
);
|
||||
}
|
||||
const raw = {
|
||||
cover: document.getElementById('box-cover'),
|
||||
toc: document.getElementById('box-toc'),
|
||||
cover: document.getElementById('box-cover'),
|
||||
toc: document.getElementById('box-toc'),
|
||||
summary: document.getElementById('box-summary'),
|
||||
content: document.getElementById('box-content')
|
||||
};
|
||||
|
||||
let globalPage = 1;
|
||||
let reportTitle = raw.cover.querySelector('h1')?.innerText || "Report";
|
||||
|
||||
function cleanH1Text(text) {
|
||||
if (!text) return "";
|
||||
const parts = text.split("-");
|
||||
return parts[0].trim(); // 첫 번째 부분만 남기고 나머지는 버림
|
||||
}
|
||||
|
||||
// [0] Sanitizer & Pre-processing (Integrity Preserved Version)
|
||||
function detox(node) {
|
||||
if (node.nodeType !== 1) return;
|
||||
|
||||
// [Safety Check 1] SVG 내부는 절대 건드리지 않음 (차트 깨짐 방지)
|
||||
if (node.closest('svg')) return;
|
||||
|
||||
// [Logic A] 클래스 속성 확인 및 변수 할당
|
||||
let cls = "";
|
||||
if (node.hasAttribute('class')) {
|
||||
cls = node.getAttribute('class');
|
||||
}
|
||||
|
||||
// [Logic B] 하이라이트 박스 감지 및 변환 (조건 정밀화)
|
||||
// 조건: 1. bg-, border-, box 중 하나라도 포함되어야 함
|
||||
// 2. 단, title-box(제목박스), toc-(목차), cover-(표지)는 절대 아니어야 함
|
||||
if ( (cls.includes('bg-') || cls.includes('border-') || cls.includes('box')) &&
|
||||
!cls.includes('title-box') &&
|
||||
!cls.includes('toc-') &&
|
||||
!cls.includes('cover-') &&
|
||||
!cls.includes('highlight-box') ) { // 이미 변환된 놈도 건드리지 않음
|
||||
|
||||
// 1. 표준 클래스로 강제 교체
|
||||
node.setAttribute('class', 'highlight-box atomic-block');
|
||||
|
||||
// 2. 박스 내부 제목 스타일 초기화 (기존 스타일과의 충돌 방지)
|
||||
const internalHeads = node.querySelectorAll('h3, h4, strong, b');
|
||||
internalHeads.forEach(head => {
|
||||
head.removeAttribute('style');
|
||||
head.removeAttribute('class');
|
||||
});
|
||||
|
||||
// 3. 인라인 스타일 삭제 (Tailwind inline style 등 제거)
|
||||
node.removeAttribute('style');
|
||||
|
||||
// [중요] 여기서 return하면 안됨! 아래 공통 로직(표 테두리 등)도 타야 함.
|
||||
// 대신, class는 이미 세팅했으므로 class 삭제 로직만 건너뛰게 플래그 변경
|
||||
cls = 'highlight-box atomic-block';
|
||||
}
|
||||
|
||||
// [Logic C] 일반 요소 세탁 (화이트리스트 유지)
|
||||
// 목차, 표지, 제목박스, 그리고 방금 변환된 하이라이트 박스는 살려둠
|
||||
if (node.hasAttribute('class')) {
|
||||
// 위에서 cls 변수가 갱신되었을 수 있으므로 다시 확인하지 않고 기존 조건 활용
|
||||
if (!cls.includes('toc-') &&
|
||||
!cls.includes('cover-') &&
|
||||
!cls.includes('highlight-') &&
|
||||
!cls.includes('title-box') &&
|
||||
!cls.includes('atomic-block')) {
|
||||
|
||||
node.removeAttribute('class');
|
||||
}
|
||||
}
|
||||
|
||||
// [Logic D] 공통 정리 (인라인 스타일 삭제)
|
||||
// 단, 이미 변환된 박스는 위에서 지웠으니 중복 실행되어도 상관없음
|
||||
node.removeAttribute('style');
|
||||
|
||||
// [Logic E] 표 테두리 강제 적용
|
||||
if (node.tagName === 'TABLE') node.border = "1";
|
||||
|
||||
// [Logic F] 캡션 중복 텍스트 숨김 처리
|
||||
if (node.tagName === 'FIGURE') {
|
||||
const internalTitles = node.querySelectorAll('h3, h4, .chart-title');
|
||||
internalTitles.forEach(t => t.style.display = 'none');
|
||||
}
|
||||
}
|
||||
|
||||
function getFlatNodes(element) {
|
||||
// [1] 목차(TOC) 처리 로직 (제목 생성 + 완벽한 그룹화)
|
||||
if(element.id === 'box-toc') {
|
||||
// 1. 스타일 초기화
|
||||
element.querySelectorAll('*').forEach(el => detox(el));
|
||||
|
||||
// 2. 레벨 분석 (위의 formatTOC 실행)
|
||||
formatTOC(element);
|
||||
|
||||
const tocNodes = [];
|
||||
|
||||
// [수정] 원본에 H1이 없으면 '목차' 타이틀 강제 생성
|
||||
let title = element.querySelector('h1');
|
||||
if (!title) {
|
||||
title = document.createElement('h1');
|
||||
title.innerText = "목차";
|
||||
// 디자인 통일성을 위해 스타일 적용은 CSS에 맡김
|
||||
}
|
||||
tocNodes.push(title.cloneNode(true));
|
||||
|
||||
// 3. 그룹화 로직 (Flattened List -> Grouped Divs)
|
||||
// 중첩이 엉망인 원본 무시하고, 모든 li를 긁어모음
|
||||
const allLis = element.querySelectorAll('li');
|
||||
let currentGroup = null;
|
||||
|
||||
allLis.forEach(li => {
|
||||
const isLevel1 = li.classList.contains('toc-lvl-1');
|
||||
|
||||
// 대목차(Level 1)가 나오면 새로운 그룹 시작
|
||||
if (isLevel1) {
|
||||
// 이전 그룹이 있으면 저장
|
||||
if (currentGroup) tocNodes.push(currentGroup);
|
||||
|
||||
// 새 그룹 박스 생성
|
||||
currentGroup = document.createElement('div');
|
||||
currentGroup.className = 'toc-group atomic-block';
|
||||
|
||||
// 내부 UL 생성 (들여쓰기 구조용)
|
||||
const ulWrapper = document.createElement('ul');
|
||||
ulWrapper.style.margin = "0";
|
||||
ulWrapper.style.padding = "0";
|
||||
currentGroup.appendChild(ulWrapper);
|
||||
}
|
||||
|
||||
// 안전장치: 첫 시작이 소목차라 그룹이 없으면 하나 만듦
|
||||
if (!currentGroup) {
|
||||
currentGroup = document.createElement('div');
|
||||
currentGroup.className = 'toc-group atomic-block';
|
||||
const ulWrapper = document.createElement('ul');
|
||||
ulWrapper.style.margin = "0";
|
||||
ulWrapper.style.padding = "0";
|
||||
currentGroup.appendChild(ulWrapper);
|
||||
}
|
||||
|
||||
// 현재 그룹의 ul에 li 추가
|
||||
currentGroup.querySelector('ul').appendChild(li.cloneNode(true));
|
||||
});
|
||||
|
||||
// 마지막 그룹 저장
|
||||
if (currentGroup) tocNodes.push(currentGroup);
|
||||
|
||||
return tocNodes;
|
||||
}
|
||||
|
||||
// [2] 본문(Body) 처리 로직 (기존 박스 보존 로직 유지)
|
||||
let nodes = [];
|
||||
Array.from(element.children).forEach(child => {
|
||||
detox(child);
|
||||
|
||||
if (child.classList.contains('highlight-box')) {
|
||||
child.querySelectorAll('h3, h4, strong, b').forEach(head => {
|
||||
head.removeAttribute('style');
|
||||
head.removeAttribute('class');
|
||||
});
|
||||
nodes.push(child.cloneNode(true));
|
||||
}
|
||||
else if(['DIV','SECTION','ARTICLE','MAIN'].includes(child.tagName)) {
|
||||
nodes = nodes.concat(getFlatNodes(child));
|
||||
}
|
||||
else if (['UL','OL'].includes(child.tagName)) {
|
||||
Array.from(child.children).forEach((li, idx) => {
|
||||
detox(li);
|
||||
const w = document.createElement(child.tagName);
|
||||
w.style.margin="0"; w.style.paddingLeft="20px";
|
||||
if(child.tagName==='OL') w.start=idx+1;
|
||||
const cloneLi = li.cloneNode(true);
|
||||
cloneLi.querySelectorAll('*').forEach(el => detox(el));
|
||||
w.appendChild(cloneLi);
|
||||
nodes.push(w);
|
||||
});
|
||||
} else {
|
||||
const clone = child.cloneNode(true);
|
||||
detox(clone);
|
||||
clone.querySelectorAll('*').forEach(el => detox(el));
|
||||
nodes.push(clone);
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// [Final Optimized Engine] Place -> Squeeze -> Check -> Split
|
||||
// 목적: 배치 즉시 자간을 줄여 2글자 고아를 방지하고, 공간을 확보하여 페이지 밀림을 막음
|
||||
function renderFlow(sectionType, sourceNodes) {
|
||||
if (!sourceNodes.length) return;
|
||||
|
||||
let currentHeaderTitle = sectionType === 'toc' ? "목차" : (sectionType === 'summary' ? "요약" : reportTitle);
|
||||
|
||||
let page = createPage(sectionType, currentHeaderTitle);
|
||||
let body = page.querySelector('.body-content');
|
||||
|
||||
// 원본 노드 보존을 위해 큐에 담기
|
||||
let queue = [...sourceNodes];
|
||||
|
||||
while (queue.length > 0) {
|
||||
let node = queue.shift();
|
||||
let clone = node.cloneNode(true);
|
||||
|
||||
// [태그 판별]
|
||||
let isH1 = clone.tagName === 'H1';
|
||||
let isHeading = ['H2', 'H3'].includes(clone.tagName);
|
||||
// LI도 텍스트로 취급하여 분할 대상에 포함
|
||||
let isText = ['P', 'LI'].includes(clone.tagName) && !clone.classList.contains('atomic-block');
|
||||
let isAtomic = ['TABLE', 'FIGURE', 'IMG', 'SVG'].includes(clone.tagName) ||
|
||||
clone.querySelector('table, img, svg') ||
|
||||
clone.classList.contains('atomic-block');
|
||||
|
||||
// [전처리] H1 텍스트 정제 ("-" 뒤 제거)
|
||||
if (isH1 && clone.innerText.includes('-')) {
|
||||
clone.innerText = clone.innerText.split('-')[0].trim();
|
||||
}
|
||||
|
||||
// [Rule 1] H1 처리 (무조건 새 페이지)
|
||||
if (isH1 && (sectionType === 'body' || sectionType === 'summary')) {
|
||||
currentHeaderTitle = clone.innerText;
|
||||
if (body.children.length > 0) {
|
||||
page = createPage(sectionType, currentHeaderTitle);
|
||||
body = page.querySelector('.body-content');
|
||||
} else {
|
||||
page.querySelector('.page-header').innerText = currentHeaderTitle;
|
||||
}
|
||||
}
|
||||
|
||||
// [Rule 2] Orphan Control (제목이 페이지 끝에 걸리는 것 방지)
|
||||
if (isHeading) {
|
||||
const spaceLeft = CONFIG.maxHeight - body.scrollHeight;
|
||||
if (spaceLeft < 90) {
|
||||
page = createPage(sectionType, currentHeaderTitle);
|
||||
body = page.querySelector('.body-content');
|
||||
}
|
||||
}
|
||||
|
||||
// ▼▼▼ [Step 1: 일단 배치 (Place)] ▼▼▼
|
||||
body.appendChild(clone);
|
||||
|
||||
// ▼▼▼ [Step 2: 자간 최적화 (Squeeze Logic)] ▼▼▼
|
||||
// 배치 직후, 자간을 줄여서 줄바꿈을 없앨 수 있는지 확인
|
||||
// 대상: 10글자 이상인 텍스트 노드
|
||||
if (isText && clone.innerText.length > 10) {
|
||||
const originalHeight = clone.offsetHeight;
|
||||
|
||||
// 1. 강력하게 줄여봄
|
||||
clone.style.letterSpacing = "-1.0px";
|
||||
|
||||
// 2. 높이가 줄어들었는가? (줄바꿈이 사라짐 = Orphan 해결)
|
||||
if (clone.offsetHeight < originalHeight) {
|
||||
// 성공! 너무 빽빽하지 않게 -0.8px로 안착
|
||||
clone.style.letterSpacing = "-0.8px";
|
||||
} else {
|
||||
// 실패! 줄여도 줄이 안 바뀌면 가독성을 위해 원상복구
|
||||
clone.style.letterSpacing = "";
|
||||
}
|
||||
}
|
||||
// ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
|
||||
|
||||
// [Rule 3] 넘침 감지 (Overflow Check)
|
||||
if (body.scrollHeight > CONFIG.maxHeight) {
|
||||
|
||||
// 3-1. 텍스트 분할 (Split) - LI 태그 포함
|
||||
if (isText) {
|
||||
body.removeChild(clone); // 일단 제거
|
||||
|
||||
let textContent = node.innerText;
|
||||
let tempP = node.cloneNode(false); // 태그 속성 유지 (li면 li, p면 p)
|
||||
tempP.innerText = "";
|
||||
|
||||
// 위에서 결정된 최적 자간 스타일 유지
|
||||
if (clone.style.letterSpacing) tempP.style.letterSpacing = clone.style.letterSpacing;
|
||||
|
||||
body.appendChild(tempP);
|
||||
|
||||
const words = textContent.split(' ');
|
||||
let currentText = "";
|
||||
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
let word = words[i];
|
||||
let prevText = currentText;
|
||||
currentText += (currentText ? " " : "") + word;
|
||||
tempP.innerText = currentText;
|
||||
|
||||
// 단어 하나 추가했더니 넘쳤는가?
|
||||
if (body.scrollHeight > CONFIG.maxHeight) {
|
||||
// 직전 상태(안 넘치는 상태)로 복구
|
||||
tempP.innerText = prevText;
|
||||
|
||||
// [디자인 보정] 잘린 문단의 마지막 줄 양쪽 정렬
|
||||
tempP.style.textAlign = "justify";
|
||||
tempP.style.textAlignLast = "justify";
|
||||
|
||||
// 남은 단어들을 다시 합쳐서 대기열 맨 앞으로
|
||||
let remainingText = words.slice(i).join(' ');
|
||||
let remainingNode = node.cloneNode(false);
|
||||
remainingNode.innerText = remainingText;
|
||||
|
||||
queue.unshift(remainingNode);
|
||||
|
||||
// 새 페이지 생성
|
||||
page = createPage(sectionType, currentHeaderTitle);
|
||||
body = page.querySelector('.body-content');
|
||||
|
||||
// [중요] 새 페이지 갔으면 압축 플래그/스타일 초기화
|
||||
// 새 페이지에서는 다시 넉넉하게 시작해야 함
|
||||
body.style.lineHeight = "";
|
||||
body.style.letterSpacing = "";
|
||||
|
||||
break; // for문 탈출
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3-2. 표, 그림, 박스인 경우 -> 통째로 다음 장으로 이동
|
||||
else {
|
||||
body.removeChild(clone); // 일단 뺌
|
||||
|
||||
// [Gap Filling] 빈 공간 채우기
|
||||
let spaceLeft = CONFIG.maxHeight - body.scrollHeight;
|
||||
|
||||
// 공간이 50px 이상 있고, 앞에 글자가 이미 있을 때만 채우기 시도
|
||||
if (body.children.length > 0 && spaceLeft > 50 && queue.length > 0) {
|
||||
while(queue.length > 0) {
|
||||
let candidate = queue[0];
|
||||
if (['H1','H2','H3'].includes(candidate.tagName) ||
|
||||
candidate.classList.contains('atomic-block') ||
|
||||
candidate.querySelector('img, table')) break;
|
||||
|
||||
let filler = candidate.cloneNode(true);
|
||||
|
||||
// 가져올 때도 최적화(Squeeze) 시도
|
||||
if(['P','LI'].includes(filler.tagName) && filler.innerText.length > 10) {
|
||||
const hBefore = filler.offsetHeight; // (가상)
|
||||
filler.style.letterSpacing = "-1.0px";
|
||||
// 실제 DOM에 붙여봐야 높이를 알 수 있으므로 일단 적용
|
||||
}
|
||||
|
||||
body.appendChild(filler);
|
||||
|
||||
if (body.scrollHeight <= CONFIG.maxHeight) {
|
||||
// 들어갔으면 확정하고 대기열 제거
|
||||
// 최적화 스타일 유지 (-1.0px -> -0.8px 조정 등은 생략해도 무방하나 디테일 원하면 추가 가능)
|
||||
if(filler.style.letterSpacing === "-1.0px") filler.style.letterSpacing = "-0.8px";
|
||||
queue.shift();
|
||||
} else {
|
||||
body.removeChild(filler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 이미지 배치 (수정된 핵심 로직)
|
||||
// [버그 수정] 현재 페이지가 비어있지 않을 때만 새 페이지 생성!
|
||||
if (body.children.length > 0) {
|
||||
page = createPage(sectionType, currentHeaderTitle);
|
||||
body = page.querySelector('.body-content');
|
||||
}
|
||||
|
||||
// 이미지를 붙임
|
||||
body.appendChild(clone);
|
||||
|
||||
// [Smart Fit] 넘치면 축소 (기존 유지)
|
||||
if (isAtomic && body.scrollHeight > CONFIG.maxHeight) {
|
||||
const currentH = clone.offsetHeight;
|
||||
const overflow = body.scrollHeight - CONFIG.maxHeight;
|
||||
body.removeChild(clone);
|
||||
|
||||
if (overflow > 0 && overflow < (currentH * 0.15)) {
|
||||
clone.style.transform = "scale(0.85)";
|
||||
clone.style.transformOrigin = "top center";
|
||||
clone.style.marginBottom = `-${currentH * 0.15}px`;
|
||||
body.appendChild(clone);
|
||||
} else {
|
||||
body.appendChild(clone); // 너무 크면 그냥 둠
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createPage(type, headerTitle) {
|
||||
const tpl = document.getElementById('page-template');
|
||||
const clone = tpl.content.cloneNode(true);
|
||||
const sheet = clone.querySelector('.sheet');
|
||||
|
||||
if (type === 'cover') {
|
||||
sheet.innerHTML = "";
|
||||
const title = raw.cover.querySelector('h1')?.innerText || "Report";
|
||||
const sub = raw.cover.querySelector('h2')?.innerText || "";
|
||||
const pTags = raw.cover.querySelectorAll('p');
|
||||
const infos = pTags.length > 0 ? Array.from(pTags).map(p => p.innerText).join(" / ") : "";
|
||||
|
||||
// [표지 스타일] 테두리 제거 및 중앙 정렬
|
||||
sheet.innerHTML = `
|
||||
<div style="position:absolute; top:20mm; right:20mm; text-align:right; font-size:11pt; color:#666;">${infos}</div>
|
||||
<div style="display:flex; flex-direction:column; justify-content:center; align-items:center; height:100%; text-align:center; width:100%;">
|
||||
<div style="width:85%;">
|
||||
<div style="font-size:32pt; font-weight:900; color:var(--primary); line-height:1.2; margin-bottom:30px; word-break:keep-all;">${title}</div>
|
||||
<div style="font-size:20pt; font-weight:300; color:#444; word-break:keep-all;">${sub}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
} else {
|
||||
// ... (나머지 페이지 생성 로직 기존 유지) ...
|
||||
clone.querySelector('.page-header').innerText = headerTitle;
|
||||
clone.querySelector('.rpt-title').innerText = reportTitle;
|
||||
if (type !== 'toc') clone.querySelector('.pg-num').innerText = `- ${globalPage++} -`;
|
||||
else clone.querySelector('.pg-num').innerText = "";
|
||||
}
|
||||
document.body.appendChild(sheet);
|
||||
return sheet;
|
||||
}
|
||||
|
||||
createPage('cover');
|
||||
if(raw.toc) renderFlow('toc', getFlatNodes(raw.toc));
|
||||
|
||||
// [요약 페이지 지능형 맞춤 로직 (Smart Squeeze)]
|
||||
const summaryNodes = getFlatNodes(raw.summary);
|
||||
|
||||
// 1. 가상 공간에 미리 렌더링하여 높이 측정
|
||||
const tempBox = document.createElement('div');
|
||||
tempBox.style.width = "210mm";
|
||||
tempBox.style.position = "absolute";
|
||||
tempBox.style.visibility = "hidden";
|
||||
tempBox.id = 'box-summary'; // CSS 적용
|
||||
document.body.appendChild(tempBox);
|
||||
|
||||
// 노드 복제하여 주입
|
||||
summaryNodes.forEach(node => tempBox.appendChild(node.cloneNode(true)));
|
||||
|
||||
// 2. 높이 분석 (Smart Runt Control)
|
||||
const totalHeight = tempBox.scrollHeight;
|
||||
const pageHeight = CONFIG.maxHeight; // 1페이지 가용 높이 (약 970px)
|
||||
const lastPart = totalHeight % pageHeight;
|
||||
|
||||
// [조건] 넘친 양이 100px 미만일 때 압축
|
||||
if (totalHeight > pageHeight && lastPart > 0 && lastPart < 180) {
|
||||
summaryNodes.forEach(node => {
|
||||
if(node.nodeType === 1) {
|
||||
node.classList.add('squeeze');
|
||||
if(node.tagName === 'H1') node.classList.add('squeeze-title');
|
||||
|
||||
// [추가] P, LI 태그에 더 강력한 인라인 스타일 강제 주입 (폰트 축소 포함)
|
||||
if(node.tagName === 'P' || node.tagName === 'LI') {
|
||||
node.style.fontSize = "9.5pt";
|
||||
node.style.lineHeight = "1.4";
|
||||
node.style.letterSpacing = "-0.8px";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 뒷정리
|
||||
document.body.removeChild(tempBox);
|
||||
// 3. 렌더링 실행
|
||||
renderFlow('summary', summaryNodes);
|
||||
|
||||
// ▼▼▼ [기존 유지] 본문 렌더링 및 마무리 작업 ▼▼▼
|
||||
renderFlow('body', getFlatNodes(raw.content));
|
||||
|
||||
// 긴 제목 자동 축소 (기존 기능 유지)
|
||||
document.querySelectorAll('.sheet h1, .sheet h2').forEach(el => {
|
||||
let fs = 100;
|
||||
while(el.scrollWidth > el.clientWidth && fs > 50) { el.style.fontSize = (--fs)+"%"; }
|
||||
});
|
||||
|
||||
// ▼▼▼▼▼ [수정된 핵심 로직: 통합 자간 조정] ▼▼▼▼▼
|
||||
// 변경점 1: 'li' 태그 포함
|
||||
// 변경점 2: 표, 그림 내부 텍스트 제외
|
||||
// 변경점 3: 글자수 제한 완화 (10자 이상이면 검사)
|
||||
const allTextNodes = document.querySelectorAll('.sheet .body-content p, .sheet .body-content li');
|
||||
|
||||
allTextNodes.forEach(el => {
|
||||
// 1. [제외 대상] 표(table), 그림(figure), 차트 내부는 건드리지 않음
|
||||
if (el.closest('table') || el.closest('figure') || el.closest('.chart')) return;
|
||||
|
||||
// 2. [최소 길이] 10자 미만은 무시
|
||||
if (el.innerText.trim().length < 10) return;
|
||||
|
||||
// 3. [테스트]
|
||||
const originH = el.offsetHeight;
|
||||
const originSpacing = el.style.letterSpacing;
|
||||
el.style.fontSize = "12pt";
|
||||
|
||||
// 강력하게 당겨봄
|
||||
el.style.letterSpacing = "-1.4px";
|
||||
|
||||
const newH = el.offsetHeight;
|
||||
|
||||
// 성공(높이 줄어듦) 시
|
||||
if (newH < originH) {
|
||||
el.style.letterSpacing = "-1.0px"; // 적당히 안착
|
||||
}
|
||||
else {
|
||||
el.style.letterSpacing = originSpacing; // 원상복구
|
||||
}
|
||||
});
|
||||
// ▲▲▲▲▲ [수정 끝] ▲▲▲▲▲
|
||||
|
||||
// 제목 자동 축소 (중복 실행 방지를 위해 제거해도 되지만, 안전하게 둠)
|
||||
document.querySelectorAll('.sheet h1, .sheet h2').forEach(el => {
|
||||
let fs = 100;
|
||||
while(el.scrollWidth > el.clientWidth && fs > 50) { el.style.fontSize = (--fs)+"%"; }
|
||||
});
|
||||
|
||||
const pages = document.querySelectorAll('.sheet'); // .page 대신 .sheet로 수정하여 정확도 높임
|
||||
if (pages.length >= 2) {
|
||||
const lastSheet = pages[pages.length - 1];
|
||||
const prevSheet = pages[pages.length - 2];
|
||||
// 커버나 목차가 아닐때만 진행
|
||||
if(lastSheet.querySelector('.rpt-title')) {
|
||||
const lastBody = lastSheet.querySelector('.body-content');
|
||||
const prevBody = prevSheet.querySelector('.body-content');
|
||||
|
||||
// 마지막 페이지 내용이 3줄(약 150px) 이하인가?
|
||||
if (lastBody.scrollHeight < 150 && lastBody.innerText.trim().length > 0) {
|
||||
prevBody.style.lineHeight = "1.3"; // 앞 페이지 압축
|
||||
prevBody.style.paddingBottom = "0px";
|
||||
|
||||
const contentToMove = Array.from(lastBody.children);
|
||||
contentToMove.forEach(child => prevBody.appendChild(child.cloneNode(true)));
|
||||
|
||||
if (prevBody.scrollHeight <= CONFIG.maxHeight + 5) {
|
||||
lastSheet.remove(); // 성공 시 마지막 장 삭제
|
||||
} else {
|
||||
// 실패 시 원상 복구
|
||||
for(let i=0; i<contentToMove.length; i++) prevBody.lastElementChild.remove();
|
||||
prevBody.style.lineHeight = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 원본 데이터 삭제
|
||||
const rawContainer = document.getElementById('raw-container');
|
||||
if(rawContainer) rawContainer.remove();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
⚠️ [최종 경고 - 출력 직전 필수 확인]
|
||||
1. 원본의 모든 텍스트가 100% 포함되었는가?
|
||||
2. "..." 또는 요약된 문장이 없는가?
|
||||
3. 생략된 문단이 단 하나도 없는가?
|
||||
|
||||
위 3가지 중 하나라도 위반 시, 출력을 중단하고 처음부터 다시 작성하십시오.
|
||||
원본 텍스트 글자 수와 출력 텍스트 글자 수가 동일해야 합니다.
|
||||
Reference in New Issue
Block a user