Google Docs 에서 삽입 → 기호 → 수식 으로 식을 하나 만들면, 실제로 저장되는 것은 무엇일까요.
화면에 보이는 모양 말고, 문서를 프로그램으로 읽었을 때 돌아오는 값 이야기입니다. 이게 중요한 이유는 그 문서를 다른 데로 옮기려는 순간인데, Docs 를 읽는 두 가지 방법이 완전히 다른 답을 주고 그중 하나는 조용히 쓸모가 없기 때문입니다.
Apps Script 로 일회용 프로브를 만들어 손으로 만든 수식이 든 문서를 훑고, 본문을 재귀로 걸으면서 element 종류와 getCode() · getText() 가 돌려주는 값을 전부 찍었습니다. 이 글은 그 로그와 거기서 해독한 내용입니다. 문서로 정리된 곳을 찾지 못해서 API 가 실제로 무엇을 돌려주는지 직접 본 결과이고, 따라서 전부 문서가 아니라 측정입니다.
1. 텍스트 계층은 막다른 길이다
실패부터 봅니다. 나머지 내용이 존재하는 이유가 여기 있습니다.
문단을 읽는 가장 평범한 방법은 텍스트를 달라고 하는 것입니다. 수식 두 개에 대해 그 결과가 이렇습니다.
| 화면에 보이는 것 | 텍스트 계층이 돌려주는 것 |
|---|---|
| $\frac{1}{2}$ | 12 |
| $\alpha + \beta$ | " + " |
둘 다 복구 불가능한데, 방식이 다릅니다.
분수는 구분자 없이 뭉개집니다. 12 가 돌아오고, 저자가 2분의 1 을 쓴 건지 십이를 쓴 건지 알 방법이 없습니다. 내용이 길어지면 더 나빠집니다 — $\frac{a+b}{c}$ 는 a+bc 로 돌아옵니다. 경계가 문자열에 아예 없으므로 후처리로 되살릴 수 없습니다.
그리스 문자는 더합니다. 아예 나타나지 않습니다. 텍스트 계층이 돌려주는 건 공백, 더하기, 공백 — 기호는 흔적조차 남기지 않습니다.
알아 둘 만한 함정
여기서 실질적인 문제가 하나 생깁니다. 네이티브 수식이 들어 있는 Docs 문서에 텍스트 기반 LaTeX 변환기를 쓰는 사람이면 반드시 밟게 되는 자리입니다.
$...$ 와 $$...$$ 를 찾는 변환기가 있다고 합시다. 네이티브 수식이 안 잡히길래 상식적인 대응을 합니다 — $$ 로 감쌉니다. 이제 스캐너는 $$12$$ 를 찾아 기분 좋게 변환하고, 분수가 있던 자리에 12 라고 적힌 이미지가 들어앉습니다.
에러는 없습니다. 결과가 그럴듯한 숫자라서 틀렸다고 티도 안 냅니다. 문서 변환에서 진짜 걱정할 실패는 이쪽입니다 — 뻗는 게 아니라 조용히 바꿔치기하는 것.
이 경우 원래 수식 개체가 어떻게 되는지는 확인하지 않았습니다. 밑에 남는지 대체되는지는 별개 질문이고 측정하지 않았습니다.
2. 구조 계층
이제 되는 쪽입니다.
Docs 는 수식을 텍스트로 저장하지 않습니다. 작은 트리로 저장하고, Apps Script 가 그 트리를 그대로 넘겨줍니다. element 타입은 EQUATION 이고 그 밑에 세 종류의 노드가 있습니다.
EQUATION_FUNCTION— 분수나 근호 같은 구성물. 코드를 들고 있습니다.EQUATION_SYMBOL— 기호 하나. 역시 코드를 들고 있습니다.TEXT— 글자 그대로
여기에 구분자 노드 EQUATION_FUNCTION_ARGUMENT_SEPARATOR 가 하나 더 있는데, 이게 사실상 열쇠라 곧 다룹니다.
실제 덤프입니다.
분수
1
2
3
4
5
6
EQUATION text="12a+b=2"
EQUATION_FUNCTION code="\frac" text="12"
TEXT text="1"
EQUATION_FUNCTION_ARGUMENT_SEPARATOR
TEXT text="2"
TEXT text="a+b=2"
바깥 노드의 text 속성을 보세요. 12a+b=2 입니다. 1절의 그 못 쓰는 문자열이고, 이제 어디서 나온 건지 정확히 보입니다 — 트리를 구조를 버리고 납작하게 편 결과입니다. 구조는 바로 한 층 아래 있습니다.
그리스 문자
1
2
3
4
5
6
EQUATION text=" + "
TEXT text=" "
EQUATION_SYMBOL code="\alpha"
TEXT text="+"
EQUATION_SYMBOL code="\beta"
TEXT text=" "
텍스트 계층에서 안 보이던 기호가 EQUATION_SYMBOL 노드로 그대로 있고, 코드가 이미 LaTeX 입니다.
근호 · 위첨자 · 아래첨자
1
2
3
4
5
6
7
8
9
EQUATION_FUNCTION code="\sqrt" text="x"
TEXT text="x"
EQUATION_FUNCTION code="\superscript" text="x2"
TEXT text="x"
EQUATION_FUNCTION_ARGUMENT_SEPARATOR
TEXT text="2"
EQUATION_FUNCTION code="\subscript" text="x1"
중첩된 분수
1
2
3
4
5
6
7
EQUATION_FUNCTION code="\frac" text="123"
EQUATION_FUNCTION code="\frac" text="12"
TEXT text="1"
EQUATION_FUNCTION_ARGUMENT_SEPARATOR
TEXT text="2"
EQUATION_FUNCTION_ARGUMENT_SEPARATOR
TEXT text="3"
중첩은 기대한 대로 됩니다. 함수가 다른 함수의 인자가 될 수 있습니다. 납작하게 편 text 는 123 이라 무엇이든 될 수 있지만, 트리는 $\frac{\frac{1}{2}}{3}$ 이라고 분명히 말합니다.
합과 적분
1
2
3
4
5
6
7
8
9
10
11
12
13
EQUATION text="x=1nx2"
EQUATION_FUNCTION code="\sumab" text="x=1n"
TEXT text="x=1"
EQUATION_FUNCTION_ARGUMENT_SEPARATOR
TEXT text="n"
EQUATION_FUNCTION code="\superscript" text="x2" ← 자식이 아니라 형제
EQUATION text="abxdx"
EQUATION_FUNCTION code="\intab" text="ab"
TEXT text="a"
EQUATION_FUNCTION_ARGUMENT_SEPARATOR
TEXT text="b"
TEXT text="xdx" ← 자식이 아니라 형제
마지막 두 덤프에 정말 뜻밖인 구조가 하나 들어 있어서, 아래에 규칙으로 따로 뺐습니다.
3. 전체를 해독하는 규칙 세 가지
규칙 1 — 코드가 LaTeX 모양이다. getCode() 는 \frac, \sqrt, \alpha, \beta 를 돌려줍니다. 대응표를 역설계해야 하는 불투명한 내부 식별자가 아닙니다. 얼마나 번역이 필요한지는 노드 종류에 따라 갈립니다 — 시험한 기호는 전부 그대로 통과했고, 함수는 14 개 중 9 개가 다시 써야 합니다. 5절과 6절에 두 목록을 전부 실었습니다.
규칙 2 — 구분자가 인자 경계를 표시한다. EQUATION_FUNCTION_ARGUMENT_SEPARATOR 가 정확히 텍스트 계층이 파괴하는 그 정보입니다. \sqrt 처럼 인자가 하나인 함수는 자식 중에 구분자가 없습니다. \frac 처럼 둘인 함수는 하나 있습니다. 자식을 구분자로 쪼개면 그게 인자입니다.
규칙 3 — 큰 연산자는 극한이 인자이고 본체는 형제다. 이게 주의할 자리입니다. 위 합 덤프에서 \sumab 의 자식 묶음은 정확히 둘 — x=1 과 n — 이고 이건 아래끝과 위끝입니다. 정작 더해지는 대상인 $x^2$ 는 안에 없습니다. 다음 형제입니다. 적분도 같습니다. \intab 은 a 와 b 를 들고 있고 xdx 가 형제로 따라옵니다.
본체가 자식이라고 가정하면 $\sum_{x=1}^{n}$ 뒤에 아무것도 없는 결과가 나오고 본체를 잃습니다.
4. 틀린 것으로 드러난 가설
추론이 아니라 시험으로만 잡히는 종류라서 남겨 둡니다.
\sumab 과 \intab 을 보면 ab 접미사가 극한 두 칸이 다 찼다 는 뜻으로 읽힙니다. 그렇다면 다른 조합을 위한 \suma, \sumb, 그냥 \sum 도 있어야 합니다. 처음 추측이 그랬습니다.
그래서 극한을 일부러 비운 합과 적분을 만들어 찍어 봤습니다.
| 입력 | 돌아온 코드 | 자식 |
|---|---|---|
| 합, 극한 없음 | \sumab | 구분자만 |
| 합, 아래끝만 | \sumab | TEXT, 구분자 |
| 합, 위끝만 | \sumab | 구분자, TEXT |
| 적분, 극한 없음 | \intab | 구분자만 |
추측이 틀렸습니다. \sumab 은 고정된 이름입니다. 칸은 항상 둘이고, 빈 칸은 그냥 자식이 없을 뿐입니다. \suma 나 \sumb 는 없습니다.
가설보다 규칙이 더 단순했습니다. 이름 하나, 모양 하나, 빈 칸은 출력에서 빠짐.
5. 함수 표 전체
함수 14 개, 전부 프로브로 확인했습니다. A1 과 A2 는 첫 번째 · 두 번째 인자 묶음입니다.
| Docs 코드 | 인자 | LaTeX | 번역 필요 |
|---|---|---|---|
\frac | 2 | \frac{A1}{A2} | 아니오 |
\sqrt | 1 | \sqrt{A1} | 아니오 |
\rootof | 2 | \sqrt[A1]{A2} | 예 |
\superscript | 2 | {A1}^{A2} | 예 |
\subscript | 2 | {A1}_{A2} | 예 |
\sumab | 2 | \sum_{A1}^{A2} | 예 |
\intab | 2 | \int_{A1}^{A2} | 예 |
\prodab | 2 | \prod_{A1}^{A2} | 예 |
\ointab | 2 | \oint_{A1}^{A2} | 예 |
\limab | 2 | \lim_{A1 \to A2} | 예 |
\overline | 1 | \overline{A1} | 아니오 |
\vec | 1 | \vec{A1} | 아니오 |
\widehat | 1 | \widehat{A1} | 아니오 |
\abs | 1 | 세로 막대 한 쌍 — 아래 참고 | 예 |
표에 붙일 메모가 셋입니다.
\limab 만 진짜 특수 케이스입니다. 나머지 두 칸짜리 연산자는 인자를 아래첨자와 위첨자로 보내는데, 극한은 아래첨자 하나 안에 A1 \to A2 로 들어갑니다. $\lim_{x \to 0}$ 이 위아래 경계가 아니라 한 덩어리로 쓰이기 때문입니다.
\sumab 계열에서 빈 칸은 _{} 나 ^{} 를 비운 채로 내보내는 게 아니라 묶음 자체를 빼야 합니다. 극한 없는 합은 \sum_{}^{} 가 아니라 \sum 으로 나와야 합니다.
절댓값은 출력에 세로 막대가 들어가서 마크다운 표에 깔끔히 못 넣는 한 줄입니다. 풀어 쓰면 이렇습니다.
1
\left|A1\right|
막대 하나입니다. 둘이 아닙니다. 막대 둘은 노름이라 다른 구성물이고, 이 함수가 만드는 것이 아닙니다.
하나 덧붙입니다 (2026-09-22 측정). 툴바의 Math operations 메뉴에는 항목이 14 개가 아니라 20 개 있습니다. 위 표의 14 개 중 13 개가 거기 있고 (\vec 은 타이핑으로만 됩니다), 나머지 7 개 — \subsuperscript, \bigcapab, \bigcupab, \coprodab, \rbracelr, \sbracelr, \bracelr — 도 EQUATION_FUNCTION 노드이고 이름을 쳐서도 만들어집니다. 그래서 함수는 14 개가 아니라 모두 21 개입니다. 위 표는 처음 프로브 그대로이고 늘리지 않았습니다.
6. 기호는 표가 아예 필요 없다
여기가 반가운 쪽이었습니다. 팔레트에서 기호 93 개 를 찍었는데, getCode() 값이 전부 유효한 평범한 LaTeX 명령으로 돌아왔습니다. 기호는 만들 대응표가 없습니다 — 코드를 그대로 출력하면 됩니다.
관계 기호 21 개:
1
2
\leq \geq \prec \succ \preceq \succeq \ll \gg \equiv \sim \simeq
\asymp \approx \ne \subset \supset \subseteq \supseteq \in \ni \notin
그리스 문자 40 개: \alpha 부터 \omega 까지 소문자 전체, \Gamma 부터 \Omega 까지 대문자 전체, 그리고 변형 6 개.
1
\varepsilon \vartheta \varsigma \varpi \varrho \varphi
연산자 · 기타 32 개:
1
2
3
\times \div \cdot \pm \mp \ast \star \circ \bullet \oplus \ominus
\oslash \otimes \odot \dagger \ddagger \vee \wedge \cap \cup \aleph
\Re \Im \top \bot \infty \partial \forall \exists \neg \triangle \diamond
7. 다시 조립되나
트리를 걸으면서 위 표를 적용했을 때, 첫 프로브의 수식 여덟 개가 이렇게 돌아왔습니다.
| 원본 | 재조립 |
|---|---|
| $\frac{1}{2}a+b=2$ | \frac{1}{2}a+b=2 |
| $\alpha + \beta$ | \alpha + \beta |
| $\sqrt{x}$ | \sqrt{x} |
| $x^{2}$, $x_{1}$ | x^{2}, x_{1} |
| $\frac{\frac{1}{2}}{3}$ | \frac{\frac{1}{2}}{3} |
| $\sum_{x=1}^{n} x^{2}$ | \sum_{x=1}^{n} x^{2} |
| $\int_{a}^{b} xdx$ | \int_{a}^{b} xdx |
여덟 개 전부 복구됐습니다.
큰 수식 두 개를 통합 테스트로 돌렸습니다. 근의 공식은 이렇게 돌아왔습니다.
1
x=\frac{-b\pm\sqrt{{b}^{2}-4ac}}{2a}
정규분포 밀도함수는 \superscript 안에 \frac, 그 안에 \superscript, 그 안에 \frac — 네 겹인데 중첩이 그대로 살아남았습니다.
8. Google Docs 가 만들지 못하는 것
수식 편집기를 두고 계획을 세우는 중이라면 이 절이 제일 쓸모 있습니다. 변환기를 만드는 쪽에는 오히려 좋은 소식입니다.
아래 각각을 Docs 수식 편집기에서 만들어 보려 했습니다. 앞의 셋은 못 만들었고, 뒤의 둘은 처음에 “없다” 고 적었는데 그건 글자로 친 경우에만 맞는 말이었습니다.
| 구성물 | Docs 수식 편집기에서 |
|---|---|
| 행렬 | 못 만듦 |
| cases · 경우 나누기 | 못 만듦 |
| 이중적분 | 못 만듦 |
| 크기 맞춰지는 괄호 | 구조로 있음 — 툴바의 ( ) [ ] { } | | 항목, 또는 그 이름을 친 \rbracelr, \sbracelr, \bracelr, \abs 는 인자 하나짜리 EQUATION_FUNCTION 노드로 돌아옵니다. 글자로 친 ( ) { } 는 그냥 글자이고, \left( / \right) 는 받지 않습니다 |
\sin 같은 함수 이름 | 기호로 있음 — \sin, \cos, \log 를 치고 스페이스를 누르면 그 코드를 가진 EQUATION_SYMBOL 노드로 돌아옵니다. 날 글자로 친 sin x 는 그냥 글자입니다 |
2026-09-22 정정 — 툴바와 백슬래시 명령 형태로 다시 측정했습니다. 처음 측정은 글자로 친 괄호와 날 글자 sin x 만 봤습니다. “못 만듦” 세 줄은 2026-09-21 에 다시 확인했고 그대로입니다.
Google Docs 에서 행렬을 써서 내보낼 생각이었다면 그 계획은 안 됩니다. 마감 전날 새벽 두 시보다는 지금 아는 편이 낫습니다.
뒤집으면, 이 편집기가 만들 수 있는 것의 집합이 작고 닫혀 있다는 뜻입니다. 함수 21 개 (5절의 14 개 + 그 표 아래 적은 7 개) 와 기호 93 개가 지금까지 측정한 전부입니다. 이걸 다 처리하는 변환기는 편집기 메뉴로 만들 수 있는 것을 대부분 이 아니라 전부 처리합니다.
9. 껄끄러운 구석 세 곳
글자로 친 괄호는 글자이고, 글자로 친 중괄호는 출력을 깨뜨립니다. (1/2), {1/2}, [1/2] 를 치면 그 글자가 든 평범한 TEXT 노드가 나옵니다. 소괄호와 대괄호는 괜찮지만, 날 { 나 } 가 LaTeX 컴파일러에 도달하면 묶음 기호로 읽혀 빌드를 깨뜨립니다. 재조립기는 \{ 와 \} 로 이스케이프해야 합니다. 툴바의 괄호 항목은 다른 물건입니다. Math operations 메뉴에서 고른 ( ), [ ], { } 와 절댓값 막대 — 또는 이름을 친 \rbracelr, \sbracelr, \bracelr, \abs — 는 인자 하나짜리 EQUATION_FUNCTION 노드로 돌아오고, 안의 내용은 중첩 트리로 남습니다 (안에 넣은 분수는 여전히 \frac 노드). 그래서 이쪽의 자연스러운 LaTeX 은 글자가 아니라 \left … \right 한 쌍입니다.
날 글자로 친 함수 이름은 글자로 남고, 그냥 두는 쪽이 더 충실합니다. sin x 를 치면 sinx 라고 적힌 TEXT 노드 하나가 나옵니다. 친 대로 저장됩니다. \sin 을 치고 스페이스를 누르면 다릅니다. Docs 가 코드 \sin 을 가진 EQUATION_SYMBOL 로 바꾸고 곧게 세워 그리며, 다른 기호처럼 \sin 그대로 나옵니다 (2026-09-22 측정, \cos · \log 도 같음). 날 글자 쪽은, 이걸 “친절하게” \sin 으로 고치고 싶어지는데, 문서가 실제로 어떻게 보이는지 생각해 보면 다릅니다. Docs 는 그 글자를 다른 변수와 똑같이 기울여 그립니다. LaTeX 에서 sin 은 기울임이고 \sin 은 곧게 섭니다. sin 으로 두는 쪽이 원래 렌더링과 맞고, 고치는 쪽은 의도를 추측해서 문서 모양을 바꿉니다. 1절의 조용한 12 와 같은 범주의 오류입니다 — 그럴듯한데 원본과 다른 결과. 기본값이 아니라 명시적 옵션으로 두는 게 맞습니다.
아래첨자는 글자 하나에만 붙습니다. $\log_2 x$ 를 치면 세 조각으로 갈립니다 — lo, 그다음 g 와 2 에 걸린 \subscript, 그다음 x. Docs 는 아래첨자를 바로 앞 글자 하나에만 붙입니다. 재조립된 소스는 lo{g}_{2}x 라 못생겼지만 렌더는 제대로 됩니다. 중요한 건 그쪽입니다.
10. 이 측정이 다루지 않은 것
측정을 공개하는 이유가 남이 검증할 수 있게 하는 것이니, 경계를 분명히 적어 둡니다.
- 기기 하나, 로케일 하나. 여기 로그는 전부 한 환경에서 나왔습니다. Docs 버전이나 인터페이스 언어가 코드를 바꾸는지는 확인하지 않았습니다.
- 읽기만. 이 프로브는 수식을 꺼내는 쪽만 다뤘습니다. Apps Script 로 네이티브 수식 개체를 만들 수 있는지는 별개 질문이고 시험하지 않았습니다.
- 개체 경계를 넘어가는 수식. 프로브 중에 $\sum$ 은 수식 개체에 넣고 옆의
x는 평범한 문단 텍스트로 친 경우를 만났습니다. 그 옆 텍스트가 수식에 속하는지 판단하려면 추측이 필요해서, 추측하지 않기로 했습니다. 그 텍스트가 사라지지는 않습니다 — 평범한 텍스트로 출력에 남고, 수식 바깥에 있을 뿐입니다.
11. 왜 측정했나
LaTeXFlow 를 만들고 있습니다. 문서에 적힌 LaTeX 을 렌더된 수식 이미지로 바꾸는 도구입니다. 반대 방향 — 완성된 Docs 를 LaTeX 소스로 다시 읽는 것 — 을 하다 보면 네이티브 수식에 계속 걸렸는데, 공개된 문서만으로는 복구 가능한지 판단이 서지 않았습니다. 그래서 직접 찍어 봤습니다.
짧은 답은, 복구됩니다. 결정론적으로, 이 글에 다 들어가는 크기의 표로. 더 영리한 게 필요할 줄 알았습니다.
변환기를 직접 만드신다면 5절의 표와 6절의 기호 목록이 지도 전부입니다. 빠진 코드를 발견하시면 — 특히 시험하지 못한 비영어 Docs 인터페이스에서 — 알려 주시면 좋겠습니다.
- 네이티브 수식이 왜 구분자로 안 잡히는지 → 수식 개체와 텍스트의 차이
- 구분자 네 가지의 차이 → LaTeX 수식 구분자 네 가지