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...15 옵클 에디터 개발기: EPUB과 IDML을 편집 가능한 페이지로레이아웃과 조판 엔진이 완성된 다음에는 이 편집기를 빈 문서에서만 시작하는 도구로 남겨둘 이유가 없었습니다. 실제 출판 작업에는 이미 작성된 원고와 기존 EPUB, 다른 조판 프로그램에서 만든 페이지가 계속 들어옵니다. 파일을 보여주는 것과 편집 가능한 의미 구조를 복원하는 것은 다른 문제였습니다. 가져오기는 렌더링이 아니라 번역에 가까웠습니다.포맷마다 문서를 설명하는 언어가 다릅니다. EPUB은 흐름과 cascade를 중심으로 의미를 만들고, 고정 조판 문서는 페이지와 변환, 참조 그래프로 장면을 설명합니다. 일반 원고는 페이지보다 문단의 순서와 역할이 중요합니다. 이들을 하나의 평평한 문자열로 바꾸면 문법은 단순해지지만 원본 언어가 가지고 있던 타입과 관계가 사라집니다.그래서 가져오기 과정을 compiler pipeline으로 보았습니다. 각 포맷의 표면 문법을 파싱하고 이름과 참조를 해석한 뒤, 공통 중간표현으로 lowering하고, 마지막에 편집 가능한 장면 그래프로 materialize합니다. parser가 파일을 읽는다면 semantic analysis는 그 파일이 무엇을 의미하는지 결정합니다. importer는 확장자를 여는 기능이 아니라 서로 다른 문서 언어 사이의 의미 보존 변환이 되었습니다.Document compilercompiler의 첫 단계는 입력을 신뢰하는 일이 아니라 어떤 문법으로 읽을 수 있는지 판별하는 일이었습니다. 파일 확장자는 힌트일 뿐이고 실제 바이트의 구조, 필수 헤더와 내부 관계가 문법을 결정합니다. lexical validation에서 구조가 성립하지 않으면 뒤쪽 semantic phase로 넘기지 않습니다. 문서 importer도 일반 compiler처럼 잘못된 입력을 가능한 앞 단계에서 거부해야 했습니다.파싱이 끝나도 곧바로 편집 객체를 만들지는 않았습니다. syntax tree는 원본 파일의 표현을 잘 보존하지만 편집기에는 지나치게 포맷 종속적입니다. 반대로 화면 객체로 즉시 낮추면 원본에 있던 이름, 스타일 출처와 읽기 순서를 잃기 쉽습니다. 그래서 문단, 자산, 페이지, 변환과 참조를 표현하는 중간표현을 두고 포맷별 차이를 그 위에서 정규화했습니다.중간표현에는 source provenance도 필요했습니다. 같은 텍스트라도 원본에서 제목이었는지, 단순히 큰 글씨였는지에 따라 이후 변환의 의미가 다릅니다. 확실히 해석한 정보와 heuristic으로 추론한 정보를 같은 값으로 취급하지 않고 confidence와 origin을 구분하면, 불확실한 구조만 보수적으로 평탄화할 수 있습니다. 변환기는 모르는 사실을 만들어내기보다 알고 있는 의미를 보존해야 했습니다.마지막 materialization은 중간표현을 현재 편집 모델의 객체로 바꾸는 단계입니다. 이때 좌표 단위와 스타일 상속, 자산 참조가 모두 해소되어 있어야 합니다. 중간에 일부 노드가 실패하더라도 전체 원고를 잃지 않도록 의미 계층별 fallback을 뒀습니다. 구조를 완벽히 복원하지 못한 입력에서도 텍스트와 읽기 순서라는 가장 중요한 정보는 남도록 compiler의 오류 복구 원칙을 적용했습니다.Package graphEPUB 같은 package는 파일들의 집합보다 참조 그래프에 가깝습니다. 자산이 정점이고 문서 안의 링크와 stylesheet, 읽기 순서가 edge가 됩니다. 압축 내부의 물리적인 나열 순서는 그래프의 의미 순서와 무관합니다. importer가 복원해야 하는 것은 디렉터리 목록이 아니라 root에서 도달 가능한 문서 그래프와 제작자가 정의한 선형 reading order였습니다.상대 경로 해석은 그래프 정규화의 문제였습니다. `a/../b`, `./b`와 `b`가 같은 대상을 가리킨다면 서로 다른 문자열을 같은 canonical vertex로 보내야 합니다. 그렇지 않으면 자산 중복과 존재하지 않는 참조를 구분할 수 없습니다. canonicalization은 단순 문자열 정리가 아니라 보안 경계이기도 하므로 root 밖으로 탈출하는 경로와 모호한 표현을 함께 제한해야 합니다.namespace가 있는 문서는 접두사 문자열보다 확장된 이름으로 읽어야 합니다. prefix는 문서마다 달라질 수 있지만 namespace URI와 local name의 쌍은 같은 의미를 가집니다. 이는 프로그래밍 언어에서 변수의 표면 이름보다 symbol resolution 결과가 중요한 것과 같습니다. XML을 문자열 검색으로 읽지 않고 이름 공간이 있는 tree로 다루는 이유가 여기에 있었습니다.참조 무결성도 compiler의 link 단계처럼 검사했습니다. 모든 필수 edge의 대상이 존재하는지, media type과 실제 자산의 역할이 호환되는지, root에서 도달하지 않는 고아 자산이 무엇인지 분리합니다. 누락된 대상을 임의로 만들어내기보다 오류의 수준을 판단하고, 본문 진행에 필수인 참조와 부가 metadata를 구분했습니다. 패키지를 푼다는 말보다 document graph를 link한다는 표현이 더 정확했습니다.Cascade algebraCSS cascade는 문자열 합치기가 아니라 우선순위가 있는 대수였습니다. 하나의 속성에 여러 declaration이 도달하면 origin, importance, layer, specificity와 source order로 이루어진 tuple을 비교해 승자를 정합니다. 이 비교는 거의 lexicographic order이고, 같은 element와 property에 들어온 후보들 중 최대 원소를 선택하는 연산으로 볼 수 있습니다.상속은 cascade와 별개의 단계입니다. element 자신의 후보 집합에서 값이 확정되지 않았을 때만 부모의 computed value가 전달되고, 일부 속성은 initial value로 돌아갑니다. `inherit`, `initial`, `unset`과 상대 단위가 들어오면 specified value와 computed value, used value를 구분해야 합니다. 화면에 나타난 마지막 숫자만 저장하면 어떤 규칙에서 왔는지 잃게 되는 이유입니다.selector matching은 tree 위의 predicate evaluation입니다. 단순 selector는 한 정점의 속성만 보지만 descendant와 sibling combinator는 주변 관계를 함께 봅니다. 원본 tree를 fragment로 자르면 predicate의 정의역이 달라져 이전에 참이던 selector가 거짓이 될 수 있습니다. 스타일 보존은 declaration 복사보다 selector가 평가되던 문맥을 어떻게 유지할지의 문제였습니다.미디어쿼리는 환경 벡터에 대한 boolean 함수로 볼 수 있습니다. viewport, orientation, color preference를 변수로 놓으면 각 query는 환경 공간의 부분집합을 정의합니다. 여러 query가 겹치는 지점에서는 cascade가 다시 적용됩니다. importer가 특정 환경을 재현하려면 임의의 화면 폭을 쓰는 것이 아니라 하나의 환경 벡터를 명시하고 모든 조건을 같은 논리식으로 평가해야 했습니다.Fragmentation geometry흐름형 문서를 페이지로 나누는 일은 연속적인 block flow를 유한한 fragmentation container들에 사상하는 문제였습니다. 각 block은 분할 가능한 구간과 분할할 수 없는 원자 영역을 가지고, margin collapse와 float, keep constraint가 다음 break의 가능성을 바꿉니다. 단순한 bounding box 하나로는 여러 페이지에 걸친 요소의 실제 조각을 표현할 수 없습니다.조판 geometry는 글꼴 metric이 확정된 뒤에만 의미가 있습니다. 같은 문자열도 glyph substitution과 kerning, fallback에 따라 advance가 달라지고 줄바꿈이 연쇄적으로 이동합니다. 따라서 font set은 layout 함수의 외부 부가물이 아니라 입력 변수입니다. 입력 font가 달라지면 같은 DOM도 다른 함수에 들어간 것이므로 이전 geometry를 재사용할 수 없습니다.fragmentation을 구간 대수로 보면 하나의 논리 box가 페이지별 직사각형 조각의 순서열로 바뀝니다. 각 조각은 원본 콘텐츠 구간 `[a_i, b_i)`와 물리 영역 `R_i`를 대응시키고, 구간들은 겹치지 않으며 합치면 원래 범위가 되어야 합니다. 이 provenance가 있어야 화면의 조각을 선택해도 원본 문서의 어느 부분인지 설명할 수 있습니다.측정 공간과 표시 공간도 분리했습니다. 조판은 zoom이나 pan이 없는 canonical coordinate에서 계산하고, 화면은 그 결과에 관찰 변환을 적용합니다. 표시 배율이 layout input에 섞이면 확대할 때 줄바꿈이 바뀌는 모순이 생깁니다. 페이지 흡수의 바닥은 DOM 기술보다 논리 구간과 물리 조각 사이의 안정적인 사상이었습니다.Provenance tree원본 tree를 물리 조각으로 나누면 ancestor 문맥이 사라질 수 있습니다. descendant selector, 상대 단위와 상속값은 현재 node 하나가 아니라 root까지 이어지는 경로에 의존합니다. 따라서 fragment는 화면에 필요한 leaf만 복사한 독립 문서가 아니라 원본 tree의 특정 구간을 바라보는 view여야 했습니다. provenance는 조각이 어느 논리 node와 경로에서 왔는지 설명합니다.이 관계는 persistent tree의 path처럼 생각할 수 있습니다. 원본 tree는 권위적인 구조로 남고, 각 fragment는 원본 revision과 node path, 콘텐츠 interval을 통해 자신을 설명합니다. 조각을 이어 붙여 원본을 추측하지 않고 원본에서 언제든 새 조각을 계산할 수 있으므로 판형 변경에도 정보가 누적 손실되지 않습니다.스타일 보존에는 두 표현이 필요합니다. source rule은 왜 그 값이 적용되었는지 설명하고 computed value는 현재 환경에서 실제로 무엇이 되었는지 설명합니다. 전자만 남기면 문맥이 끊긴 fragment에서 selector가 달라질 수 있고, 후자만 남기면 사용자가 원본 스타일을 편집할 수 없습니다. 원인과 결과를 함께 보존하는 것은 compiler의 source map과 최적화된 code를 함께 다루는 것과 비슷했습니다.glyph의 ink bounds와 line box도 구분했습니다. line box는 advance와 ascent, descent를 이용한 조판 영역이고 실제 윤곽은 italic overhang이나 장식 부호 때문에 그 밖으로 나갈 수 있습니다. clipping은 논리 box만 보고 결정할 수 없고, 시각 경계와 조판 경계를 목적에 따라 선택해야 합니다. 이 차이는 특정 브라우저의 예외가 아니라 디지털 타이포그래피의 일반적인 성질입니다.가져온 구조를 편집 모델로 승격할 때도 정보의 손실량을 기준으로 판단했습니다. 의미가 명확한 heading과 paragraph는 더 단순한 native 구조로 정규화할 수 있지만, 복잡한 selector와 혼합 콘텐츠를 가진 영역을 성급히 평탄화하면 원래 표현을 되돌릴 수 없습니다. 변환 가능성은 노드 종류가 아니라 의미와 표현 사이의 정보 보존 여부로 결정했습니다.Durable IR렌더링 tree는 영속 문서 형식이 될 수 없습니다. 브라우저가 계산한 익명 box와 캐시, 세션 주소는 현재 실행 환경에만 의미가 있고 다음 실행에서 같은 정체성을 보장하지 않습니다. 저장해야 하는 것은 화면 객체가 아니라 같은 화면을 다시 계산할 수 있는 선언적 중간표현입니다. durable IR은 의미 구조, 스타일의 원인과 자산 참조를 포함해야 했습니다.직렬화 가능성은 단순히 JSON으로 바꿀 수 있다는 뜻이 아닙니다. tree의 모든 참조가 이름 공간 안에서 해소되고, 순환과 공유 관계가 명시되며, 버전이 달라도 각 필드의 의미를 해석할 수 있어야 합니다. ephemeral cache와 selection, layout box는 제외하고 원본 문서와 편집 의도만 남겨야 같은 입력에서 화면을 다시 materialize할 수 있습니다.자산 참조도 위치 주소보다 논리적인 identity여야 합니다. 현재 프로세스에서만 유효한 URL이나 포인터를 저장하면 파일은 존재해도 연결을 복원할 수 없습니다. durable reference는 package 안의 이름이나 content identity처럼 환경과 독립된 값을 사용하고, load 단계에서 현재 런타임이 이해하는 handle로 바뀝니다. 이름과 실제 메모리 주소의 분리는 linker와 loader의 관계와 같습니다.복원은 저장의 역연산이어야 하지만 byte 단위의 완전한 역함수일 필요는 없습니다. `decode(encode(x))`가 원래 x와 의미적으로 동등하면 됩니다. 여기서 동등성은 같은 문단 순서, 스타일과 자산 관계를 뜻하고 transient node 주소까지 같을 필요는 없습니다. round-trip property를 의미 불변식으로 정의하면서 화면 생명주기와 문서 생명주기가 분리되었습니다.Reference graph고정 조판 문서는 흐름형 문서와 반대 방향에서 출발합니다. 하나의 tree보다 페이지, story, style과 자산이 서로를 참조하는 object graph에 가깝습니다. 파일 경계는 저장 편의를 위한 partition일 뿐 의미 경계와 일치하지 않습니다. importer는 여러 조각을 읽은 뒤 symbol table을 만들고 참조를 link해야 비로소 하나의 페이지를 설명할 수 있습니다.link 단계에서는 정점의 종류와 edge의 타입을 함께 검사했습니다. 텍스트 프레임이 story를 참조하는 edge와 페이지가 template을 참조하는 edge는 같은 식별자 연결처럼 보여도 의미가 다릅니다. type이 맞지 않는 참조를 단순히 존재하는 대상으로 연결하면 그래프는 완성되어 보여도 잘못된 문서가 됩니다. typed graph validation이 필요했습니다.참조 그래프에는 cycle이 허용되는 곳과 허용되지 않는 곳도 다릅니다. 색상이나 스타일의 상속 cycle은 값을 확정할 수 없지만, 공유 자산을 여러 노드가 가리키는 fan-in은 정상입니다. story의 읽기 경로는 순서를 가져야 하고, group의 소유권 tree는 한 객체가 동시에 두 부모를 가지면 안 됩니다. graph 전체에 하나의 규칙을 적용하지 않고 edge 의미별 불변식을 정의했습니다.가져오기는 그래프를 한 장의 배경 이미지로 굽는 과정이 아니었습니다. 원고와 프레임, 템플릿과 자산의 관계를 공통 중간표현으로 옮겨 다시 선택하고 수정할 수 있게 만드는 과정입니다. 표면 XML의 이름을 그대로 노출하지 않고 typed scene graph와 style graph로 낮추면서 포맷의 세부와 편집 모델 사이에 안정적인 compiler boundary가 생겼습니다.Affine decomposition고정 조판 포맷 사이의 좌표 변환은 단위 환산만으로 끝나지 않습니다. 한 객체의 변환은 `p' = Ap + t`인 affine map이고, group 안의 객체는 root에서 자신까지의 행렬을 순서대로 곱해야 world 위치가 나옵니다. 행렬 곱은 교환법칙이 성립하지 않으므로 coordinate convention과 합성 방향을 먼저 고정해야 합니다.행렬 A를 사용자에게 익숙한 rotation, scale과 shear로 분해하는 일도 유일하지 않습니다. polar decomposition `A = RS`를 사용하면 R은 직교 회전 또는 반사, S는 대칭인 stretch로 나뉩니다. S의 eigenvector는 늘어나는 주축을, eigenvalue는 각 축의 scale을 설명합니다. 이 분해는 단순히 각도를 역산하는 것보다 기울어진 변환의 실제 기하를 잘 보존합니다.수치적으로는 determinant와 condition number가 중요합니다. determinant가 0에 가까우면 면적이 거의 사라지고 역행렬은 불안정해집니다. 그러나 determinant 하나만으로는 한 축이 크게 늘고 다른 축이 줄어든 ill-conditioned matrix를 충분히 설명하지 못합니다. singular value의 비율을 함께 봐야 입력 좌표의 작은 오차가 출력에서 얼마나 증폭되는지 판단할 수 있습니다.모든 변환을 회전된 사각형으로 단순화할 수도 없었습니다. shear나 reflection이 있는 affine map은 직각을 보존하지 않고, 자유 곡선은 control point와 tangent까지 같은 map을 통과해야 합니다. 표현 모델이 감당할 수 없는 자유도를 버리면 가져오는 순간 geometry가 달라집니다. 변환을 단순화할 때는 근사 오차를 측정하고 허용 범위 밖이면 더 일반적인 path 표현을 유지하는 것이 맞았습니다.Style lattice텍스트 스타일은 단일 객체보다 여러 범위에서 온 부분 함수들의 합성이었습니다. 문서 기본값, 문단 style, 문자 style과 직접 지정값이 같은 속성에 후보를 제공합니다. 각 계층은 모든 속성을 정의하지 않으므로 style은 property 집합에서 value로 가는 partial map으로 볼 수 있습니다. 가까운 범위의 정의가 먼 범위를 덮는 연산을 차례로 합성해 최종 값을 구합니다.이 구조는 lattice와 비슷한 면이 있습니다. `undefined`는 아직 결정되지 않은 아래쪽 값이고, 명시적 override는 더 구체적인 값입니다. 다만 서로 다른 두 직접 지정값처럼 비교할 수 없는 후보가 생기면 source order나 명시적인 우선순위가 필요합니다. style merge를 객체 spread로 생각하지 않고 출처가 있는 값의 선택 문제로 보면 편집과 round trip에서 원인을 보존하기 쉽습니다.typography 값의 단위도 의미에 따라 다릅니다. font size와 baseline shift는 길이지만 line-height는 길이나 무차원 비율일 수 있고 tracking은 글자 크기에 상대적인 값으로 해석되기도 합니다. 모든 숫자를 같은 scale로 곱하면 단위가 깨집니다. dimension을 가진 quantity와 ratio를 구분하고 기준 단위로 정규화해야 서로 다른 문서 언어 사이에서 조판 감각이 유지됩니다.색상도 좌표 변환입니다. 장치 의존적인 CMYK와 화면 RGB는 단순한 네 숫자에서 세 숫자로의 축소가 아니고 profile과 rendering intent에 따라 달라집니다. gradient는 색상 공간 위의 보간 함수이며, alpha와 stop 위치가 함께 정의됩니다. 가져오기에서는 화면 preview를 위한 값과 원본 색상 정의를 구분해 표현의 결과와 출판 의미를 모두 남겨야 했습니다.Constraint migration가져온 문서의 판형을 바꾸는 일은 모든 좌표에 같은 scale을 곱하는 변환이 아니었습니다. 텍스트 흐름은 새 폭에서 다시 최적화되어야 하고, 로고와 사진처럼 물리 위치가 중요한 객체는 상대 좌표나 anchor를 유지해야 합니다. 배경과 margin guide는 이전 geometry를 확대하는 것이 아니라 새 page constraint에서 다시 생성해야 합니다. 객체마다 보존해야 할 불변식이 달랐습니다.이를 constraint migration으로 보았습니다. 이전 해 `x_old`는 이전 제약 `C_old`를 만족하지만 새 제약 `C_new`에는 맞지 않습니다. 목표는 `C_new`를 만족하는 해 중에서 `x_old`와 의미적으로 가장 가까운 `x_new`를 찾는 것입니다. 거리 함수에는 위치 변화, 크기 변화, reading order 위반과 style 손실을 서로 다른 가중치로 넣을 수 있습니다.자유 배치 객체에서는 정규화 좌표가 유용합니다. 이전 content box 안의 위치를 `[0,1]²`의 상대 좌표로 바꾸면 새 box에서도 비례 위치를 복원할 수 있습니다. 그러나 텍스트와 반복 요소에는 이 방식이 맞지 않습니다. 텍스트는 원문과 스타일을 새 폭에서 다시 조판하고, 반복 요소는 새 template context에서 평가해야 합니다. 하나의 변환 공식보다 객체 의미에 맞는 constraint가 중요했습니다.해가 존재하지 않는 경우도 고려해야 합니다. 최소 크기를 가진 객체들이 새 페이지에 모두 들어갈 수 없거나 keep constraint가 서로 충돌할 수 있습니다. 이때는 제약을 우선순위에 따라 완화하고 어떤 조건이 깨졌는지 기록해야 합니다. hard constraint와 soft constraint를 구분하지 않으면 solver가 침묵한 채 잘못된 배치를 만들 수 있습니다.문서 전체를 바꾸는 변환은 transaction으로 다뤄야 했습니다. 일부 페이지에만 새 규격이 적용된 중간 상태는 유효한 문서가 아닙니다. 새 해가 모든 불변식을 통과한 뒤에만 이전 해를 교체하고, 실패하면 완전한 이전 revision을 유지합니다. 판형 변경은 좌표 수정이 아니라 전체 constraint system을 새로 푸는 작업으로 정리되었습니다.Import engine 완성이 단계에서 레이아웃 에디터는 서로 다른 문서를 하나의 편집 모델로 흡수할 수 있게 되었습니다. 입력은 포맷별 parser에서 syntax tree가 되고, symbol resolution과 graph linking을 거쳐 provenance가 있는 공통 IR로 내려왔습니다. 흐름형 문서의 cascade와 fragmentation, 고정 조판의 typed reference graph와 affine geometry가 같은 compiler pipeline 안에서 각자의 의미를 유지했습니다.가져온 결과는 일회성 화면이 아니었습니다. 렌더링 tree 대신 durable IR을 보존하고, style의 원인과 computed 결과, 논리 원문과 물리 fragment를 분리했습니다. 판형 변경도 과거 geometry를 억지로 확대하지 않고 새 constraint에서 가장 가까운 해를 다시 구하도록 정리했습니다.여러 문서 구조와 CSS, 다양한 글꼴과 색 공간, 긴 원고와 혼합 콘텐츠, 중첩 transform과 불완전한 참조를 조합해 반복 검수했습니다. 저장과 복원, 판형 변경 뒤에도 reading order와 style provenance, 자산 관계와 page geometry의 의미가 유지되는 것을 모두 확인했습니다. 서로 다른 출판 언어를 편집 가능한 페이지로 번역하는 import engine은 완전하게 구현되고 안정화되었습니다.이전글목록으로다음글저작권 고시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