EDITORCLASSBLOGLOGINimg default descriptionimg default descriptionimg default description표현한다는 것의무한한 가능성,새로운 형태로
담아내다.
img default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default description새로운 형태의 콘텐츠Opkle은 코드 없이 웹을 마음껏 만들고, 누구나 자기 화면을 그릴 수 있도록 에디터를 만들고, 그 결과물을 어디서나 즐길 수 있게 돕는 팀입니다.텍스트와 화면과의 조화를 통해, 웹을 짓는다는 것이 그저 단순한 코딩이 아닌, 상상을 펼치고 감각을 깨우는 과정이 될 수 있도록 좋은 도구를 만들어 냅니다.
옵클 에디터 개발기: 그래픽 엔진 안의 선형대수dev12옵클 에디터 개발기: 인디자인처럼 편집하는 레이아웃 편집기dev13옵클 에디터 개발기: 원고가 다음 페이지로 흐르는 편집기dev14옵클 에디터 개발기: EPUB과 IDML을 편집 가능한 페이지로dev15옵클 에디터 개발기: 한 번 만든 레이아웃을 EPUB과 인쇄물로dev1689101112...13 옵클 에디터 개발기: 인디자인처럼 편집하는 레이아웃 편집기벡터 에디터와 그 뒤의 Rust 그래픽 엔진까지 만들고 나니 다음에 필요한 도구는 자연스럽게 정해졌습니다. 책 안에 들어갈 이미지와 도형을 직접 만들 수 있게 되었지만, 그것들을 실제 페이지 위에 배치하는 일은 전혀 다른 종류의 편집이었습니다. 그래픽 에디터가 하나의 평면 위에서 오브젝트를 다룬다면, 레이아웃 에디터는 여러 페이지와 펼침면, 도련, 마진, 마스터 페이지와 인쇄 규격을 동시에 이해해야 합니다.처음부터 이 에디터를 페이지 테두리가 추가된 벡터 캔버스로 생각하지는 않았습니다. 실제 책에서는 같은 좌표를 가진 오브젝트라도 어느 페이지에 속했는지에 따라 전혀 다른 대상이 되고, 왼쪽 면과 오른쪽 면은 책등을 기준으로 서로 다른 마진을 가집니다. 사진은 책등을 넘어 펼침면 전체에 걸칠 수 있어야 하고, 공통 머리말과 쪽번호는 개별 페이지가 아니라 마스터에서 관리되어야 합니다.그렇다고 종이책의 규칙만 복제할 생각도 없었습니다. 옵클의 레이아웃은 나중에 EPUB과 웹으로 다시 나가야 하므로 자유 좌표와 흐름형 구조를 함께 품을 수 있어야 했습니다. 우선은 이 모든 작업의 바닥이 되는 페이지 공간부터 정확하게 만들기로 했습니다. 장면의 소유권을 어떻게 설명할지, 물리 단위를 무엇으로 계산할지, 화면에서 일어난 조작이 확대율과 무관한 문서 좌표로 남는지부터 순서대로 정리했습니다.벡터 다음의 빈자리벡터 에디터의 중심은 오브젝트입니다. 사각형, 타원, path, text, image와 group이 하나의 좌표계 위에서 움직이고 selection과 transform이 그 오브젝트를 따라갑니다. 레이아웃 에디터에는 이 장면 그래프 위로 페이지라는 소유 영역이 하나 더 올라갑니다. 같은 도형이라도 어느 페이지의 자식인지에 따라 local origin과 clip, 템플릿 문맥이 달라집니다. 페이지는 화면의 테두리가 아니라 좌표와 소유권을 함께 정의하는 scene node가 됩니다.문서 모델을 설계할 때는 의미 상태와 표시 상태를 먼저 구분했습니다. 판형과 페이지 순서, 스타일처럼 문서의 의미를 이루는 값은 zoom이나 현재 선택과 독립적이어야 합니다. 반대로 pan, 선택 상자, resize handle과 hover 표시는 같은 문서를 잠시 바라보는 view state입니다. 둘을 섞으면 화면을 확대했다는 이유로 저장 결과가 바뀌거나, 안내선이 출력물에 들어가는 문제가 생깁니다. Model과 View의 분리는 UI 패턴을 넘어 문서 불변식을 지키는 장치였습니다.순서와 정체성도 서로 다른 개념으로 두었습니다. 배열의 index는 빠르게 순회하기 좋지만 앞에 항목이 들어오는 순간 뒤쪽 대상이 모두 다른 번호를 갖습니다. 안정적인 식별자는 위치가 바뀌어도 같은 대상을 가리키고, 순서는 별도의 관계로 표현합니다. 이 원리는 데이터베이스의 primary key와 정렬 열을 분리하는 것과 같고, 장면 그래프에서는 selection, history와 참조 무결성을 유지하는 기초가 됩니다.편집 연산은 가능한 한 하나의 local coordinate space를 대상으로 정의했습니다. 이동은 vector addition, 회전과 확대는 affine transform이고, 이 연산 자체는 대상이 첫 페이지에 있는지 백 번째 페이지에 있는지 알 필요가 없습니다. 상위 page transform을 통해 local과 world 사이를 오갈 수 있다면 같은 기하 연산을 모든 소유 영역에서 재사용할 수 있습니다. 멀티페이지 에디터의 규모를 키운 것은 기능을 복제하는 일이 아니라 좌표 문맥을 명시하는 일이었습니다.Physical unit레이아웃 에디터에서는 좌표 단위부터 다시 정리해야 했습니다. 화면에서 익숙한 CSS pixel과 인쇄 규격에서 쓰는 point, 사용자가 판형과 여백을 입력할 때 쓰는 millimeter는 같은 숫자가 아닙니다. 저는 문서 규격과 출력 좌표를 point 계열로 유지하고, 사용자에게 보이는 판형과 마진 입력은 millimeter로 변환해 보여주도록 잡았습니다. PDF와 IDML이 point 공간을 중심으로 움직이기 때문에 내부 기준을 여기에 맞추는 편이 정확했습니다.변환의 정준 단위는 inch로 두었습니다. `1 inch = 25.4 mm`, `1 inch = 72 pt`이고 CSS pixel은 기본 96 DPI를 기준으로 환산됩니다. 임의의 길이는 먼저 inch 비율로 정규화한 뒤 목적 단위로 돌려보냈습니다. mm와 pt, px와 pt 사이에 서로 다른 변환 상수를 흩어놓는 대신 하나의 단위 경로를 공유하면서 입력창, 문서 초기값, 프리셋, SVG 좌표와 PDF 출력이 같은 값을 사용하게 했습니다.A4도 단순히 `595 × 842`처럼 반올림한 값으로 저장하지 않았습니다. `210 × 297 mm`를 정확히 point로 환산한 값을 기본으로 두어 다시 mm로 표시했을 때 210과 297이 깨끗하게 돌아오게 했습니다. 작은 반올림 차이는 한 페이지에서는 눈에 띄지 않지만 페이지 박스와 도련, 인쇄 마크를 반복 계산하면 누적될 수 있습니다. 판형 preset과 새 문서, reset, 과거 프로젝트 fallback까지 같은 기준값을 쓰도록 맞췄습니다.인쇄 해상도가 필요한 시점에는 문서 좌표와 raster 좌표를 다시 분리했습니다. 페이지에서 1 unit이 의미하는 물리 길이를 먼저 구하고, 출력 DPI에 따라 pixel 수를 결정합니다. 편집 화면의 zoom은 사용자가 보는 배율일 뿐 문서의 실제 크기에 영향을 주지 않습니다. 이 원칙을 고정한 뒤부터 화면 확대, PDF 출력, 고해상도 이미지 출력이 서로 다른 크기를 만드는 일이 없어졌습니다.세 개의 좌표 공간단위가 정리된 다음에는 좌표 공간을 분리했습니다. 마우스 이벤트가 주는 값은 브라우저 화면의 client 좌표이고, SVG stage는 pan과 zoom이 적용된 viewport 좌표를 사용합니다. 실제 오브젝트는 다시 각 페이지의 왼쪽 위를 `(0, 0)`으로 삼는 page local 좌표에 저장됩니다. 같은 점이 화면, 캔버스, 페이지에서 서로 다른 숫자를 가지는 구조입니다.화면의 점 `p_screen`에서 stage의 translation을 빼고 zoom으로 나누면 world 좌표가 나옵니다. 여기서 대상 페이지 group의 translation을 빼야 page local 좌표가 됩니다. 식으로 줄이면 `p_local = (p_screen - t_view) / zoom - t_page`입니다. 마스터 편집 공간은 별도의 page translation을 사용하므로 현재 모드와 편집 중인 면에 따라 마지막 변환 행렬을 선택했습니다.이 변환은 단순한 pointer 계산을 넘어 모든 삽입 경로에 적용했습니다. 사용자가 다른 페이지 위로 이미지를 드롭하면 먼저 screen point와 각 page rect의 교차를 계산해 대상 페이지를 찾고, 그 페이지를 활성화한 뒤 local point로 변환합니다. 오브젝트를 펼침면 반대쪽으로 옮길 때도 출발 페이지 좌표를 root 좌표로 올린 다음 도착 페이지의 local 좌표로 내립니다. 중간에 직접 x 차이만 더하면 zoom이나 spread gap이 바뀌는 순간 위치가 어긋납니다.선택 영역과 resize handle도 같은 원칙을 따랐습니다. 오브젝트 geometry는 page local에 남겨두고 selection overlay만 root layer에 그렸습니다. 그래서 여러 오브젝트를 함께 선택하거나 서로 다른 page translation을 가진 면으로 이동해도 실제 저장 좌표는 오염되지 않습니다. 화면에 맞게 보이는 좌표와 문서에 맞게 저장되는 좌표를 분리한 것이 멀티페이지 편집의 기준점이 되었습니다.펼침면의 기하페이지 배치는 단면과 펼침면을 서로 다른 규칙으로 계산했습니다. 단면에서는 페이지를 세로로 나열하면 되지만, 펼침면에서는 첫 페이지가 오른쪽에 단독으로 놓이고 그다음부터 왼쪽과 오른쪽 페이지가 한 쌍을 이룹니다. 페이지 parity로 면을 판정하고, 한 쌍이 완성될 때마다 다음 spread row로 이동시켰습니다. 페이지 추가와 삭제로 parity가 바뀌면 이후 면의 마진과 마스터도 다시 계산합니다.도련이 들어가면 단순히 페이지 폭만큼 떨어뜨려서는 안 됩니다. 책등 쪽의 두 bleed 영역이 겹쳐 보이거나 반대로 불필요한 틈이 생길 수 있으므로 spread gap에는 양쪽 bleed와 실제 작업 간격을 함께 반영했습니다. 페이지 배경, 도련 가이드, 마진 가이드, 재단 경계는 본문 오브젝트보다 아래에 있는 전용 layer로 분리했습니다. 활성 페이지를 앞으로 올려도 흰 배경이 옆 페이지의 펼침 이미지를 덮지 않게 하기 위한 배치입니다.책등을 가로지르는 오브젝트를 위해 clip도 비대칭으로 만들었습니다. 모든 페이지에 자기 width만큼의 clip을 주면 왼쪽 페이지에서 시작한 사진이 오른쪽으로 넘어가는 순간 잘립니다. 서로 마주 보는 면에서는 clip 영역을 책등 방향으로만 이웃 페이지까지 확장하고, 바깥쪽과 위아래는 계속 page box 기준으로 잘리게 했습니다. 한 오브젝트가 어느 페이지에 소속됐는지 유지하면서도 실제 펼침면에서는 자연스럽게 이어져 보입니다.마진은 left와 right보다 inside와 outside의 의미를 먼저 가집니다. 오른쪽 홀수 면에서는 inside가 물리적인 왼쪽이고, 왼쪽 짝수 면에서는 inside가 오른쪽입니다. 문서 설정을 적용할 때 논리 마진을 페이지 parity에 맞는 물리 마진으로 바꾸고 본문 가이드와 자동 텍스트 프레임에 반영했습니다. 비대칭 제본 여백과 좌우 마스터가 페이지 순서에 따라 정확하게 뒤집히도록 만들었습니다.Smart guide레이아웃 작업에서는 정확한 숫자만큼 손으로 움직이는 감각이 중요했습니다. 오브젝트를 끌었을 때 페이지 중앙과 모서리, 마진선, 다른 오브젝트의 중심과 경계에 자연스럽게 붙어야 합니다. rect와 ellipse, image, foreignObject, path와 group은 geometry 저장 방식이 다르기 때문에 먼저 실제 bounds를 공통 형태 `(x, y, w, h)`로 정규화했습니다.선택 영역의 왼쪽·가운데·오른쪽과 위·가운데·아래를 스냅 축으로 만들고, 페이지와 주변 오브젝트의 같은 축을 후보로 수집했습니다. 각 후보와의 delta를 비교해 임계값 안에서 가장 가까운 값만 이동량에 반영했습니다. zoom이 커져도 화면에서 느껴지는 스냅 거리가 지나치게 달라지지 않도록 threshold는 문서 좌표와 화면 좌표 사이에서 현재 배율에 맞게 환산했습니다.임계값 하나만 사용하면 경계에서 오브젝트가 붙었다 떨어지기를 반복합니다. 그래서 스냅이 시작된 마우스 위치와 적용한 delta를 기억하고, 진입보다 넓은 해제 임계값을 가진 hysteresis를 넣었습니다. 사용자가 스냅 축을 분명하게 벗어날 때까지 기존 결정을 유지하므로 느리게 움직여도 오브젝트가 떨리지 않습니다. x축과 y축은 독립적으로 잠글 수 있게 해 한쪽 정렬을 유지한 채 다른 방향으로 계속 움직일 수 있습니다.등간격 스냅은 별도의 후보군으로 계산했습니다. 선택 오브젝트와 좌우 이웃 사이의 간격 `g_left`, `g_right`를 비교하고 두 값이 같아지는 이동량을 찾았습니다. 세로축도 같은 방식으로 위아래 간격을 계산했습니다. 일반 정렬이 이미 걸린 축에서는 등간격 판단이 개입하지 않게 우선순위를 두었고, 실제 간격은 치수선과 숫자 배지로 표시했습니다. 가이드는 page local에서 계산하지만 root overlay에 그려 pan과 zoom 중에도 정확히 따라오도록 했습니다.룰러도 현재 viewport가 보고 있는 world 범위를 역산해 눈금을 만들었습니다. zoom에 따라 major tick과 minor tick 간격을 바꾸고, 화면 밖의 눈금은 생성하지 않았습니다. pointermove마다 stage의 layout 값을 다시 읽으면 강제 reflow 비용이 생기므로 bounding rect와 변환값을 캐시하고 pan, zoom, resize 시점에만 무효화했습니다. 조작의 부드러움은 결국 좌표 계산과 브라우저 layout 비용을 함께 관리한 결과였습니다.Object hierarchy페이지 안의 오브젝트는 모두 같은 깊이에만 놓이지 않습니다. 여러 요소를 묶은 group은 하나의 transform 대상으로 움직여야 하지만, 필요할 때는 안쪽 요소를 다시 선택하고 편집할 수 있어야 합니다. 일반 선택 모드에서는 group을 하나의 객체로 취급하고, 더블 클릭하면 그 group만 편집하는 isolation 상태로 들어가도록 만들었습니다.격리 상태에서는 선택 탐색의 root를 전체 page layer가 아니라 현재 group으로 제한했습니다. 마우스 아래에 group 바깥의 요소가 함께 겹쳐 있어도 선택 후보로 승격하지 않고, group의 직계 자식만 선택합니다. 새 도형이나 텍스트를 만들면 현재 page가 아니라 격리 중인 group 안에 삽입됩니다. Tab 순회와 marquee selection도 같은 root를 사용해서 화면상의 격리와 실제 DOM 소유권이 일치하도록 했습니다.현재 편집 범위는 점선 outline으로 보여주되 이 overlay는 문서 데이터가 아니므로 transient 요소로 분리했습니다. 저장 문자열과 history snapshot을 만들기 전에 제외하고, zoom이 바뀌면 stroke와 dash 간격을 다시 맞췄습니다. 바깥 배경을 클릭하거나 페이지를 전환하면 격리를 끝내고 group 자체를 다시 선택합니다. 편집 중 group이 삭제되거나 DOM에서 분리된 경우에도 상태를 자동으로 정리해 죽은 참조가 selection에 남지 않게 했습니다.페이지 배경과 가이드, 마스터 DOM, 개별 오브젝트, selection overlay도 같은 이유로 서로 다른 계층에 두었습니다. 저장되는 layer와 화면에만 존재하는 layer를 구분하니 undo에 resize handle이 남거나 출력물에 안내선이 섞이는 일을 막을 수 있었습니다. 레이아웃 에디터의 page는 단순한 SVG 한 장이 아니라 소유권과 편집 수명을 구분한 여러 layer의 조합으로 완성되었습니다.Master page책 전체에서 반복되는 요소를 매 페이지에 복사해 관리할 수는 없습니다. 머리말과 꼬리말, 장식선, 배경과 쪽번호는 템플릿의 문제입니다. 일반 페이지가 템플릿의 결과를 실제 자식처럼 소유해버리면 공통 요소 하나를 고칠 때 모든 페이지를 다시 수정해야 하고, 어느 값이 원본인지도 불분명해집니다. 그래서 반복 요소는 정의와 인스턴스를 구분하는 prototype 관계로 바라봤습니다.템플릿은 고정 그림이 아니라 문맥을 받는 함수에 가깝습니다. 페이지 번호와 전체 장수, 왼쪽·오른쪽 면 같은 값은 `T(context)`가 평가될 때 비로소 실제 표시값이 됩니다. 같은 정의가 페이지마다 다른 결과를 만들 수 있고, 페이지 순서가 바뀌면 원본을 고치는 대신 새 문맥에서 다시 평가하면 됩니다. 선언적 UI와 함수형 템플릿이 가진 장점을 출판 페이지에도 그대로 적용한 셈입니다.상속에는 우선순위가 필요했습니다. 문서 기본값 위에 템플릿 값이 놓이고, 개별 페이지가 명시적으로 바꾼 속성은 그보다 가까운 범위에서 우선합니다. 모든 속성을 무조건 복사하면 원본이 바뀌어도 override와 구분할 수 없으므로, 페이지는 자신이 실제로 덮어쓴 값만 기억하는 편이 낫습니다. CSS cascade처럼 출처와 specificity를 알고 있어야 반복 요소와 개별 편집이 함께 존재할 수 있습니다.편집 범위도 논리적으로 분리했습니다. 일반 페이지를 편집할 때는 템플릿의 결과가 보이지만 직접 선택되지는 않고, 템플릿 문맥에 들어갔을 때만 공통 정의를 고칠 수 있습니다. 같은 화면에 그려진다는 이유로 같은 수명의 객체가 되는 것은 아닙니다. 렌더 순서, 선택 가능성, 저장 소유권을 각각 구분하면서 반복 구조가 본문 오브젝트와 섞이지 않게 했습니다.페이지 엔진 완성레이아웃 에디터의 첫 번째 완성점은 도구의 개수가 아니었습니다. 의미 상태와 view state가 분리되고, 순서가 바뀌어도 객체의 정체성이 유지되며, 물리 단위가 화면 배율과 무관하게 정확히 왕복하는 것이 먼저였습니다. local, world와 screen 좌표 사이의 변환이 하나의 행렬 체계 안에서 설명되어야 이후의 텍스트 조판과 문서 변환도 같은 위치를 믿고 작동할 수 있습니다.그 위에 첫 페이지가 오른쪽에 놓이는 펼침면, 책등 방향의 비대칭 clip, inside와 outside 마진의 미러링, 도련과 page box의 포함 관계를 올렸습니다. 서로 다른 도형을 공통 bounds 모델로 정규화해 정렬과 등간격 스냅을 만들었고, group isolation과 transient overlay로 복잡한 장면 그래프도 안전하게 편집할 수 있게 했습니다. 반복 요소는 템플릿 함수와 상속 우선순위로 본문과 분리했습니다.페이지 순서 변경, 단면과 펼침면 전환, 비대칭 마진, 서로 다른 도련, 고배율과 저배율, 페이지 간 오브젝트 이동, 중첩 group 편집과 템플릿 적용을 반복하며 좌표 불변식을 검수했습니다. 화면에서 맞은 위치가 다시 열었을 때도 같은 물리 좌표로 복원되고, 출력 page box와도 일치하는 것을 모두 확인했습니다. 벡터 자산을 실제 책의 지면 위에 조립할 수 있는 페이지 엔진은 이 단계에서 완성도 있게 안정화되었습니다.이전글목록으로다음글저작권 고시Copyright Notice본 웹사이트의 모든 디자인 결과물 및 영상에 대한 저작권은 Abstract Cloud에 있으며, 저작권법 및 관련 법령에 의해 보호받습니다. 웹, 영상, 본문, 표지, 내지 디자인을 포함한 모든 콘텐츠는 저작권자의 자산으로, 사전 동의 없이 무단 복제, 배포, 2차 저작물 제작, 온라인 공유 등을 금지합니다. 이를 위반할 시, 저작권법에 따라 민형사상 책임을 질 수 있습니다. 정당한 구매와 저작권 보호는 창작자의 권리를 지키며, 더 나은 작품으로 보답할 힘이 됩니다.저작권자: Abstract Cloud | 대표자: 배창규(uragen)
© Abstract Cloud. All Rights Reserved.
HOMEFAQ이용 약관개인정보 이용방침help@opkle.app
010-2747-3403
상호 :추상적 형상 디자인(Abstract cloud)  |  대표자 :배창규사업자등록번호 :249-74-00533통신판매업 신고번호 :2025-의정부송산-0634주소 :경기도 의정부시 부용로 49, 108동 402호웹의 모든 콘텐츠, 디자인, 소스 코드에 대한
저작권은 Opkle에게 있습니다.
img default description