genetics-mcp 2.9.1 → 2.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aarch64-apple-darwin/gcell +0 -0
- package/bin/aarch64-unknown-linux-musl/gcell +0 -0
- package/bin/manifest.json +4 -4
- package/bin/x86_64-apple-darwin/gcell +0 -0
- package/bin/x86_64-unknown-linux-musl/gcell +0 -0
- package/genetics.mjs +129 -16
- package/package.json +1 -1
- package/skills/genetics-cell-id/SKILL.md +186 -0
- package/skills/genetics-report/SKILL.md +212 -93
- package/skills/genetics-report/assets/example/cases.json +19 -22
- package/skills/genetics-report/assets/example/report.json +198 -53
- package/skills/genetics-report/assets/render.py +21 -10
- package/skills/genetics-report/assets/subset_font.py +24 -8
- package/skills/genetics-report/references/content-pipeline.md +316 -733
- package/skills/genetics-report/references/design-system.md +20 -723
- package/skills/genetics-report/references/font-embedding.md +132 -108
- package/skills/genetics-report/references/illustrations.md +20 -462
- package/skills/genetics-report/references/lim-portal-format.md +623 -185
- package/skills/genetics-report/references/process.md +414 -403
- package/skills/genetics-report/references/qa.md +28 -712
- package/skills/genetics-report/assets/generate-illustrations.py +0 -159
- package/skills/genetics-report/assets/qa.py +0 -198
- package/skills/genetics-report/assets/visual-baseline.py +0 -459
- package/skills/genetics-report/references/textbook-mode.md +0 -111
- package/skills/genetics-report/references/wysiwyg.md +0 -751
|
@@ -1,161 +1,185 @@
|
|
|
1
1
|
# 폰트 임베드
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
산출물은 **외부 리소스 0인 단일 HTML** 이다. 웹폰트 CDN 을 쓸 수 없다.
|
|
6
|
-
그렇다고 시스템 폰트 스택에 맡기면 고객 PC 환경에 따라 문서가 이렇게 갈린다.
|
|
3
|
+
## 결론 먼저 — 고를 자리가 없다
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
| macOS | Apple SD Gothic Neo |
|
|
11
|
-
| Windows | 맑은 고딕 |
|
|
12
|
-
| Pretendard 설치된 PC | Pretendard |
|
|
5
|
+
**서식이 폰트를 정한다.**
|
|
6
|
+
`report.json` 에 `font` 를 적어도 두 경로 모두에서 버려진다.
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
| 경로 | 무슨 일이 일어나는가 | 근거 |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| 로컬 렌더 | `cfg.update(t['lim-report'])` 가 `font` 키를 통째로 교체 | `assets/render.py:157-158` |
|
|
11
|
+
| 포털 발행 | `font` 가 `REPORT_CLIENT_KEYS` 에 없어 `report_client_fields()` 가 깎아 냄 | `report_publish.php:31-` |
|
|
17
12
|
|
|
18
|
-
그래서
|
|
13
|
+
그래서 `font.embed: false` 도 안 먹는다 — 교체된 블록에 `embed` 키가 없어 기본값 `True` 로 돌아간다.
|
|
14
|
+
(`SKILL.md:311` 의 「`font.embed: false` 로 끌 수 있다」는 死文 — 그 파일은 이 문서의 담당이 아니라 적어만 둔다.)
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
폰트를 바꿀 이유가 생기면 그것은 **서버 등록부를 고치는 일**이다.
|
|
17
|
+
문서 데이터에서 할 일이 아니다.
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
## 지금 실리는 것
|
|
23
20
|
|
|
24
|
-
| 항목 | 값 |
|
|
21
|
+
| 항목 | 값 | 확인 |
|
|
25
22
|
|---|---|---|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
23
|
+
| 파일 | `private/builder/fonts/pretendard-gazette.woff2` | `report_publish.php:872` |
|
|
24
|
+
| 크기 | **409,044B** · 1면 | `ls` · 렌더 로그 |
|
|
25
|
+
| 패밀리 | **Pretendard GOV** | `report_publish.php:869` |
|
|
26
|
+
| 축 | **wght 300–700 가변** | `'weight' => '300 700'` |
|
|
27
|
+
| 라이선스 | Pretendard · SIL OFL 1.1 | CSS 첫 줄 주석으로 실림 |
|
|
28
|
+
|
|
29
|
+
예시 문서를 실제로 구운 결과다.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
폰트 임베드 1면 409,044B → base64 545,392B
|
|
33
|
+
생성 · 예시-보고서.html 659,164 B
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
문서의 **83%가 폰트**다.
|
|
37
|
+
대신 외부 요청이 0이다 — 산출물에서 `http(s)` 로 나가는 `src`·`href` 0건을 실측했다.
|
|
38
|
+
|
|
39
|
+
## 왜 심는가
|
|
40
|
+
|
|
41
|
+
시스템 폰트에 맡기면 같은 문장의 폭이 환경마다 갈린다.
|
|
42
|
+
같은 문자열을 64px 로 재면 이렇다.
|
|
43
|
+
|
|
44
|
+
| 폰트 | 폭 |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Pretendard GOV (임베드본) | 879.88 |
|
|
47
|
+
| Apple SD Gothic Neo (macOS) | 846.08 |
|
|
48
|
+
| Noto Sans KR | 883.58 |
|
|
49
|
+
| `sans-serif` | 869.94 |
|
|
50
|
+
|
|
51
|
+
4% 차이는 줄바꿈 위치를 바꾼다.
|
|
52
|
+
관보 본문은 `--measure: 37em ≈ 43자`로 글줄을 잰다 — 자폭이 달라지면 그 계산이 통째로 어긋난다.
|
|
53
|
+
인쇄·PDF 에서는 더 벌어진다.
|
|
31
54
|
|
|
32
|
-
##
|
|
55
|
+
## 왜 300 인가 — 「400–700」은 死文이다
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
`gazette.css` 의 굵기는 3종이다: `--w-light: 300` · `--w-body: 400` · `--w-bold: 700`.
|
|
58
|
+
300 이 서는 자리는 셋뿐이고, 전부 문서의 얼굴이다.
|
|
35
59
|
|
|
36
|
-
|
|
|
60
|
+
| 선택자 | 크기 | 무엇 |
|
|
37
61
|
|---|---|---|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
| 정적 2종 (400/700) | 325,576B | `font-weight:650` 이 700 으로 튄다 |
|
|
62
|
+
| `.cover__t2` | 42px | 표지 대제목 **둘째 행**(`title_sub`) |
|
|
63
|
+
| `.pt__no` | 61px | PART 간지 번호 |
|
|
64
|
+
| `.toc__pn` | 20px | 차례의 PART 번호 |
|
|
42
65
|
|
|
43
|
-
축을 400–700 으로 자르면
|
|
44
|
-
|
|
66
|
+
축을 400–700 으로 자르면 이 셋이 **400 으로 눌린다**.
|
|
67
|
+
육안으로는 「좀 굵네」로만 보여서 안 잡힌다.
|
|
45
68
|
|
|
46
|
-
|
|
69
|
+
축이 살아 있는지는 폭이 단조 증가하는지로 안다(실측).
|
|
47
70
|
|
|
48
71
|
```
|
|
49
|
-
|
|
50
|
-
wght 500 → 885.03 wght 650 → 892.94
|
|
72
|
+
300 → 870.72 400 → 879.88 500 → 885.03 650 → 892.94 700 → 895.50
|
|
51
73
|
```
|
|
52
74
|
|
|
53
|
-
|
|
75
|
+
렌더된 문서에서 실제로 쓰인 굵기도 300·400·700 셋뿐이다(실측).
|
|
54
76
|
|
|
55
|
-
|
|
56
|
-
|---|---|---|---|
|
|
57
|
-
| 문서에 실제 쓰인 글자만 | 623 | ~60KB | ❌ **편집기로 새 글자를 타이핑하면 다른 폰트로 떨어진다** |
|
|
58
|
-
| **KS X 1001 완성형** | 2,350 | **300KB** | ✅ 일반 한국어 산문을 사실상 전부 덮는다 |
|
|
59
|
-
| 현대 한글 전체 | 11,172 | 1,195KB | ❌ 4배인데 실익은 의성어·외래어 표기뿐 |
|
|
77
|
+
## 커버리지 — 재는 법
|
|
60
78
|
|
|
61
|
-
이
|
|
62
|
-
|
|
79
|
+
이 기계에는 `fontTools`·`brotli` 가 없다.
|
|
80
|
+
그래서 폰트 파일을 열어 글리프를 세는 대신 **브라우저에서 잰다**.
|
|
81
|
+
없는 글자는 폴백으로 떨어지므로 폭이 다르게 나온다.
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
```js
|
|
84
|
+
const c = document.createElement('canvas').getContext('2d');
|
|
85
|
+
c.font = '400 64px "Pretendard GOV"';
|
|
86
|
+
const d = new Map(); // 현대 한글 11,172 음절의 폭 분포
|
|
87
|
+
for (let cp = 0xAC00; cp <= 0xD7A3; cp++) {
|
|
88
|
+
const v = c.measureText(String.fromCodePoint(cp)).width.toFixed(2);
|
|
89
|
+
d.set(v, (d.get(v) || 0) + 1);
|
|
90
|
+
}
|
|
91
|
+
console.log([...d].sort((a, b) => b[1] - a[1])); // 두 무리로 갈린다
|
|
69
92
|
```
|
|
70
93
|
|
|
71
|
-
|
|
94
|
+
실측 결과 — 55.31px 이 **2,350자**, 55.36px 이 8,822자.
|
|
95
|
+
2,350 = KS X 1001 완성형이고, 55.36 은 폴백 폭이다.
|
|
96
|
+
즉 지금 실린 것은 **KS X 1001 완성형**이다.
|
|
72
97
|
|
|
73
|
-
|
|
74
|
-
판정표의 절 번호, 표지 메타의 날짜. 여기서 자릿수 폭이 어긋나면 표가 흔들린다.
|
|
98
|
+
폰트 파일 전체 글리프 수(라틴·기호 포함)는 **확인 못 함** — 세려면 `fontTools` 가 필요하다.
|
|
75
99
|
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
KS X 1001 밖 음절(`뷁` 같은)은 시스템 폰트로 떨어진다.
|
|
101
|
+
두부(□)가 되지는 않는다 — 우아한 폴백이다.
|
|
102
|
+
포털 편집기(`/edit`)에서 사람이 새로 타이핑한 글자가 여기 걸린다.
|
|
78
103
|
|
|
79
|
-
|
|
104
|
+
## `tnum` 은 반드시 남긴다
|
|
80
105
|
|
|
81
|
-
|
|
106
|
+
`gazette.css` 가 `font-variant-numeric: tabular-nums` 를 **20곳**에서 쓴다.
|
|
107
|
+
차례의 절 번호, 머리띠 배지, 표의 수치가 전부 여기 걸린다.
|
|
82
108
|
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
`pyftsubset --layout-features=''` 로 피처를 통째로 버리면 `tnum` 이 사라진다.
|
|
110
|
+
**CSS 는 그대로인데 자릿수 정렬만 조용히 죽는다** — 화면으로는 「표가 좀 어긋나 보이네」로만 보인다.
|
|
85
111
|
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
남길 목록은 `assets/subset_font.py` 의 `LAYOUT` 상수다.
|
|
113
|
+
`ccmp locl kern mark mkmk calt clig liga rlig tnum case`.
|
|
88
114
|
|
|
115
|
+
## 다시 만들 때 — 함정 하나
|
|
116
|
+
|
|
117
|
+
거의 실행할 일이 없다.
|
|
118
|
+
실행한다면 `--wght` 는 **기본값이 `300:700`** 이라 적지 않아도 된다(2026-08-24 고침).
|
|
119
|
+
관보가 `--w-light: 300` 을 쓰기 때문이고, 400 부터 자르면 표지 둘째 행과 간지 숫자가 눌린다.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
python3 -m venv .fontenv && .fontenv/bin/pip install fonttools brotli
|
|
89
123
|
.fontenv/bin/python assets/subset_font.py PretendardVariable.ttf \
|
|
90
|
-
|
|
124
|
+
~/project/FCG/report-portal/private/builder/fonts/pretendard-gazette.woff2 \
|
|
125
|
+
--wght=300:700
|
|
91
126
|
```
|
|
92
127
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
| 플래그 | 용도 |
|
|
96
|
-
|---|---|
|
|
97
|
-
| `--coverage=full` | 현대 한글 11,172자 (1.19MB) |
|
|
98
|
-
| `--coverage=used --used-from=out.html` | 그 문서에 쓰인 글자만 (편집 안 할 때만) |
|
|
99
|
-
| `--wght=400:900` | 더 굵은 표제가 필요할 때 |
|
|
100
|
-
| `--wght=400` | 단일 굵기 (가변 축 제거) |
|
|
128
|
+
기본값대로 구우면 파일 이름만 `gazette` 이고 내용은 300 이 없는 폰트가 된다.
|
|
129
|
+
그러면 표지 둘째 행과 간지 번호가 400 으로 눌린 문서가 나간다.
|
|
101
130
|
|
|
102
|
-
## CSS 쪽 함정
|
|
131
|
+
## CSS 쪽 함정 셋
|
|
103
132
|
|
|
104
|
-
|
|
133
|
+
**① `format("woff2-variations")` 를 쓰지 않는다.**
|
|
134
|
+
폐기된 표기다 — 인식 못 하는 브라우저가 그 `src` 를 통째로 건너뛰고 조용히 대체 폰트로 떨어진다.
|
|
135
|
+
`build.py:328-331` 이 `"woff2"` 만 쓴다. 산출물에서 `woff2-variations` 0건 확인.
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
**② 패밀리 이름은 `Pretendard` 가 아니라 `Pretendard GOV` 다.**
|
|
138
|
+
고객 PC 에 Pretendard 가 설치돼 있어도 부딪히지 않는다 — 이름이 다르기 때문이다.
|
|
139
|
+
스택 첫 후보가 `"Pretendard GOV"`, 둘째가 `"Pretendard"` 인 것이 그 설계다.
|
|
108
140
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
```
|
|
141
|
+
**③ 이름을 바꿀 때는 따옴표를 조심한다.**
|
|
142
|
+
`build.py:330` 이 `font-family:{fam}` 을 **따옴표 없이** 박는다.
|
|
143
|
+
숫자로 시작하는 낱말이나 특수문자가 든 이름을 넣으면 그 선언이 깨진다.
|
|
113
144
|
|
|
114
|
-
|
|
145
|
+
## `build.py` 를 직접 부르면 폰트가 갈린다
|
|
115
146
|
|
|
116
|
-
|
|
117
|
-
|
|
147
|
+
`cfg` 에 `font` 가 없으면 `build.py:313-315` 가 `fonts/pretendard-report.woff2` + `'400 700'` 으로 떨어진다.
|
|
148
|
+
그 옛 파일(**300,544B**)이 아직 레포에 남아 있어서 **조용히 실린다**.
|
|
149
|
+
결과는 300 이 죽은 문서인데, 렌더는 성공으로 끝난다.
|
|
118
150
|
|
|
119
|
-
|
|
120
|
-
고객 PC 에 Pretendard Variable 이 설치돼 있으면 **그쪽이 먼저 잡혀** 환경마다 렌더가 갈린다.
|
|
121
|
-
임베드한 의미가 사라진다.
|
|
151
|
+
로컬 렌더는 `python3 assets/render.py <report.json>` 하나로 한다.
|
|
122
152
|
|
|
123
|
-
## 임베드가
|
|
153
|
+
## 임베드가 먹었는지 확인하는 법
|
|
124
154
|
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
`fontchk.mjs` 는 이것을 **안 잡는다** — 렌더된 활자 크기·굵기 분포만 본다.
|
|
156
|
+
콘솔에서 두 줄로 확인한다.
|
|
127
157
|
|
|
128
158
|
```js
|
|
129
|
-
document.fonts.
|
|
130
|
-
|
|
131
|
-
const c = document.createElement('canvas').getContext('2d');
|
|
132
|
-
c.font = `${wt} 64px ${f}`;
|
|
133
|
-
return c.measureText('읽을 수 있는 보고서 0123456789').width;
|
|
134
|
-
};
|
|
135
|
-
w('Pretendard') !== w('"Apple SD Gothic Neo"') // true 여야 한다 (폴백이 아님)
|
|
136
|
-
w('Pretendard', 650) !== w('Pretendard', 700) // true 여야 한다 (가변 축이 살아 있음)
|
|
159
|
+
[...document.fonts].map(f => [f.family, f.weight, f.status]);
|
|
160
|
+
// → [["Pretendard GOV", "300 700", "loaded"]] 여야 한다
|
|
137
161
|
```
|
|
138
162
|
|
|
139
|
-
|
|
163
|
+
다른 패밀리와 폭을 비교하는 방식은 쓰지 않는다.
|
|
164
|
+
실측에서 **없는 패밀리의 폴백 폭 895.50 이 Pretendard GOV 700 과 같은 값**으로 나왔다.
|
|
165
|
+
같은 패밀리 안에서 300 < 400 < 700 인지를 보는 편이 확실하다.
|
|
140
166
|
|
|
141
|
-
## 다른
|
|
167
|
+
## 다른 폰트로 갈아야 한다면
|
|
142
168
|
|
|
143
|
-
|
|
169
|
+
**임베드는 배포에 해당한다.** 웹폰트 임베드를 허용하는 라이선스여야 한다.
|
|
144
170
|
|
|
145
171
|
| 폰트 | 라이선스 | 임베드 |
|
|
146
172
|
|---|---|---|
|
|
147
|
-
| Pretendard | SIL OFL 1.1 |
|
|
148
|
-
| 나눔 계열 | SIL OFL 1.1 |
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"family": "내폰트", "weight": "400 700",
|
|
158
|
-
"license": "폰트명 · 라이선스 · 출처 URL" }
|
|
159
|
-
```
|
|
173
|
+
| Pretendard | SIL OFL 1.1 | 가능 |
|
|
174
|
+
| 나눔 계열 · 본고딕 / Noto Sans KR | SIL OFL 1.1 | 가능 |
|
|
175
|
+
| 맑은 고딕 | Microsoft | 불가 (시스템 폴백으로만) |
|
|
176
|
+
| 유료 한글 폰트 | 개별 계약 | 웹폰트 조항 확인 필수 |
|
|
177
|
+
|
|
178
|
+
바꾸는 자리는 세 곳이고, 전부 `report.json` 바깥이다.
|
|
179
|
+
`report_publish.php` 의 `REPORT_TEMPLATES` · `gazette.css` 의 `--font` · 레포의 `fonts/`.
|
|
180
|
+
셋 중 하나를 놓치면 문서가 폴백으로 나가는데 육안으로는 잘 안 잡힌다.
|
|
181
|
+
|
|
182
|
+
## 조판 규칙은 여기 없다
|
|
160
183
|
|
|
161
|
-
|
|
184
|
+
활자 스케일·괘선·간격 격자·인쇄 페이지 문법은 `gazette-report` 스킬이 원전이다.
|
|
185
|
+
검사기 8종의 실행법도 그쪽에 있다 — 두 벌로 두지 않는다.
|