with one click
math-hwpx
수학 수식 문제지 HWPX 생성 (중1~고3, 학력평가/수능 형식)
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
수학 수식 문제지 HWPX 생성 (중1~고3, 학력평가/수능 형식)
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | math-hwpx |
| description | 수학 수식 문제지 HWPX 생성 (중1~고3, 학력평가/수능 형식) |
수학 수식(hp:equation)을 포함한 2열 문제지를 HWPX 파일로 생성하는 스킬.
중학교 1학년 ~ 고등학교 3학년 범위의 수학 문제를 한컴오피스 수식 편집기 스크립트 문법으로 작성한다.
hwpx 스킬과 동일한 XML-first 워크플로우를 따르며, 기존 hwpx 스킬의 빌드/검증 도구와 호환된다.
두 가지 형식 지원:
--exam-type worksheet 명시 필요): 단순 2열 수학 문제지# SKILL_DIR는 이 SKILL.md가 위치한 디렉토리의 절대 경로로 설정
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)" # 스크립트 내에서
# hwpx 기본 스킬 경로 (검증/추출 도구 사용 시)
HWPX_SKILL_DIR="<hwpx 스킬 설치 경로>"
# Python 가상환경 (프로젝트에 맞게 설정)
VENV="<프로젝트>/.venv/bin/activate"
모든 Python 실행 시:
# 프로젝트의 .venv를 활성화 (pip install lxml 필요)
source "$VENV"
.claude/skills/math-hwpx/
├── SKILL.md # 이 파일
├── scripts/
│ ├── build_math_hwpx.py # CLI + build 오케스트레이션 (~170줄)
│ ├── xml_primitives.py # IDGen, STYLE 상수, 기본 문단/수식 생성기
│ ├── exam_helpers.py # 시험지 전용 XML 생성기 (배점, 선택지, 이미지)
│ ├── table_layout.py # 투명 테이블 2×2 레이아웃 로직
│ ├── section_generators.py # worksheet/exam section0.xml 조립
│ ├── hwpx_utils.py # 검증/패키징/메타데이터
│ ├── graph_generator.py # 그래프 PNG 생성 (matplotlib)
│ └── test_refactor.py # 리그레션 테스트 스크립트
├── templates/
│ ├── base/ # 2단 레이아웃 기본 템플릿
│ │ ├── mimetype, META-INF/*, version.xml, settings.xml, Preview/*
│ │ └── Contents/ (header.xml, section0.xml, content.hpf)
│ └── worksheet/ # (확장용) 오버레이 템플릿
├── examples/
│ ├── sample_middle_school.json # 중학교 문제 예시
│ ├── sample_high_school.json # 고등학교 문제 예시
│ ├── sample_exam_2020_march.json # 학력평가 형식 예시
│ ├── 01_middle_school_worksheet.sh # 빌드 예제
│ ├── 02_high_school_worksheet.sh # 빌드 예제
│ └── 03_exam_paper.sh # 학력평가 시험지 빌드 예제
└── references/ # 리그레션 테스트 레퍼런스 XML
build_math_hwpx.py (CLI + build 오케스트레이션)
├── hwpx_utils.py (validate_xml, pack_hwpx, validate_hwpx, update_metadata, _add_images_to_manifest)
├── section_generators.py (generate_worksheet_section_xml, generate_exam_section_xml)
│ ├── table_layout.py (_make_problem_cell_content, make_problem_table)
│ │ ├── xml_primitives.py (IDGen, STYLE, make_*_para, _make_equation_run)
│ │ └── exam_helpers.py (make_exam_problem_para, make_picture_para)
│ └── xml_primitives.py
└── graph_generator.py (변경 없음)
의존 방향: primitives → helpers → table → section → build (순환 없음)
반드시 아래 형식을 따를 것. exam_type, year, month, grade, points, choices 등 학력평가 필드를 포함해야 한다.
{
"exam_type": "학력평가",
"year": 2025,
"month": 3,
"grade": "중2",
"session": 2,
"subject_area": "수학",
"total_pages": 4,
"question_type_label": "5지선다형",
"problems": [
{
"text": "의 값은?",
"equation": "2x + 3 = 7",
"points": 4,
"choices": ["1", "2", "3", "4", "5"]
},
{
"text": "을 간단히 한 것은?",
"equation": "sqrt 12 + sqrt 27",
"points": 5,
"choices": ["$3 sqrt 3$", "$4 sqrt 3$", "$5 sqrt 3$", "$6 sqrt 3$", "$7 sqrt 3$"]
},
{
"text": "다음 중 옳은 것은?",
"points": 4,
"choices": ["$sqrt 4 = 2$", "$sqrt 9 = +- 3$", "$(-2)^2 = -4$", "$sqrt {16} = 4$", "$sqrt 25 = -5$"]
},
{
"section_label": "주관식",
"text": "을 인수분해하시오.",
"equation": "x^2 - 5x + 6",
"points": 5
}
]
}
source "$VENV"
# 학력평가 형식 (기본값 — exam_type 지정 불필요)
python3 "$SKILL_DIR/scripts/build_math_hwpx.py" \
--problems problems.json \
--creator "수학교사" \
--output exam.hwpx
# 단순 worksheet 형식 (명시적 지정 필요)
python3 "$SKILL_DIR/scripts/build_math_hwpx.py" \
--problems problems.json \
--exam-type worksheet \
--title "중2 일차방정식" \
--output worksheet.hwpx
python3 "$HWPX_SKILL_DIR/scripts/validate.py" worksheet.hwpx
중요: 모든 문제 JSON은 반드시 학력평가 형식으로 작성한다. title/subtitle/info는 사용하지 않는다.
{
"exam_type": "학력평가",
"year": 2025,
"month": 3,
"grade": "중3",
"session": 2,
"subject_area": "수학",
"total_pages": 4,
"question_type_label": "5지선다형",
"problems": [
{
"text": "문제 본문 (수식 앞뒤 텍스트)",
"equation": "한컴 수식 스크립트",
"points": 4,
"choices": ["선택지1", "$수식선택지$", "선택지3", "선택지4", "선택지5"]
},
{
"text": "소문제가 있는 경우",
"equation": "메인 수식",
"points": 5,
"sub_problems": [
{"equation": "소문제 수식"}
],
"choices": ["1", "2", "3", "4", "5"]
},
{
"section_label": "주관식",
"text": "을 인수분해하시오.",
"equation": "x^2 - 5x + 6",
"points": 5
}
]
}
| 필드 | 필수 | 설명 |
|---|---|---|
exam_type | O | 반드시 "학력평가" 지정 |
year | O | 학년도 (예: 2025) |
month | O | 시행 월 (예: 3, 6, 9, 11) |
grade | O | 학년 (예: "중1", "중2", "중3", "고1", "고2", "고3") |
session | X | 교시 (기본: 2) |
subject_area | X | 과목 영역 (기본: "수학") |
total_pages | X | 총 페이지 (기본: 문제 수에 따라 자동) |
question_type_label | X | 문항유형 라벨 (기본: "5지선다형") |
problems | O | 문제 배열 |
problems[].text | X | 문제 텍스트 |
problems[].equation | X | 독립 수식 (display 모드) |
problems[].points | O | 배점 (정수, [N점] 형태로 표시) |
problems[].choices | X | 객관식 5지선다 ($...$로 감싸면 수식) |
problems[].sub_problems | X | 소문제 배열 [{text, equation}] |
problems[].section_label | X | 섹션 구분 라벨 (예: "주관식") |
| 규칙 | 설명 |
|---|---|
{ } | 그룹화 (여러 항을 하나로) |
~ | 공백 (1em) |
` | 1/4 공백 |
# | 줄바꿈 (수식 내) |
& | 열 정렬 (행렬, 연립방정식) |
"..." | 텍스트 모드 (수식 해석 비활성) |
| 수식 | 스크립트 | 예시 |
|---|---|---|
| 분수 | a over b | {x+1} over {x-1} |
| 제곱근 | sqrt {x} | sqrt {b^2 - 4ac} |
| n제곱근 | root n of {x} | root 3 of {27} |
| 수식 | 스크립트 |
|---|---|
| 위첨자 | x^2 또는 x SUP 2 |
| 아래첨자 | x_i 또는 x SUB i |
| 둘 다 | x_i ^2 |
| 수식 | 스크립트 |
|---|---|
| 정적분 | int _{a} ^{b} f(x) dx |
| 이중적분 | dint f(x,y) dxdy |
| 삼중적분 | tint f dxdydz |
| 급수(시그마) | sum _{k=1} ^{n} a_k |
| 곱(파이) | prod _{i=1} ^{n} x_i |
| 수식 | 스크립트 |
|---|---|
| 극한 | lim _{x -> 0} f(x) |
| 대문자 | Lim _{n -> inf} |
| 수식 | 스크립트 |
|---|---|
| 자동 크기 소괄호 | left ( {a over b} right ) |
| 자동 크기 대괄호 | left [ x right ] |
| 자동 크기 중괄호 | left lbrace x right rbrace |
| 절댓값 | `left |
| 수식 | 스크립트 |
|---|---|
| 기본 행렬 | matrix {a & b # c & d} |
| 소괄호 행렬 | pmatrix {a & b # c & d} |
| 대괄호 행렬 | bmatrix {1 & 0 # 0 & 1} |
| 행렬식 | dmatrix {a & b # c & d} |
| 수식 | 스크립트 |
|---|---|
| 연립방정식 | cases {2x+y=5 # 3x-2y=4} |
| 정렬 수식 | eqalign {a &= b # c &= d} |
| 수직 스택 | pile {a # b # c} |
| 장식 | 스크립트 |
|---|---|
| 모자(^) | hat a |
| 물결 | tilde a |
| 벡터 화살표 | vec v |
| 윗줄 | bar x |
| 밑줄 | under x |
| 점 1개 | dot a |
| 점 2개 | ddot a |
소문자: alpha, beta, gamma, delta, epsilon, zeta, eta, theta, iota, kappa, lambda, mu, nu, xi, pi, rho, sigma, tau, upsilon, phi, chi, psi, omega
대문자: ALPHA, BETA, GAMMA, DELTA 등
변형: vartheta, varphi, varepsilon
| 기호 | 스크립트 |
|---|---|
| 무한대 | inf |
| 편미분 | partial |
| 나블라 | nabla |
| 고로 | therefore |
| 왜냐하면 | because |
| 모든 | forall |
| 존재 | exist |
| ± | +- 또는 pm |
| ≠ | ne |
| ≤ | le 또는 leq |
| ≥ | ge 또는 geq |
| ≈ | approx |
| ≡ | equiv |
| ⊂ | subset |
| ∈ | in |
| → | -> 또는 rarrow |
| ← | larrow |
| ↔ | <-> 또는 lrarrow |
| ··· | cdots |
| 스타일 | 명령 |
|---|---|
| 로만(정체) | rm |
| 이탤릭 | it |
| 볼드 | bold |
| 볼드 로만 | rmbold |
sin, cos, tan, cot, sec, csc, arcsin, arccos, arctan, log, ln, lg, exp, det, mod, gcd, max, min, sinh, cosh, tanh
section0.xml에서 수식은 다음과 같이 삽입된다:
<hp:p id="고유ID" paraPrIDRef="22" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
<hp:run charPrIDRef="9">
<hp:equation id="고유ID" type="0" textColor="#000000"
baseUnit="1000" letterSpacing="0" lineThickness="100">
<hp:sz width="0" height="0" widthRelTo="ABS" heightRelTo="ABS"/>
<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0"
allowOverlap="0" holdAnchorAndSO="0" rgroupWithPrevCtrl="0"
vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT"
vertOffset="0" horzOffset="0"/>
<hp:script>x = {-b +- sqrt {b^2 - 4ac}} over {2a}</hp:script>
</hp:equation>
</hp:run>
</hp:p>
| 속성 | 값 | 설명 |
|---|---|---|
baseUnit | 1000 | 기본 10pt (100 HWPUNIT = 1pt) |
textColor | #000000 | 수식 색상 |
lineThickness | 100 | 분수선/루트선 두께 |
treatAsChar | 1 | 인라인 수식 (텍스트와 같은 줄) |
한 문단에 텍스트와 수식을 함께 배치:
<hp:p id="..." paraPrIDRef="21" ...>
<hp:run charPrIDRef="9"><hp:t>방정식 </hp:t></hp:run>
<hp:run charPrIDRef="9">
<hp:equation ...>
<hp:script>2x + 3 = 7</hp:script>
</hp:equation>
</hp:run>
<hp:run charPrIDRef="9"><hp:t> 의 해를 구하라.</hp:t></hp:run>
</hp:p>
section0.xml 첫 문단의 hp:colPr으로 설정:
<hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="2" sameSz="1" sameGap="2268"/>
| 속성 | 값 | 설명 |
|---|---|---|
type | NEWSPAPER | 좌→우 순서로 채움 |
colCount | 2 | 2단 |
sameSz | 1 | 동일 너비 |
sameGap | 2268 | 단간격 8mm |
<hp:pagePr landscape="WIDELY" width="59528" height="84186" gutterType="LEFT_ONLY">
<hp:margin header="4252" footer="4252" gutter="0"
left="5668" right="5668" top="4252" bottom="4252"/>
</hp:pagePr>
| ID | 설명 | 크기 | 굵기 | 기울임 | 비고 |
|---|---|---|---|---|---|
| 0 | 기본 본문 | 10pt | 보통 | - | 함초롬바탕 |
| 1 | 돋움 기본 | 10pt | 보통 | - | 함초롬돋움 |
| 2~6 | Skeleton 호환 | 다양 | 보통 | - | |
| 7 | 문제지 제목 | 16pt | 볼드 | - | worksheet용 |
| 8 | 문제 번호 | 11pt | 볼드 | - | worksheet용 |
| 9 | 문제 본문 | 10pt | 보통 | - | 공통 |
| 10 | 단원명/소제목 | 12pt | 볼드 | - | 공통 |
| 11 | 선택지/보기 | 9pt | 보통 | - | 공통 |
| 12 | 시험 제목줄 | 10pt | 보통 | - | exam용 |
| 13 | 과목 영역 | 18pt | 볼드 | - | exam용 "수학 영역" |
| 14 | 교시 라벨 | 10pt | 보통 | - | exam용 "제 2 교시" |
| 15 | 시험 문제번호 | 10pt | 볼드 | 이탤릭 | exam용 |
| 16 | 배점 | 9pt | 보통 | - | exam용 "[2점]" |
| 17 | 페이지 번호 | 9pt | 보통 | - | exam용 |
| ID | 정렬 | 줄간격 | 용도 |
|---|---|---|---|
| 0 | JUSTIFY | 160% | 기본 본문 |
| 1~19 | 다양 | 다양 | Skeleton 호환 |
| 20 | CENTER | 160% | 제목 (worksheet) |
| 21 | LEFT | 150% | 문제 본문 (공통) |
| 22 | LEFT | 140% | 수식 표시 (공통) |
| 23 | LEFT | 140% | 선택지/보기 세로 (공통) |
| 24 | CENTER | 130% | 시험 제목줄 (exam) |
| 25 | LEFT | 130% | 교시+과목 라인 (exam) |
| 26 | CENTER | 150% | 과목 영역 대제목 (exam) |
| 27 | LEFT | 140% | 선택지 가로 (exam, tabPr=3) |
| 28 | LEFT | 140% | 문항유형 라벨 (exam, 테두리) |
| 29 | CENTER | 130% | 페이지 하단 번호 (exam) |
| ID | 설명 |
|---|---|
| 0 | 기본 (탭 없음) |
| 1 | 좌측 자동탭 |
| 2 | 우측 자동탭 |
| 3 | 가로 선택지용 5개 탭스톱 (4590, 9180, 13770, 18360) |
| ID | 설명 |
|---|---|
| 1 | 없음 (페이지 보더) |
| 2 | 없음 + 투명배경 |
| 3 | SOLID 4면 (표용) |
| 4 | SOLID + #E8E8E8 배경 (헤더 셀) |
| 5 | 하단 DASH 선 (문제 구분) |
| 6 | SOLID 테두리 (교시/문항유형 라벨) |
| 7 | 두꺼운 SOLID 테두리 (페이지번호 박스) |
# 일차방정식
2x + 3 = 7
# 분수 방정식
{2x+1} over 3 = {x-2} over 5
# 연립방정식
cases {2x + y = 5 # 3x - 2y = 4}
# 제곱근
sqrt 12 + sqrt 27 - sqrt 48
# 부등식
3x - 5 > 2x + 1
# 이차방정식
x^2 - 5x + 6 = 0
# 피타고라스
a^2 + b^2 = c^2
# 일차함수
y = ax + b
# 지수법칙
a^m times a^n = a^{m+n}
# 로그
log _a xy = log _a x + log _a y
# 절댓값
left | x - 3 right | < 5
# 이차함수 꼭짓점
y = a(x - p)^2 + q
# 근의 공식
x = {-b +- sqrt {b^2 - 4ac}} over {2a}
# 극한
lim _{x -> 0} {sin x} over x = 1
# 미분 정의
f'(x) = lim _{h -> 0} {f(x+h) - f(x)} over h
# 정적분
int _{0} ^{pi} sin x dx = 2
# 급수
sum _{k=1} ^{n} k = {n(n+1)} over 2
# 등차수열
a_n = a_1 + (n-1)d
# 조합
{_n}C{_r} = {n!} over {r!(n-r)!}
# 이항정리
(a+b)^n = sum _{k=0} ^{n} {_n}C{_k} a^{n-k} b^k
# 확률
P(A cup B) = P(A) + P(B) - P(A cap B)
# 정규분포
f(x) = {1} over {sigma sqrt {2 pi}} e^{-{(x- mu)^2} over {2 sigma ^2}}
# 도함수
{d} over {dx} x^n = n x^{n-1}
# 합성함수 미분
{dy} over {dx} = {dy} over {du} times {du} over {dx}
# 부분적분
int u dv = uv - int v du
# 치환적분
int f(g(x)) g'(x) dx = int f(u) du
# 테일러 급수
e^x = sum _{n=0} ^{inf} {x^n} over {n!}
# 벡터 내적
vec a cdot vec b = left | vec a right | left | vec b right | cos theta
# 원의 방정식
(x-a)^2 + (y-b)^2 = r^2
# 타원
{x^2} over {a^2} + {y^2} over {b^2} = 1
# 쌍곡선
{x^2} over {a^2} - {y^2} over {b^2} = 1
# 행렬 곱
pmatrix {a & b # c & d} pmatrix {x # y} = pmatrix {ax+by # cx+dy}
graph_generator.py는 함수 그래프(polynomial, trig 등) 외에 5가지 기하 도형 타입을 지원한다.
문제 JSON의 graph 필드에 아래 스펙을 지정하면 도형 PNG가 자동 생성된다.
꼭짓점 좌표 3개로 정의. 내각 표시, 각도 호, 변 길이 레이블, 보조선(중선/수선/이등분선), 외접원/내접원 지원.
{
"type": "triangle",
"vertices": [[0, 0], [6, 0], [2, 5]],
"labels": {"A": [2, 5], "B": [0, 0], "C": [6, 0]},
"show_angles": [true, true, true],
"angle_labels": ["80°", "50°", "50°"],
"side_labels": {"AB": "5", "BC": "6", "AC": "5"},
"equal_marks": {"AB": 1, "AC": 1},
"show_circumcircle": false,
"show_incircle": false,
"auxiliary_lines": [{"type": "bisector", "vertex": "B"}]
}
| 필드 | 필수 | 설명 |
|---|---|---|
vertices | O | 꼭짓점 좌표 3개 [[x,y], [x,y], [x,y]] |
labels | X | 꼭짓점 이름→좌표 매핑 |
show_angles | X | 각 꼭짓점별 각도 호 표시 여부 [bool, bool, bool] |
angle_labels | X | 각도 라벨 (예: ["80°", "50°", "50°"]) |
side_labels | X | 변 이름→길이 라벨 (예: {"AB": "5"}) |
equal_marks | X | 변 이름→등분 표시 개수 (예: {"AB": 1, "AC": 1}) |
show_circumcircle | X | 외접원 표시 |
show_incircle | X | 내접원 표시 |
auxiliary_lines | X | 보조선 배열. type: "median", "altitude", "bisector" |
중심, 반지름, 원 위의 점, 현, 접선, 호 강조, 중심각/원주각 표시.
{
"type": "circle",
"center": [0, 0],
"radius": 3,
"show_center": true,
"points_on_circle": [
{"angle_deg": 30, "label": "A"},
{"angle_deg": 150, "label": "B"}
],
"chords": [["A", "B"]],
"tangent_at": ["A"],
"arc_highlight": {"from": "A", "to": "B", "color": "gray"},
"central_angle": true,
"inscribed_angle": {"vertex": "C", "arc": ["A", "B"]}
}
| 필드 | 필수 | 설명 |
|---|---|---|
center | X | 중심 좌표 (기본: [0,0]) |
radius | X | 반지름 (기본: 3) |
show_center | X | 중심점 O 표시 |
points_on_circle | X | 원 위의 점 배열 {angle_deg, label} |
chords | X | 현 [["A","B"]] |
tangent_at | X | 접선 그릴 점 이름 |
arc_highlight | X | 호 강조 {from, to, color} |
central_angle | X | 중심각 선분 표시 |
inscribed_angle | X | 원주각 {vertex, arc: [A, B]} |
4개 꼭짓점 좌표로 정의. 대각선, 평행 표시, 등분 표시, 직각 표시 지원.
{
"type": "quadrilateral",
"kind": "parallelogram",
"vertices": [[0, 0], [5, 0], [7, 3], [2, 3]],
"labels": {"A": [0, 0], "B": [5, 0], "C": [7, 3], "D": [2, 3]},
"show_diagonals": true,
"diagonal_intersection_label": "O",
"parallel_marks": {"AB_DC": 1, "AD_BC": 2},
"equal_marks": {"AB": 1, "DC": 1},
"show_right_angles": [],
"side_labels": {"AB": "10", "BC": "6"}
}
| 필드 | 필수 | 설명 |
|---|---|---|
vertices | O | 꼭짓점 좌표 4개 |
kind | X | 참고용 종류명 (parallelogram, rectangle 등) |
labels | X | 꼭짓점 이름→좌표 |
show_diagonals | X | 대각선 점선 표시 |
diagonal_intersection_label | X | 대각선 교점 라벨 |
parallel_marks | X | 평행 표시 {"AB_DC": 1} (AB∥DC, 화살표 1개) |
equal_marks | X | 등분 표시 |
show_right_angles | X | 직각 표시할 꼭짓점 이름 배열 |
side_labels | X | 변 길이 라벨 |
좌표축 + 선분/다각형/점/직선의 조합. 일차함수와 도형의 넓이 등.
{
"type": "coordinate",
"xlim": [-1, 7],
"ylim": [-1, 7],
"segments": [[[0, 6], [2, 0]], [[2, 0], [6, 0]], [[0, 6], [6, 0]]],
"points": [
{"pos": [0, 6], "label": "(0, 6)"},
{"pos": [2, 0], "label": "(2, 0)"}
],
"fill_polygon": [[0, 6], [2, 0], [6, 0]],
"shade_alpha": 0.15,
"lines": [{"slope": -3, "intercept": 6, "style": "k-"}],
"circles": [{"center": [3, 3], "radius": 2}]
}
| 필드 | 필수 | 설명 |
|---|---|---|
xlim, ylim | X | 좌표축 범위 |
segments | X | 선분 배열 [[[x1,y1],[x2,y2]], ...] |
points | X | 점 배열 {pos, label} |
fill_polygon | X | 채울 다각형 꼭짓점 좌표 |
shade_alpha | X | 채우기 투명도 (기본: 0.15) |
lines | X | 직선 {slope, intercept, style} |
circles | X | 원 {center, radius} |
2D oblique projection으로 시험지 스타일 입체도형 렌더링. 점선으로 뒷면 모서리 표현.
{
"type": "solid3d",
"kind": "cylinder",
"params": {"radius": 2, "height": 4},
"labels": {"r": "2", "h": "4"},
"show_hidden": true
}
지원 kind: cylinder, cone, sphere, rectangular_prism, triangular_prism, pyramid
| 필드 | 필수 | 설명 |
|---|---|---|
kind | X | 입체 종류 (기본: "cylinder") |
params | X | 종류별 파라미터 (radius, height, width, depth, base 등) |
labels | X | 치수 라벨 (예: {"r": "2", "h": "4"}) |
show_hidden | X | 뒷면 모서리 점선 표시 (기본: true) |
kind별 params:
cylinder: {radius, height}cone: {radius, height}sphere: {radius}rectangular_prism: {width, height, depth}triangular_prism: {base, height, depth}pyramid: {base, height, depth}JSON 대신 직접 section0.xml을 작성하여 더 세밀한 제어 가능:
SECTION=$(mktemp /tmp/section0_XXXX.xml)
cat > "$SECTION" << 'XMLEOF'
<?xml version='1.0' encoding='UTF-8'?>
<hs:sec xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph"
xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section"
xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core">
<!-- base/section0.xml의 첫 문단(secPr+colPr) 그대로 복사 -->
<!-- ... -->
<!-- 제목 -->
<hp:p id="1000000002" paraPrIDRef="20" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
<hp:run charPrIDRef="7"><hp:t>수학 문제지</hp:t></hp:run>
</hp:p>
<!-- 수식 문단 -->
<hp:p id="1000000003" paraPrIDRef="22" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
<hp:run charPrIDRef="9">
<hp:equation id="1000000099" type="0" textColor="#000000"
baseUnit="1000" letterSpacing="0" lineThickness="100">
<hp:sz width="0" height="0" widthRelTo="ABS" heightRelTo="ABS"/>
<hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0"
allowOverlap="0" holdAnchorAndSO="0" rgroupWithPrevCtrl="0"
vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT"
vertOffset="0" horzOffset="0"/>
<hp:script>x = {-b +- sqrt {b^2 - 4ac}} over {2a}</hp:script>
</hp:equation>
</hp:run>
</hp:p>
</hs:sec>
XMLEOF
python3 "$SKILL_DIR/scripts/build_math_hwpx.py" --section "$SECTION" --output result.hwpx
rm -f "$SECTION"
math-hwpx는 hwpx 스킬의 도구를 재사용할 수 있다:
| 도구 | 경로 | 용도 |
|---|---|---|
| validate.py | $HWPX_SKILL_DIR/scripts/validate.py | HWPX 구조 검증 |
| unpack.py | $HWPX_SKILL_DIR/scripts/office/unpack.py | HWPX → 디렉토리 |
| pack.py | $HWPX_SKILL_DIR/scripts/office/pack.py | 디렉토리 → HWPX |
| text_extract.py | $HWPX_SKILL_DIR/scripts/text_extract.py | 텍스트 추출 |
# 생성된 문제지 구조 확인
python3 "$HWPX_SKILL_DIR/scripts/office/unpack.py" worksheet.hwpx ./unpacked/
# → ./unpacked/Contents/section0.xml 편집 후
python3 "$HWPX_SKILL_DIR/scripts/office/pack.py" ./unpacked/ edited.hwpx
| 값 | HWPUNIT | 의미 |
|---|---|---|
| 1pt | 100 | 기본 단위 |
| 10pt | 1000 | 기본 글자크기 |
| 1mm | 283.5 | 밀리미터 |
| A4 폭 | 59528 | 210mm |
| A4 높이 | 84186 | 297mm |
| 문제지 좌우여백 | 5668 | 20mm |
| 문제지 본문폭 | 48192 | 170mm |
| 단간격 | 2268 | 8mm |
| 단 너비 | 22962 | 약 81mm |
"exam_type": "학력평가"를 포함. worksheet 형식은 사용 금지"points" 필드 필수. 객관식은 "choices" 5개, 주관식은 "section_label": "주관식" 포함<hp:script> 안에: LaTeX가 아닌 한컴 수식 문법 사용.venv/bin/activate (lxml 패키지 필요)baseUnit="1000" = 10pt (본문과 동일), 필요시 1200(12pt) 등 조절$...$로 감싸면 수식으로 처리. 반드시 한컴 수식 스크립트 문법 사용 ($3 sqrt 2$ ✓, $3\sqrt{2}$ ✗ LaTeX 안됨). 예: $30 pi$, $sqrt 3$, ${x^2} over 2$학력평가 형식이 기본값이다. 모든 문제 JSON은 반드시 "exam_type": "학력평가"를 포함하고, 각 문제에 "points" 배점을 지정해야 한다. 객관식은 5개 "choices"를 포함하고, 주관식은 "section_label": "주관식"을 지정한다.
{
"exam_type": "학력평가",
"year": 2020,
"month": 3,
"grade": "고1",
"session": 2,
"subject_area": "수학",
"total_pages": 12,
"question_type_label": "5지선다형",
"problems": [
{
"text": "의 값은?",
"equation": "-{7} over {2} times (-3) + 4 times left | -{5} over {2} right |",
"points": 2,
"choices": ["-1", "$-{1} over {2}$", "0", "${1} over {2}$", "1"]
},
{
"section_label": "주관식",
"text": "의 값을 구하시오.",
"equation": "2x + 3 = 7",
"points": 4
}
]
}
| 필드 | 필수 | 설명 |
|---|---|---|
exam_type | O | "학력평가", "수능", "exam" 중 하나 |
year | X | 학년도 (예: 2020) |
month | X | 시행 월 (예: 3) |
grade | X | 학년 (예: "고1") |
session | X | 교시 (기본: 2) |
subject_area | X | 과목 영역 (기본: "수학") |
total_pages | X | 총 페이지 수 (기본: 12) |
question_type_label | X | 문항유형 라벨 (기본: "5지선다형") |
problems[].points | X | 배점 (정수, [2점] 형태로 표시) |
problems[].section_label | X | 섹션 구분 라벨 (예: "주관식") |
┌─────────────────────────────────────────┐
│ 2020학년도 3월 고1 전국연합학력평가 문제지 │ ← 1단 (전체너비)
│ │
│ 제 2 교시 수학 영역 │ ← 1단 (전체너비)
├────────────────────┬────────────────────┤
│ ┌──────────┐ │ │
│ │ 5지선다형 │ │ │ ← 2단 시작
│ └──────────┘ │ │
│ │ │
│ 1. 문제... [2점] │ 3. 문제... [2점] │
│ ① a ② b ③ c ④ d ⑤ e│ ① a ② b ③ c ④ d│ ← 가로 선택지
│ │ │
│ 2. 문제... [2점] │ 4. 문제... [3점] │
│ ① a ② b ③ c ④ d ⑤ e│ ① a ② b ③ c ④ d│
├────────────────────┴────────────────────┤
│ 1 / 12 │ ← 1단 (전체너비)
└─────────────────────────────────────────┘
exam 형식에서는 hp:colPr을 이용해 섹션 내 단 수를 전환한다:
colCount="1"로 시작 (헤더 영역 전체너비)hp:colPr colCount="2"로 전환 (본문 2단)hp:colPr colCount="1"로 전환 (하단 페이지 번호)tabPr ID 3에 정의된 5개 탭스톱을 이용해 선택지를 한 줄에 배치:
① -1 ② -1/2 ③ 0 ④ 1/2 ⑤ 1
각 선택지 사이에 <hp:tab/>을 삽입하여 균등 간격으로 배치한다.
# 학력평가 형식 (기본값)
python3 "$SKILL_DIR/scripts/build_math_hwpx.py" \
--problems problems.json \
--creator "교육청" \
--output exam.hwpx