옵클(Opkle)은 창작자를 위한 다양한 앱과 시스템을 제공하는 개발사입니다. 전자책 에디터 앱 'Opkle editor'를 출시했고, 관련 전자책 클래스를 제공하고 있습니다.
EDITORCLASSBLOGLOGIN표현한다는 것의무한한 가능성,새로운 형태로 담아내다.새로운 형태의 콘텐츠Opkle은 코드 없이 웹을 마음껏 만들고, 누구나 자기 화면을 그릴 수 있도록 에디터를 만들고, 그 결과물을 어디서나 즐길 수 있게 돕는 팀입니다.텍스트와 화면과의 조화를 통해, 웹을 짓는다는 것이 그저 단순한 코딩이 아닌, 상상을 펼치고 감각을 깨우는 과정이 될 수 있도록 좋은 도구를 만들어 냅니다.옵클 에디터 개발기: 그래픽 엔진 안의 선형대수dev12옵클 에디터 개발기: 인디자인처럼 편집하는 레이아웃 편집기dev13옵클 에디터 개발기: 원고가 다음 페이지로 흐르는 편집기dev14옵클 에디터 개발기: EPUB과 IDML을 편집 가능한 페이지로dev15옵클 에디터 개발기: 한 번 만든 레이아웃을 EPUB과 인쇄물로dev1689101112...16 옵클 에디터 개발기: 한 번 만든 레이아웃을 EPUB과 인쇄물로레이아웃 에디터가 외부 문서를 편집 가능한 페이지로 받아들이게 된 다음에는 반대 방향을 완성할 차례였습니다. 페이지 안에서 만든 결과가 레이아웃 에디터에 갇혀 있으면 제작 과정의 마지막 도구가 될 수 없습니다. 이미지는 벡터 에디터로, 표는 표 편집기로 다시 보내 수정할 수 있어야 하고, 완성된 지면은 반응형 EPUB과 웹 문서 또는 인쇄용 PDF로 나갈 수 있어야 했습니다.이 출력들은 같은 문서를 사용하지만 요구하는 정보가 서로 다릅니다. 벡터 편집 왕복에서는 오브젝트의 안정적인 정체성과 원본 프로젝트 연결이 중요합니다. EPUB은 고정 좌표보다 reading order와 반응형 관계가 중요하고, 인쇄 PDF는 반대로 정확한 page box와 폰트, 도련과 재단선이 중요합니다. 하나의 범용 export 함수로 모두 처리하면 어느 쪽도 정확해지지 않습니다.그래서 레이아웃 상태를 읽는 단계만 공유하고, 목적에 따라 변환기를 분리했습니다. 편집기 왕복은 object id와 blob 생명주기를 유지하고, fixed-to-reflow 변환은 geometry에서 문서 구조를 추론합니다. PDF 경로는 모든 페이지와 폰트를 확정한 뒤 물리 규격으로 렌더합니다. 입력에서 시작한 변환 고리를 다른 편집기와 EPUB, 웹, 인쇄물까지 완전히 닫았습니다.Editor round trip레이아웃 안의 이미지를 벡터 에디터로 보낼 때 가장 먼저 한 일은 현재 페이지를 저장하는 것이었습니다. 아직 id가 없는 오브젝트에는 안정적인 object id를 부여하고, 그 값이 page 문자열에 들어간 뒤에만 외부 편집기를 열었습니다. 편집을 마치고 돌아왔을 때 index나 선택 순서가 아니라 정확히 같은 오브젝트를 찾기 위한 기준입니다.교환 데이터는 작은 metadata와 큰 binary를 분리했습니다. metadata에는 현재 프로젝트와 layout map, 대상 object id, 새 작업인지 기존 벡터 프로젝트를 다시 여는지, 돌아와야 할 session id를 넣었습니다. 실제 seed image와 최종 결과 blob은 IndexedDB의 임시 교환 row에 두었습니다. 브라우저 storage 문자열에 대용량 base64를 넣지 않고도 iframe 교체와 앱 재진입을 견딜 수 있습니다.부모 에디터와 자식 편집기 사이의 화면 전환은 `postMessage`로 요청하되 데이터 자체는 message에 싣지 않았습니다. 메시지는 어떤 편집기를 열고 어느 session을 읽을지만 알려줍니다. 자식 편집기는 session row에서 seed와 metadata를 읽고 작업한 뒤 결과를 같은 교환소에 flush합니다. 레이아웃 에디터가 다시 열리면 자신의 map id와 session이 맞는 결과만 회수합니다.빠른 더블 클릭이나 중복 우클릭으로 두 작업이 동시에 열리지 않도록 공통 in-flight guard를 두었습니다. 새 벡터 작업은 흰 캔버스와 연결된 placeholder image를 먼저 본문 마진 안에 만들고, 기존 작업은 오브젝트가 기억한 원본 프로젝트를 엽니다. 시작에 실패하거나 사용자가 취소하면 원래 오브젝트와 blob은 그대로 유지하고 session metadata만 정리했습니다.결과를 제자리로벡터 편집 결과가 돌아오면 page DOM을 새로 만드는 대신 기존 오브젝트를 가능한 한 제자리에서 갱신했습니다. 정적 결과는 image blob을 교체하고, 애니메이션이나 영상 결과는 같은 bounds를 가진 media object로 바꿨습니다. x와 y, 표시 폭, z-order, object id는 그대로 두어 레이아웃의 배치와 다시 편집할 연결이 끊기지 않게 했습니다.결과의 원본 pixel width와 height가 달라졌다면 기존 표시 폭을 기준으로 새 종횡비에 맞는 높이를 계산했습니다. 사용자가 페이지에서 잡은 폭은 유지하고 콘텐츠 비율만 새 결과에 맞춥니다. 영상으로 바뀔 때는 재생 자산과 poster를 각각 영구 blob으로 등록하고, 정적 이미지로 돌아오면 더 이상 필요하지 않은 video 참조를 정리했습니다.표 편집기도 같은 왕복 계약을 사용하지만 session collection과 project marker는 분리했습니다. 새 표는 본문 영역 왼쪽 위에 4:3 placeholder를 만들고, 결과 JPEG가 돌아오면 같은 image object의 blob과 높이를 교체합니다. 표 내용이 비어 있거나 편집이 취소되면 placeholder를 지우지 않고 table project id만 유지해 다시 열 수 있게 했습니다.이전 blob은 새 결과를 저장한 뒤에 정리했습니다. 프로젝트의 모든 page와 master, 현재 DOM에서 같은 id를 참조하는 오브젝트가 있는지 확인하고 더 이상 참조되지 않을 때만 row를 삭제했습니다. 새 blob 생성, 오브젝트 repoint, 프로젝트 저장, 고아 자산 삭제 순서를 지켜 중간 실패가 보이는 이미지를 잃는 일로 이어지지 않게 했습니다.Fixed의 reading order고정 페이지를 EPUB 본문으로 돌려보낼 때는 x와 y를 그대로 HTML의 absolute position으로 옮기지 않았습니다. 그렇게 만든 문서는 특정 화면 크기에서만 같은 모양을 보이고, 폭이 바뀌면 읽기 순서와 접근성이 모두 무너집니다. 레이아웃에서 사람이 만든 시각적 관계를 읽어 반응형 문서 구조로 다시 해석해야 했습니다.먼저 표지를 제외한 페이지를 순서대로 읽고 모든 visible object의 page local bounds를 수집했습니다. 기본 정렬은 y가 먼저이고 x가 다음이지만, y가 조금 다르다는 이유만으로 같은 행의 두 오브젝트를 서로 다른 문단으로 볼 수는 없습니다. 세로 범위가 일정 비율 이상 겹치는 오브젝트를 하나의 band로 묶고 band 안에서는 x 순서로 정렬했습니다.같은 band에서 가로 영역이 겹치지 않는 요소는 서로 다른 column 후보가 됩니다. 반대로 한 오브젝트가 band 폭 대부분을 차지하면 독립 block으로 판단합니다. 명시적인 Flex 컨테이너는 geometry를 다시 추측하지 않고 이미 저장된 direction과 wrap, gap, padding, item order를 우선 사용했습니다. 자유 배치 영역에서만 overlap과 간격을 이용해 row와 column 구조를 추론했습니다.겹침이 강한 도형과 이미지, 텍스트는 별개의 흐름으로 풀면 원래 합성 관계가 사라집니다. 이런 경우에는 하나의 visual group으로 묶거나 필요한 범위만 raster asset으로 변환했습니다. 모든 좌표를 억지로 reflow시키는 것이 아니라 읽기 구조로 표현할 수 있는 관계와 고정 합성으로 남겨야 하는 관계를 구분했습니다.Responsive structure텍스트 프레임은 내부 paragraph node를 순서대로 읽어 `
` 구조로 평탄화했습니다. paragraph style의 정렬, line-height, 첫 줄 들여쓰기, 앞뒤 여백을 CSS로 옮기고 character style span과 링크는 inline markup으로 유지했습니다. thread 때문에 여러 프레임으로 나뉜 같은 원고도 page와 frame 순서를 따라 하나의 reading flow로 이어집니다.