웹에서 잘 보이는 파일 하나를 만들었다고 로고 관리가 끝나지는 않는다. 다음 수정과 다른 컴퓨터, 작은 크기와 어두운 배경을 생각해야 한다. 실제 자산 구조를 읽고 새 익명 글자 예제로 그 차이를 확인했다.
수정할 원본과 전달할 파일의 역할을 나눈다
로고의 글자를 바꾸려면 편집 가능한 원본이 편하다. 반면 다른 컴퓨터에서 같은 모습을 보여주려면 글꼴에 기대지 않는 전달 파일이 필요할 수 있다. 나는 브랜드 자료의 현재 SVG 구조를 다시 읽고 두 역할을 나누어 남긴 이유를 정리했다. 원본을 윤곽선으로 바꾼 파일 하나로 대체하면 다음 수정 때 잃는 정보가 생긴다.
확인한 폴더의 최상위 SVG는 서른여덟 개였고, 그중 이름과 구조를 함께 확인한 윤곽선 파일 열세 개에는 text 요소가 없었다. 편집할 text 요소가 있는 파일도 열세 개였다. 이 수는 현재 파일 구조의 관찰이다. 모든 파일의 인쇄 승인이나 상표 확인을 새로 수행했다는 뜻은 아니다. 고객의 실제 로고는 이곳에 옮기지 않았다.
윤곽선은 모양을 고정하고 문자는 따로 남긴다
윤곽선 파일은 선택한 글꼴의 글자 모양을 path로 남긴다. 글꼴이 설치되지 않은 환경에서도 그 경로 자체가 전달된다. 다만 경로가 문장과 같은 편집 정보를 가진 것은 아니다. 로고를 설명하는 대체 텍스트와 제목, 다음 수정을 위한 문자열은 별도로 남겨야 한다. 웹에서 파일을 넣을 때도 모양만 보이는 것으로 설명이 끝나지 않는다.
새 예제는 업체명을 쓰지 않고 Aa 두 글자만 사용했다. 선택한 설치 글꼴의 윤곽선을 읽어 간단한 SVG 쌍을 만들었다. 글꼴 소프트웨어는 내려받는 파일에 포함하지 않는다. 편집 원본은 글꼴 이름을 참조하므로 다른 환경에서 대체 글꼴이 선택될 수 있다. 비교용 윤곽선과 같다고 보장하려면 같은 글꼴 조건을 준비해야 한다.
작게 보일 때는 굵기와 배경도 함께 본다
벡터 파일은 확대해도 경로를 유지하지만 아주 작은 크기의 읽기 품질까지 자동으로 해결하지는 않는다. 새 예제를 처음 줄였을 때 기본 가변 글꼴 굵기만 사용하는 선택을 다시 검토했다. 현재 생성기는 wght 축이 있으면 요청한 굵기를 범위 안에서 적용하고, 정해진 글꼴이면 실제 굵기를 기록한다. 파일 이름만 굵게 붙이는 대신 적용 값을 남긴다.
윤곽선 예제는 32·64·256픽셀 폭으로 렌더링해 확인했고 어두운 배경의 흰 글자도 따로 확인했다. 이 미리보기에서는 눈에 띄는 잘림을 보지 못했다. 작은 아이콘과 브라우저 화면, 실제 인쇄물의 최종 승인을 대신하는 검토는 아니다. 밝은 배경과 어두운 배경용 파일을 구별해 잘못된 조합을 고르지 않도록 한다.
변환의 한계를 코드에 명시한다
이 생성기는 단순한 라틴 글자와 공백만 받는다. 글리프의 경계를 계산해 여백을 두고 두 파일에 같은 viewBox를 사용한다. 글꼴에 글자가 없으면 실패하며 기존 출력 파일이 있으면 덮어쓰지 않는다. 작은 모듈이라도 잘못된 결과를 빈 그림으로 넘기기보다 조건을 분명히 거절하는 편이 다음 작업에 도움이 된다.
아래 코드는 생성기에서 글자의 경로와 크기를 계산하는 핵심을 분리한 것이다. 반환한 경로와 위치는 실제 SVG 저장 단계에서 사용한다. 커닝과 합자, 복잡한 문자 조합을 처리하는 완전한 조판기는 아니다. 한글이나 연결형 문자를 무조건 같은 방식으로 변환하지 않는다. 실제 브랜드 제작에서는 조판과 라이선스 확인, 인쇄소 조건이 더 필요하다.
코드와 구현 살펴보기
윤곽선과 viewBox를 계산하는 핵심 함수
"""Create a simple Latin diagnostic text master and glyph-outline SVG pair.
Uses an explicitly selected, licensed font. No kerning, ligatures or complex-script
shaping. Font software is not copied into output. Existing files are preserved.
"""
import argparse,hashlib,html,json,math,re
from pathlib import Path
from xml.etree import ElementTree as ET
from fontTools.pens.boundsPen import BoundsPen
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.ttLib import TTFont
from fontTools.varLib.instancer import instantiateVariableFont
def glyph_geometry(font_path,text='Aa',size=64,padding=16,weight=600):
if not isinstance(text,str) or not text.strip() or len(text)>48 or not re.fullmatch(r'[A-Za-z ]+',text):
raise ValueError('One to48 simple Latin letters/spaces required')
if not math.isfinite(size) or size<=0 or not math.isfinite(padding) or padding<0:
raise ValueError('Positive size and nonnegative padding required')
with TTFont(font_path) as font:
if 'fvar' in font and any(axis.axisTag=='wght' for axis in font['fvar'].axes):
axis=next(axis for axis in font['fvar'].axes if axis.axisTag=='wght')
if not math.isfinite(weight) or not axis.minValue<=weight<=axis.maxValue:raise ValueError('Weight outside the selected font axis')
font=instantiateVariableFont(font,{'wght':weight},inplace=True)
applied_weight=weight
else:applied_weight=font['OS/2'].usWeightClass if 'OS/2' in font else 400
glyphs=font.getGlyphSet();cmap=font.getBestCmap();units=font['head'].unitsPerEm;scale=size/units
family=font['name'].getDebugName(1) or 'serif';cursor=0;parts=[];bounds=[]
for char in text:
name=cmap.get(ord(char))
if not name:raise ValueError('Selected font lacks a requested glyph')
pen=SVGPathPen(glyphs);glyphs[name].draw(pen)
bbox=BoundsPen(glyphs);glyphs[name].draw(bbox)
if bbox.bounds:
left,bottom,right,top=bbox.bounds;bounds.append((left+cursor,bottom,right+cursor,top));parts.append((cursor,pen.getCommands()))
cursor+=font['hmtx'].metrics[name][0]
if not bounds:raise ValueError('No visible glyph outline')
minimum=min(x[0] for x in bounds);maximum=max(x[2] for x in bounds);bottom=min(x[1] for x in bounds);top=max(x[3] for x in bounds)
width=math.ceil((maximum-minimum)*scale+2*padding);height=math.ceil((top-bottom)*scale+2*padding)
origin=padding-minimum*scale;baseline=padding+top*scale
return {"viewBox": [0,0,width,height], "family": family, "weight": applied_weight, "scale": scale, "baseline": baseline, "origin": origin, "paths": [{"x": x, "d": commands} for x,commands in parts]}
파일 목록에 확인 조건을 같이 남긴다
같은 그림처럼 보여도 편집본과 전달본, 밝은 배경용과 어두운 배경용의 용도가 다르다. 생성 기록에는 선택한 글꼴의 해시와 적용 굵기, viewBox와 경로 수를 남긴다. 이후 파일이 달라졌을 때 무엇을 다시 확인해야 할지 찾는 기준이다. 원본 폴더의 파일 개수만 보고 전체 자산이 같은 기준으로 승인됐다고 결론 내리지 않는다.
새 생성기에서 구조와 입력 조건 등 여덟 검사를 수행했고 새 미리보기 네 개를 직접 읽었다. 아래 파일은 그 새 익명 예제의 편집본과 윤곽선본이다. 원래 고객 파일이나 글꼴 파일은 포함하지 않았다. 수정할 원본을 보존한 채 전달본을 새로 만들고, 크기와 배경에 따른 확인 범위를 기록하는 것이 이 모듈의 핵심이다.
구현 출력물, 영상과 문서로 살펴보기
직접 만든 익명 Aa 출력물을 원본·작은 전달본·어두운 배경 순서로 비교한다. 영상은 출력물 설명이며 실제 사이트 화면 녹화는 아니다. 브라우저의 영상 탐색과 PDF 뷰어 조작은 별도 검수 범위다.