미리디
경력근거: 모두가 쉽게 디자인을 통해 세상과 소통하게 하자
미리디의 이 자리는 텍스트만 입력하면 프레젠테이션이 나오는 서비스를 만들면서 그 결과를 등록하고 수정하고 검수하는 내부 시스템까지 직접 개발하는 자리라, 자동 생성 결과가 실제 템플릿 상자에 들어가는지를 계산하고 운영자의 판단을 받아 확정하는 이 화면이 그 일의 축소판과 같은 모양입니다. 공고가 내용 대치를 PoC 예시로 못박고 품질을 판단할 기준을 정리하는 것까지 기대 모습으로 적었기 때문에, 내용 대치의 성공 여부를 사람의 인상이 아니라 줄 수와 넘침 픽셀이라는 규칙으로 판정하고 그 규칙에 걸리면 화면이 내보내기를 막는 흐름까지 만들었습니다. 온톨로지 체계를 새로 세우는 일은 여러 팀이 합의해야 존재하는 결과물이라 전제하지 않고, 이미 템플릿에 붙어 있는 상자 규격을 그대로 입력으로 받는 한 단계 앞에서 시작합니다. 이 포트폴리오가 보여주는 것은 레이아웃 규격을 도메인 모델로 다루고 사람의 선택마다 즉시 다시 계산되는 내부 도구를 백엔드부터 배포까지 혼자 만드는 풀스택 기술이며, 타이포그래피 설계나 다국어 조판 같은 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
텍스트 입력만으로 디자이너 수준의 프레젠테이션을 자동 생성하는 서비스를 만드는 팀이라서, 자동 생성 결과가 실제 템플릿 상자에 들어가는지를 픽셀로 계산하는 층을 제품의 골조로 둡니다.
디자인 데이터의 등록과 수정과 검수를 위한 내부 시스템을 만들고 운영자가 반복해서 쓰는 형태로 워크플로우를 개선하는 일이 주요 업무라서, 화면을 보고 끝나는 리포트가 아니라 운영자의 선택이 있어야 다음 단계가 열리는 검수 화면으로 만듭니다.
내용 대치와 스타일 대치와 편집 보조를 예로 든 PoC 를 설계하고 품질을 판단할 기준을 정리하는 자리라서, 내용 대치가 성공했는지를 사람의 인상이 아니라 줄 수와 넘침 픽셀이라는 규칙으로 판정하는 기준까지 함께 만듭니다.
AI 초안 생성기가 낸 슬라이드별 원고와 템플릿의 텍스트 상자 규격을 함께 받아 각 상자에서 글자가 몇 줄이 되고 몇 픽셀 넘치는지 계산하고, 폰트를 어디까지 줄이면 들어가는지와 몇 자를 줄여야 하는지를 산술로 답한 뒤, 운영자가 슬라이드마다 폰트 축소와 축약안 선택과 레이아웃 교체와 예외 승인 가운데 하나를 골라야 확정본 내보내기가 열리는 1주 포트폴리오입니다. 고르지 않은 슬라이드가 한 장이라도 남으면 내보내기는 잠긴 채로 있고, 사람이 고른 처리는 곧바로 같은 계산기에 다시 들어가 정말 들어맞는지 확인된 뒤에야 확정됩니다.
AI 가 만든 프레젠테이션 초안을 하루에 수십 장씩 눈으로 넘겨 보며 제목이 상자 밖으로 삐져나갔는지 확인하고, 삐져나간 장은 캡처해 문서에 붙여 개발자에게 넘기던 템플릿 운영 담당자입니다. 어느 장이 얼마나 넘쳤는지는 도구가 세어 주되, 문장을 줄여도 되는지와 레이아웃을 바꿔도 되는지는 본인이 정하고 싶은 사람입니다.
텍스트 상자 규격과 글자 폭 표가 데이터로 있으면 넘침 여부와 몇 자를 줄여야 하는지는 산술로 정확히 나오고 사람은 어떤 처리로 해결할지만 고르면 되므로, 눈으로 넘겨 보던 검수가 슬라이드마다 한 번의 선택으로 끝난다는 가정입니다.
원고 파일과 템플릿 상자 규격을 넣으면 슬라이드마다 어느 상자가 몇 줄이 되고 몇 픽셀 넘치는지와 폰트를 어디까지 줄이면 들어가는지가 계산되어 뜨고, 운영자가 폰트 축소와 축약안 선택과 레이아웃 교체와 예외 승인 가운데 하나를 골라야 그 장이 해결로 넘어가며, 남은 장이 없어야 확정본 내보내기가 열리는 웹 화면이 배포까지 끝난 완결품입니다. 만드는 덩어리는 텍스트 맞춤 계산 엔진과 슬라이드 검수 화면 둘이며 평일 5일에 혼자 끝나는 범위입니다.
확정 이력을 쌓아 같은 상자 유형에서 운영자가 늘 같은 처리를 고른 경우를 찾아 자동 처리 규칙 후보로 제안하고, 운영자가 그 규칙을 켜야 다음 덱부터 해당 유형이 미리 처리되어 오는 규칙 관리 화면입니다. 이 주차부터 계산 엔진과 화면을 Docker 컨테이너로 묶고 저장소와 인증을 Supabase 로 붙여 실제 주소로 운영합니다.
축약으로 풀리지 않는 슬라이드에서 원고의 구조를 상자 개수와 역할과 길이 분포로 요약해 비슷한 템플릿 레이아웃을 벡터 검색으로 찾아 오고, 후보마다 그 레이아웃에 넣었을 때의 넘침 계산 결과를 함께 보여 주어 운영자가 골라 갈아 끼우는 화면입니다.
슬라이드 18장짜리 덱과 템플릿 상자 규격 12종을 넣으면 넘치는 상자와 그 줄 수와 넘침 픽셀이 함께 뜨고, 처리안을 고를 때마다 숫자가 다시 계산되며, 미해결이 없어야 확정본이 나오는 흐름을 처음부터 끝까지 시연합니다.
손계산으로 만든 정답표 60건과 줄 수가 정확히 같고 넘침 픽셀이 소수점 첫째 자리까지 일치하는 것을 자동 대조로 보입니다. 다만 정답과 데이터를 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
높이가 빠진 옛 템플릿과 최소 폰트가 기준 폰트보다 큰 모순 규격과 줄바꿈이 안 되는 긴 주소와 빈 원고와 이모지가 섞인 하드 케이스 25종에서, 화면이 계산을 멈추지 않고 그 장을 미해결로 표시하며 사유와 사람이 할 일을 내는 것을 보입니다.
확정 이력에서 뽑은 자동 처리 규칙 후보가 다음 덱에서 운영자의 선택과 같았던 비율을 냅니다 (측정: 규칙이 미리 처리한 상자와 운영자의 최종 선택 일치율, 표본: 슬라이드 200장). 2주차 결과입니다.
위 수치는 더미데이터 자가채점이며, 실제 검수 시간 단축과 넘침 누락률은 미리캔버스의 실제 템플릿 규격과 실제 폰트 렌더링 결과와 운영자의 실사용이 있어야 측정 가능합니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모가 도는 상태와 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 높이가 0 으로 비어 있는 옛 템플릿과 최소 폰트가 기준 폰트보다 큰 모순 규격과 줄바꿈이 안 되는 긴 주소와 빈 원고와 이모지가 섞인 문장을 더미에 일부러 섞어 두었습니다.
글자 폭 표로 계산한 값은 실제 폰트 엔진의 렌더링 결과와 자간과 커닝만큼 어긋납니다. 넘침 판정에 여유 픽셀을 두고, 경계에 걸린 장은 자동으로 해결 처리하지 않고 사람이 화면에서 확인하게 두었습니다. 실제 도입에서는 폭 표를 미리캔버스가 쓰는 폰트의 실측값으로 바꿔야 합니다.
문장을 줄여도 되는지는 계산으로 결정되지 않습니다. 숫자와 고유명사가 빠지면 안 되는 장이 있어서, 시스템은 축약 후보와 계산 결과까지만 내고 무엇을 쓸지는 운영자가 화면에서 골라야 다음이 진행되게 두었습니다.
이 포트폴리오가 보여주는 것은 레이아웃 규격을 계산 가능한 모델로 다루고 사람의 선택마다 다시 계산되는 내부 도구를 혼자 만드는 기술이며, 타이포그래피 설계나 다국어 조판 같은 도메인 전문성은 주장하지 않습니다. 온톨로지 체계를 세우는 일도 여러 팀이 합의해야 하는 결과물이라 이 포트폴리오의 범위 밖입니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
슬라이드 템플릿에 들어 있는 텍스트 상자 한 개의 규격입니다. 넘침 여부와 폰트 축소 여지가 전부 이 표의 크기와 폰트와 최대 줄 수에서 산술로 나오므로, 계산이 막히는 규격을 일부러 섞어 두었습니다.
| slot_id | template_id | slide_role | w_px | h_px | font_size_px | min_font_size_px | line_height_ratio | max_lines | wrap_rule | overflow_policy |
|---|---|---|---|---|---|---|---|---|---|---|
| slot_t01 | tpl_biz_title | title | 880 | 120 | 44 | 32 | 1.3 | 2 | word | shrink |
| slot_t02 | tpl_biz_title | subtitle | 880 | 64 | 24 | 20 | 1.4 | 2 | word | shrink |
| slot_b01 | tpl_biz_bullet3 | bullet | 640 | 220 | 20 | 16 | 1.5 | 6 | word | shrink |
| slot_b02 | tpl_biz_bullet3 | caption | 300 | 36 | 14 | 14 | 1.4 | 1 | word | none |
| slot_c01 | tpl_cover_wide | title | 1,180 | 96 | 60 | 40 | 1.2 | 1 | word | shrink |
아래 Zod 스키마에 맞는 슬라이드 템플릿 텍스트 상자 규격 80행을 JSON 배열로 생성합니다.
{"type":"object","properties":{"slot_id":{"type":"string"},"template_id":{"type":"string"},"slide_role":{"type":"string","enum":["title","subtitle","bullet","body","caption"]},"w_px":{"type":"integer"},"h_px":{"type":"integer"},"font_size_px":{"type":"integer"},"min_font_size_px":{"type":"integer"},"line_height_ratio":{"type":"number"},"max_lines":{"type":"integer"},"wrap_rule":{"type":"string","enum":["word","anywhere"]},"overflow_policy":{"type":"string","enum":["shrink","clip","none"]}},"required":["slot_id","template_id","slide_role","w_px","h_px","font_size_px","min_font_size_px","line_height_ratio","max_lines","wrap_rule","overflow_policy"]}
쉬운 데이터만 만들지 않습니다. 하드 케이스를 반드시 섞습니다: (1) h_px 가 0 이라 높이 규격이 빠진 옛 템플릿 상자 3건, (2) min_font_size_px 가 font_size_px 보다 큰 모순 규격 2건, (3) min_font_size_px 와 font_size_px 가 같아 축소 여지가 아예 없는 상자 4건, (4) max_lines 가 1 이면서 w_px 가 200 이하라 한 줄에 열 글자도 못 넣는 좁은 상자 3건, (5) overflow_policy 가 none 이라 넘치면 무조건 사람이 처리해야 하는 상자 4건, (6) wrap_rule 이 anywhere 라 어절 중간에서도 줄이 갈리는 상자 2건, (7) line_height_ratio 가 1.0 이라 줄이 붙는 상자 2건, (8) 같은 template_id 안에서 slot_id 가 중복된 행 1쌍. JSON 배열만 출력합니다.{"type":"object","properties":{"slot_id":{"type":"string"},"template_id":{"type":"string"},"slide_role":{"type":"string","enum":["title","subtitle","bullet","body","caption"]},"w_px":{"type":"integer"},"h_px":{"type":"integer"},"font_size_px":{"type":"integer"},"min_font_size_px":{"type":"integer"},"line_height_ratio":{"type":"number"},"max_lines":{"type":"integer"},"wrap_rule":{"type":"string","enum":["word","anywhere"]},"overflow_policy":{"type":"string","enum":["shrink","clip","none"]}},"required":["slot_id","template_id","slide_role","w_px","h_px","font_size_px","min_font_size_px","line_height_ratio","max_lines","wrap_rule","overflow_policy"]}AI 초안 생성기가 낸 슬라이드별 원고 블록입니다. 이 도구가 만든 글이 아니라 밖에서 받아 오는 입력이며, 같은 글자 수라도 숫자와 영문과 한글의 폭이 달라 계산이 갈리는 문장을 섞어 두었습니다.
| block_id | deck_id | slide_no | slide_role | text | locked_terms | lang | source |
|---|---|---|---|---|---|---|---|
| blk_001 | deck_a | 1 | title | 누구나 쉽게 만드는 프레젠테이션, 미리캔버스 AI 프레젠테이션 소개 | ["미리캔버스"] | ko | ai_draft |
| blk_002 | deck_a | 2 | title | 2026년 상반기 글로벌 확장 로드맵과 분기별 실행 계획 | ["2026년"] | ko | ai_draft |
| blk_003 | deck_a | 2 | subtitle | 국내 사용자 기반을 발판으로 삼는 다국어 템플릿 전략 | [] | ko | ai_draft |
| blk_004 | deck_a | 3 | bullet | 첫째, 템플릿 메타데이터를 언어별로 나누어 관리합니다. 둘째, 번역 … | [] | ko | ai_draft |
| blk_005 | deck_a | 4 | caption | 전년 대비 매출 성장률 (2025년 기준) | ["2025년"] | ko | ai_draft |
아래 Zod 스키마에 맞는 슬라이드 원고 블록 220행을 JSON 배열로 생성합니다.
{"type":"object","properties":{"block_id":{"type":"string"},"deck_id":{"type":"string"},"slide_no":{"type":"integer"},"slide_role":{"type":"string","enum":["title","subtitle","bullet","body","caption"]},"text":{"type":"string"},"locked_terms":{"type":"array","items":{"type":"string"}},"lang":{"type":"string","enum":["ko","en","ja"]},"source":{"type":"string","enum":["ai_draft","user_paste"]}},"required":["block_id","deck_id","slide_no","slide_role","text","locked_terms","lang","source"]}
쉬운 데이터만 만들지 않습니다. 하드 케이스를 반드시 섞습니다: (1) text 가 빈 문자열이거나 공백만 있는 행 4건, (2) 줄바꿈이 불가능한 40자 이상의 주소나 영문 식별자가 통째로 들어간 행 3건, (3) 한 문장이 120자를 넘어 어떤 상자에도 들어가지 않는 행 4건, (4) 숫자와 단위와 괄호가 섞여 폭 계산이 한글과 달라지는 행 5건, (5) locked_terms 가 text 전체와 같아 한 글자도 줄일 수 없는 행 2건, (6) 이모지와 특수문자가 섞인 행 3건, (7) lang 이 en 또는 ja 라 글자 폭 표가 달라지는 행 각 4건, (8) 같은 deck_id 와 slide_no 에 같은 slide_role 이 두 번 오는 중복 행 1쌍. JSON 배열만 출력합니다.{"type":"object","properties":{"block_id":{"type":"string"},"deck_id":{"type":"string"},"slide_no":{"type":"integer"},"slide_role":{"type":"string","enum":["title","subtitle","bullet","body","caption"]},"text":{"type":"string"},"locked_terms":{"type":"array","items":{"type":"string"}},"lang":{"type":"string","enum":["ko","en","ja"]},"source":{"type":"string","enum":["ai_draft","user_paste"]}},"required":["block_id","deck_id","slide_no","slide_role","text","locked_terms","lang","source"]}| TypeScript | 텍스트 맞춤 계산기와 화면을 같은 타입으로 묶고 상자 규격과 처리 결과를 Zod 로 런타임에 검증합니다. |
| React 또는 Next.js | 슬라이드 미리보기와 넘침 표시와 처리안 선택과 확정 게이트가 한 화면에서 맞물리는 검수 화면을 만들고, 선택이 바뀔 때마다 계산 결과를 다시 받아 옵니다. |
| PostgreSQL | 템플릿 상자 규격과 원고 블록과 운영자가 확정한 처리 이력을 저장하고, 어떤 상자 유형에서 넘침이 잦은지를 쿼리로 직접 집계합니다. |
| REST API | 넘침 계산과 축약 후보 요청과 확정 저장을 각각의 엔드포인트로 나눠 화면과 계산 엔진의 경계를 분명히 둡니다. |
| LLM API (Claude) | 넘침이 난 문장의 축약 후보만 만들고, 그 후보가 정말 들어가는지는 다시 계산기가 판정해 통과한 것만 화면에 올립니다. |
| Supabase | 상자 규격과 확정 이력 저장과 운영자 로그인을 BaaS 로 붙여 화면 만드는 시간을 계산 엔진 쪽에 몰아 줍니다. |
| Docker | 계산 엔진과 화면을 컨테이너 하나로 묶어 2주차부터 실제 주소로 운영합니다. |
| 시멘틱 검색·벡터 DB | 3주차에 원고 구조와 상자 구성이 닮은 레이아웃 후보를 찾아 교체 대상으로 제시하는 데 씁니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차에서 확정 이력을 쌓아 자동 처리 규칙 후보를 뽑는 화면을 붙입니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 3주차에서 구조가 닮은 레이아웃을 찾아 교체 후보로 보여 주는 화면을 만듭니다.
운영자가 화면에서 슬라이드마다 처리안을 골라 확정한 덱 한 개의 결과입니다. 상자마다 무엇이 얼마나 넘쳤고 무엇으로 처리했으며 처리 뒤 다시 계산한 값이 무엇인지를 함께 담아, 다음 사람이 숫자를 손으로 검산하게 합니다.
{"type":"object","properties":{"deck_id":{"type":"string"},"template_id":{"type":"string"},"confirmed_by":{"type":["string","null"]},"slides":{"type":"array","items":{"type":"object","properties":{"slide_no":{"type":"integer"},"slot_id":{"type":"string"},"block_id":{"type":"string"},"status":{"type":"string","enum":["fits","shrink_ok","cut_needed","swap_needed","spec_broken"]},"measured":{"type":"object","properties":{"lines":{"type":"integer"},"text_width_px":{"type":"number"},"content_height_px":{"type":"number"},"overflow_px":{"type":"number"}},"required":["lines","text_width_px","content_height_px","overflow_px"]},"decision":{"type":"string","enum":["keep","shrink","replace_text","swap_layout","accept_overflow","unresolved"]},"decided_by":{"type":"string","enum":["rule","human"]},"applied_font_size_px":{"type":["integer","null"]},"final_text":{"type":["string","null"]},"reason":{"type":"string"},"recheck":{"type":["object","null"],"properties":{"lines":{"type":"integer"},"overflow_px":{"type":"number"},"pass":{"type":"boolean"}},"required":["lines","overflow_px","pass"]}},"required":["slide_no","slot_id","block_id","status","measured","decision","decided_by","applied_font_size_px","final_text","reason","recheck"]},"minimum":1},"unresolved_slides":{"type":"array","items":{"type":"integer"}},"export_locked":{"type":"boolean"}},"required":["deck_id","template_id","confirmed_by","slides","unresolved_slides","export_locked"]}[
{
"deck_id": "deck_a",
"template_id": "tpl_biz_title",
"confirmed_by": "ops.template@example.com",
"slides": [
{
"slide_no": 2,
"slot_id": "slot_t01",
"block_id": "blk_002",
"status": "fits",
"measured": {
"lines": 2,
"text_width_px": 1150.4,
"content_height_px": 114.4,
"overflow_px": 0
},
"decision": "keep",
"decided_by": "rule",
"applied_font_size_px": 44,
"final_text": "2026년 상반기 글로벌 확장 로드맵과 분기별 실행 계획",
"reason": "두 줄 안에 들어가고 높이가 상자보다 5.6픽셀 남습니다.",
"recheck": null
},
{
"slide_no": 3,
"slot_id": "slot_b01",
"block_id": "blk_004",
"status": "shrink_ok",
"measured": {
"lines": 8,
"text_width_px": 4992,
"content_height_px": 240,
"overflow_px": 20
},
"decision": "shrink",
"decided_by": "human",
"applied_font_size_px": 18,
"final_text": "첫째, 템플릿 메타데이터를 언어별로 나누어 관리합니다. 둘째, 번역 문구가 원문보다 길어지는 경우를 미리 걸러 냅니다. 셋째, 지원 폰트가 없는 언어는 대체 폰트 규칙을 먼저 정합니다.",
"reason": "운영자가 문장을 그대로 두고 18픽셀까지 줄이는 쪽을 골랐습니다.",
"recheck": {
"lines": 6,
"overflow_px": 0,
"pass": true
}
},
{
"slide_no": 4,
"slot_id": "slot_k01",
"block_id": "blk_005",
"status": "cut_needed",
"measured": {
"lines": 2,
"text_width_px": 216,
"content_height_px": 33.6,
"overflow_px": 5.6
},
"decision": "replace_text",
"decided_by": "human",
"applied_font_size_px": 12,
"final_text": "매출 성장률 (2025년)",
"reason": "축소 여지가 없는 상자라 축약 후보 세 개 가운데 연도를 남긴 안을 운영자가 골랐습니다.",
"recheck": {
"lines": 1,
"overflow_px": 0,
"pass": true
}
},
{
"slide_no": 5,
"slot_id": "slot_x01",
"block_id": "blk_007",
"status": "spec_broken",
"measured": {
"lines": 1,
"text_width_px": 588.8,
"content_height_px": 41.6,
"overflow_px": 0
},
"decision": "unresolved",
"decided_by": "rule",
"applied_font_size_px": null,
"final_text": null,
"reason": "상자 높이 규격이 0 으로 비어 있어 넘침을 판정할 수 없습니다. 템플릿 규격을 채우거나 다른 레이아웃으로 바꾸는 선택이 필요합니다.",
"recheck": null
}
],
"unresolved_slides": [
5
],
"export_locked": true
}
]자동: (1) Zod parse, (2) decision 이 unresolved 인 슬라이드 번호가 unresolved_slides 와 정확히 같은지, (3) unresolved_slides 가 비어 있지 않으면 export_locked 가 참인지, (4) decision 이 keep 이 아닌 모든 항목에 recheck 가 있고 pass 가 참이며 overflow_px 가 0 이하인지, (5) applied_font_size_px 가 해당 상자의 min_font_size_px 이상 font_size_px 이하인지, (6) final_text 가 원문의 locked_terms 를 모두 포함하는지, (7) 손계산 정답표와 lines 가 정확히 같고 overflow_px 가 소수점 첫째 자리까지 같은지 대조합니다. 사람 확인: 축약안이 문장의 뜻을 지켰는지와 레이아웃을 바꿔도 되는지와 넘침을 예외로 승인해도 되는지는 운영자가 화면에서 판단합니다. accept_overflow 로 확정한 항목은 사유가 비어 있으면 저장이 거절되며 확정본과 함께 예외 목록으로 따로 남습니다.