Font의 Vector와 Bezier Curve

폰트의 벡터좌표를 얻어올 방법을 찾던중 opentype.js 라고하는 라이브러리를 찾게되었다

https://github.com/opentypejs/opentype.js

woff나 ttf등의 폰트를 이용해서 좌표값을 얻어낼 수 있었다

폰트에서 곡선의 표현은 Bezier Curve를 이용하게되며 곡선좌표에는 이것을 위한 좌표들이 포함되어 있다

font.getPath 함수의 리턴값은 Object이며 이것의 속성중 commands가 있는데 내용은 아래와 같다

배열형태이며 요소는 Object로 이루어져있고 Object의 type에 따라 좌표의 성격이 구분된다

M: 새로운 덩어리의 시작 (여기서 덩어리란 예를들어 "양" 이라는 글자를 예로들면 세덩어리이다)

C, Q: 곡선을 의미하며 x1,x2,y1,y2등 숫자가 붙은 좌표들은 Bezier Curve에서 곡선을 만드는 힘을 의미한다. Bezier Curve를 계산하기 위한 포인트의 순서로는 [이전좌표, [x1,y1], [x2,y2], [x,y]] 이다

L: 그냥 라인이다. 이전좌표에서 현좌표로 선하나 그으면 된다

Z: 덩어리를 끝맺는다. 여기에는 좌표는 없다

 

Ww0KICB7DQogICAgInR5cGUiOiAiTSIsDQogICAgIngiOiA2OS4yMzYsDQogICAgInkiOiA4My44MTE5OTk5OTk5OTk5OA0KICB9LA0KICB7DQogICAgInR5cGUiOiAiQyIsDQogICAgIngxIjogNjcuNDE0LA0KICAgICJ5MSI6IDc3Ljg5MDUsDQogICAgIngyIjogNjguNTUyNzUsDQogICAgInkyIjogNzMuNzkxLA0KICAgICJ4IjogNzUuODQwNzUsDQogICAgInkiOiA2OS45MTkyNQ0KICB9LA0KICB7DQogICAgInR5cGUiOiAiTCIsDQogICAgIngiOiAxMC43MDQyNSwNCiAgICAieSI6IDUzLjI5MzQ5OTk5OTk5OTk5NQ0KICB9LA0KICB7DQogICAgInR5cGUiOiAiUSIsDQogICAgIngxIjogMTMyLjU1MDUsDQogICAgInkxIjogNTYuNzA5NzQ5OTk5OTk5OTg1LA0KICAgICJ4MiI6IDEyOC45MDY1LA0KICAgICJ5MiI6IDU5LjQ0Mjc0OTk5OTk5OTk5LA0KICAgICJ4IjogMTI3LjMxMjI1LA0KICAgICJ5IjogNjQuMjI1NDk5OTk5OTk5OTgNCiAgfSwNCiAgew0KICAgICJ0eXBlIjogIkwiLA0KICAgICJ4IjogOTMuMTQ5NzUsDQogICAgInkiOiAxNjYuNzEzDQogIH0sDQogIHsNCiAgICAidHlwZSI6ICJaIg0KICB9LA0KICB7DQogICAgInR5cGUiOiAiTSIsDQogICAgIngiOiAyNzguOTkzNzUsDQogICAgInkiOiAxMzUuOTY2NzUNCiAgfSwNCiAgew0KICAgICJ0eXBlIjogIkwiLA0KICAgICJ4IjogMjc4Ljk5Mzc1LA0KICAgICJ5IjogMjE3LjcyODk5OTk5OTk5OTk4DQogIH0sDQogIHsNCiAgICAidHlwZSI6ICJDIiwNCiAgICAieDEiOiAyNzguOTkzNzUsDQogICAgInkxIjogMjI2LjYxMTI1LA0KICAgICJ4MiI6IDI4NS44MjYyNSwNCiAgICAieTIiOiAyMzEuMTY2MjUsDQogICAgIngiOiAyOTcuNjY5MjUsDQogICAgInkiOiAyMzEuMTY2MjUNCiAgfSwNCiAgew0KICAgICJ0eXBlIjogIloiDQogIH0NCl0=
[ { "type": "M", "x": 69.236, "y": 83.81199999999998 }, { "type": "C", "x1": 67.414, "y1": 77.8905, "x2": 68.55275, "y2": 73.791, "x": 75.84075, "y": 69.91925 }, { "type": "L", "x": 10.70425, "y": 53.293499999999995 }, { "type": "Q", "x1": 132.5505, "y1": 56.709749999999985, "x2": 128.9065, "y2": 59.44274999999999, "x": 127.31225, "y": 64.22549999999998 }, { "type": "L", "x": 93.14975, "y": 166.713 }, { "type": "Z" }, { "type": "M", "x": 278.99375, "y": 135.96675 }, { "type": "L", "x": 278.99375, "y": 217.72899999999998 }, { "type": "C", "x1": 278.99375, "y1": 226.61125, "x2": 285.82625, "y2": 231.16625, "x": 297.66925, "y": 231.16625 }, { "type": "Z" } ]

 

Bezier Curve를 만들어낼때 보간을 해주어야한다 보간을 세밀히 해줄수록 곡선은 부드러워진다

폰트는 크기가 커질수록 보간이 세밀해진다

아래는 보간의 정도에 따른 차이를 나타냈다

 

구현코드

d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ2xvYWQnLCBlID0+IHsNCiAgIGxldCBzY3JlZW5zaXplID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcignbWFpbicpLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpOw0KICAgZnVuY3Rpb24gRG90KHByb3BzLCBkcmF3KSB7DQogICAgICB0aGlzLmxpbmUgPSBwcm9wcy5saW5lOw0KICAgICAgdGhpcy5wcmV2bGluZSA9IHByb3BzLnByZXZsaW5lOw0KICAgICAgdGhpcy54ID0gcHJvcHMueDsNCiAgICAgIHRoaXMueSA9IHByb3BzLnk7DQogICAgICB0aGlzLm9yaWdpbmFsX3ggPSBwcm9wcy54Ow0KICAgICAgdGhpcy5vcmlnaW5hbF95ID0gcHJvcHMueTsNCiAgICAgIHRoaXMuc2l6ZSA9IHByb3BzLnNpemUgfHwgMTA7DQogICAgICB2YXIgY2lyY2xlID0gZHJhdy5jaXJjbGUodGhpcy5zaXplKS5maWxsKCcjMDAwJykubW92ZSgNCiAgICAgICAgIHRoaXMueCAtICh0aGlzLnNpemUgLyAyKSwNCiAgICAgICAgIHRoaXMueSAtICh0aGlzLnNpemUgLyAyKQ0KICAgICAgKQ0KICAgICAgdGhpcy5zZXRDb2xvciA9IGMgPT4gew0KICAgICAgICAgY2lyY2xlLmZpbGwoYyk7DQogICAgICB9DQogICAgICB0aGlzLnNldFggPSBjID0+IHsNCiAgICAgICAgIHRoaXMueCA9IGM7DQogICAgICAgICBjaXJjbGUueCh0aGlzLnggLSAodGhpcy5zaXplIC8gMikpDQogICAgICAgICBpZiAodGhpcy5saW5lKSB7DQogICAgICAgICAgICBsZXQgcGF0aCA9IHRoaXMubGluZS5hcnJheSgpOw0KICAgICAgICAgICAgdGhpcy5saW5lLnBsb3QoW1t0aGlzLngsIHRoaXMueV0sIHBhdGhbMV1dKTsNCiAgICAgICAgIH0NCiAgICAgICAgIGlmICh0aGlzLnByZXZsaW5lKSB7DQogICAgICAgICAgICBsZXQgcGF0aCA9IHRoaXMucHJldmxpbmUuYXJyYXkoKTsNCiAgICAgICAgICAgIHRoaXMucHJldmxpbmUucGxvdChbcGF0aFswXSwgW3RoaXMueCwgdGhpcy55XV0pOw0KICAgICAgICAgfQ0KICAgICAgfQ0KICAgICAgdGhpcy5zZXRZID0gYyA9PiB7DQogICAgICAgICB0aGlzLnkgPSBjOw0KICAgICAgICAgY2lyY2xlLnkodGhpcy55IC0gKHRoaXMuc2l6ZSAvIDIpKTsNCiAgICAgICAgIGlmICh0aGlzLmxpbmUpIHsNCiAgICAgICAgICAgIGxldCBwYXRoID0gdGhpcy5saW5lLmFycmF5KCk7DQogICAgICAgICAgICB0aGlzLmxpbmUucGxvdChbW3RoaXMueCwgdGhpcy55XSwgcGF0aFsxXV0pOw0KICAgICAgICAgfQ0KICAgICAgICAgaWYgKHRoaXMucHJldmxpbmUpIHsNCiAgICAgICAgICAgIGxldCBwYXRoID0gdGhpcy5wcmV2bGluZS5hcnJheSgpOw0KICAgICAgICAgICAgdGhpcy5wcmV2bGluZS5wbG90KFtwYXRoWzBdLCBbdGhpcy54LCB0aGlzLnldXSk7DQogICAgICAgICB9DQogICAgICB9DQogICB9DQogICBmdW5jdGlvbiBvcGVudHlwZV90b19kb3RsaXN0KHBhdGgsIGludGVycG9sYXRlX3JhdGUpIHsNCiAgICAgIGZ1bmN0aW9uIHBpY2tNaWQoZG90MSwgZG90MiwgdCkgew0KICAgICAgICAgbGV0IHggPSAoZG90MS54ICsgKGRvdDIueCAtIGRvdDEueCkgKiB0KTsNCiAgICAgICAgIGxldCB5ID0gKGRvdDEueSArIChkb3QyLnkgLSBkb3QxLnkpICogdCk7DQogICAgICAgICByZXR1cm4geyB4LCB5IH0NCiAgICAgIH0NCiAgICAgIGZ1bmN0aW9uIGJlemllckN1cnZlKHBhdGgsIHQpIHsNCiAgICAgICAgIHdoaWxlICh0cnVlKSB7DQogICAgICAgICAgICBsZXQgc3ViID0gW107DQogICAgICAgICAgICBwYXRoLmZvckVhY2gobGluZSA9PiB7DQogICAgICAgICAgICAgICBzdWIucHVzaChwaWNrTWlkKGxpbmVbMF0sIGxpbmVbMV0sIHQpKTsNCiAgICAgICAgICAgIH0pOw0KICAgICAgICAgICAgbGV0IG5ld2xpbmUgPSBzdWIubWFwKChkb3QsIGlkeCkgPT4gew0KICAgICAgICAgICAgICAgaWYgKHN1YltpZHggKyAxXSkgew0KICAgICAgICAgICAgICAgICAgcmV0dXJuIFtkb3QsIHN1YltpZHggKyAxXV07DQogICAgICAgICAgICAgICB9DQogICAgICAgICAgICB9KS5maWx0ZXIobGluZSA9PiBsaW5lKTsNCiAgICAgICAgICAgIHBhdGggPSBuZXdsaW5lOw0KICAgICAgICAgICAgaWYgKHBhdGgubGVuZ3RoID09PSAxKSB7DQogICAgICAgICAgICAgICByZXR1cm4gcGlja01pZChwYXRoWzBdWzBdLCBwYXRoWzBdWzFdLCB0KQ0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgaWYgKHBhdGgubGVuZ3RoID09PSAwKSB7DQogICAgICAgICAgICAgICBicmVhazsNCiAgICAgICAgICAgIH0NCiAgICAgICAgIH0NCiAgICAgIH0NCiAgICAgIGludGVycG9sYXRlX3JhdGUgPSBpbnRlcnBvbGF0ZV9yYXRlIHx8IDU7DQogICAgICBsZXQgcHJldmRvdDsNCiAgICAgIGxldCBwYXRocyA9IFtdOw0KICAgICAgbGV0IGRvdHM7DQogICAgICBwYXRoLmNvbW1hbmRzLmZvckVhY2goaW5mID0+IHsNCiAgICAgICAgIGxldCBjdXJyZW50ZG90ID0geyB4OiBpbmYueCwgeTogaW5mLnksIH07DQogICAgICAgICBpZiAoaW5mLnR5cGUgPT09ICdNJykgew0KICAgICAgICAgICAgZG90cyA9IFtdOw0KICAgICAgICAgICAgZG90cy5wdXNoKGN1cnJlbnRkb3QpOw0KICAgICAgICAgfSBlbHNlIGlmIChpbmYudHlwZSA9PT0gJ0wnKSB7DQogICAgICAgICAgICBkb3RzLnB1c2goY3VycmVudGRvdCkNCiAgICAgICAgIH0gZWxzZSBpZiAoaW5mLnR5cGUgPT09ICdDJyB8fCBpbmYudHlwZSA9PT0gJ1EnKSB7DQogICAgICAgICAgICBsZXQgbnByZXYgPSBwcmV2ZG90Ow0KICAgICAgICAgICAgbGV0IGJsaW5lID0gW107DQogICAgICAgICAgICBsZXQgY291bnQgPSAxOw0KICAgICAgICAgICAgd2hpbGUgKHRydWUpIHsNCiAgICAgICAgICAgICAgIGxldCB4ID0gYHgke2NvdW50fWA7DQogICAgICAgICAgICAgICBsZXQgeSA9IGB5JHtjb3VudH1gOw0KICAgICAgICAgICAgICAgaWYgKGluZi5oYXNPd25Qcm9wZXJ0eSh4KSkgew0KICAgICAgICAgICAgICAgICAgbGV0IGRmZSA9IHsgeDogaW5mW3hdLCB5OiBpbmZbeV0gfTsNCiAgICAgICAgICAgICAgICAgIGJsaW5lLnB1c2goW25wcmV2LCBkZmVdKTsNCiAgICAgICAgICAgICAgICAgIG5wcmV2ID0gZGZlOw0KICAgICAgICAgICAgICAgfSBlbHNlIHsgYnJlYWs7IH0NCiAgICAgICAgICAgICAgIGNvdW50Kys7DQogICAgICAgICAgICB9DQogICAgICAgICAgICBibGluZS5wdXNoKFtucHJldiwgY3VycmVudGRvdF0pOw0KICAgICAgICAgICAgbGV0IHN0ZXAgPSBpbnRlcnBvbGF0ZV9yYXRlOw0KICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPD0gMTsgaSArPSAxIC8gc3RlcCkgew0KICAgICAgICAgICAgICAgZG90cy5wdXNoKGJlemllckN1cnZlKGJsaW5lLCBpKSk7DQogICAgICAgICAgICB9DQogICAgICAgICB9IGVsc2UgaWYgKGluZi50eXBlID09PSAnWicpIHsNCiAgICAgICAgICAgIHBhdGhzLnB1c2goZG90cyk7DQogICAgICAgICB9DQogICAgICAgICBwcmV2ZG90ID0gY3VycmVudGRvdDsNCiAgICAgIH0pOw0KDQogICAgICBsZXQgYm91bmRpbmcgPSBwYXRoLmdldEJvdW5kaW5nQm94KCk7DQogICAgICBmdW5jdGlvbiBhZGRQb3MoeyB4LCB5IH0pIHsNCiAgICAgICAgIGlmICh4KSB7IGJvdW5kaW5nLngxICs9IHg7IGJvdW5kaW5nLngyICs9IHg7IH0NCiAgICAgICAgIGlmICh5KSB7IGJvdW5kaW5nLnkxICs9IHk7IGJvdW5kaW5nLnkyICs9IHk7IH0NCiAgICAgICAgIHBhdGhzLmZvckVhY2gocGF0aCA9PiB7DQogICAgICAgICAgICBwYXRoLmZvckVhY2goZG90ID0+IHsNCiAgICAgICAgICAgICAgIGlmICh4KSB7IGRvdC54ICs9IHg7IH0NCiAgICAgICAgICAgICAgIGlmICh5KSB7IGRvdC55ICs9IHk7IH0NCiAgICAgICAgICAgIH0pOw0KICAgICAgICAgfSk7DQogICAgICB9DQogICAgICBmdW5jdGlvbiBnZXRQb3MoKSB7DQogICAgICAgICByZXR1cm4gew0KICAgICAgICAgICAgeDogYm91bmRpbmcueDEsDQogICAgICAgICAgICB5OiBib3VuZGluZy55MSwNCiAgICAgICAgIH0NCiAgICAgIH0NCiAgICAgIGZ1bmN0aW9uIGdldFdpZHRoKCkgew0KICAgICAgICAgcmV0dXJuIGJvdW5kaW5nLngyIC0gYm91bmRpbmcueDE7DQogICAgICB9DQogICAgICBmdW5jdGlvbiBnZXRIZWlnaHQoKSB7DQogICAgICAgICByZXR1cm4gYm91bmRpbmcueTIgLSBib3VuZGluZy55MTsNCiAgICAgIH0NCiAgICAgIHJldHVybiB7DQogICAgICAgICBkb3Rncm91cDogcGF0aHMsDQogICAgICAgICBib3VuZGluZywNCiAgICAgICAgIGFkZFBvcywNCiAgICAgICAgIGdldFBvcywNCiAgICAgICAgIGdldFdpZHRoLA0KICAgICAgICAgZ2V0SGVpZ2h0DQogICAgICB9DQogICB9DQogICBvcGVudHlwZS5sb2FkKCdodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvZ2gvcHJvamVjdG5vb25udS9ub29uZm9udHNfb25lQDEuMC9CaW5nZ3JhZS1Cb2xkLndvZmYnLCBmdW5jdGlvbiAoZXJyLCBmb250KSB7DQogICAgICBpZiAoZXJyKSB7DQogICAgICAgICBhbGVydCgnRm9udCBjb3VsZCBub3QgYmUgbG9hZGVkOiAnICsgZXJyKTsNCiAgICAgIH0gZWxzZSB7DQogICAgICAgICBmdW5jdGlvbiBhZGR0ZXh0KHRleHQsIGlkLCBpbnRlcnBvbHkpIHsNCiAgICAgICAgICAgIGxldCBkb3RzaXplID0gc2NyZWVuc2l6ZS53aWR0aCAqIDAuMDAzOw0KICAgICAgICAgICAgbGV0IGludGVyID0gaW50ZXJwb2x5ID8gaW50ZXJwb2x5IDogc2NyZWVuc2l6ZS53aWR0aCAvIDMwMDsNCiAgICAgICAgICAgIGxldCBmb250c2l6ZSA9IHNjcmVlbnNpemUud2lkdGggLyAyIC8gMjsNCiAgICAgICAgICAgIGxldCB4ID0gMDsNCiAgICAgICAgICAgIGxldCB5ID0gMCArIGZvbnRzaXplOw0KICAgICAgICAgICAgY29uc3QgcGF0aCA9IGZvbnQuZ2V0UGF0aCh0ZXh0LCB4LCB5LCBmb250c2l6ZSk7DQogICAgICAgICAgICBsZXQgZG90bGlzdCA9IG9wZW50eXBlX3RvX2RvdGxpc3QocGF0aCwgaW50ZXIpOw0KICAgICAgICAgICAgZG90bGlzdC5hZGRQb3MoeyB4OiAtZG90bGlzdC5nZXRQb3MoKS54LCB5OiAtZG90bGlzdC5nZXRQb3MoKS55IH0pOw0KICAgICAgICAgICAgbGV0IGNvbnRhaW5lciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOw0KICAgICAgICAgICAgZG9jdW1lbnQucXVlcnlTZWxlY3RvcihpZCkuYXBwZW5kQ2hpbGQoY29udGFpbmVyKTsNCiAgICAgICAgICAgIGNvbnN0IGRyYXcgPSBTVkcoKS5hZGRUbyhjb250YWluZXIpLnNpemUoc2NyZWVuc2l6ZS53aWR0aCwgZG90bGlzdC5nZXRIZWlnaHQoKSAqIDEuNSk7DQogICAgICAgICAgICBkcmF3LnJlY3Qoc2NyZWVuc2l6ZS53aWR0aCwgc2NyZWVuc2l6ZS53aWR0aCkuYXR0cih7IGZpbGw6ICcjZWVlJyB9KTsNCiAgICAgICAgICAgIGRvdGxpc3QuYWRkUG9zKHsNCiAgICAgICAgICAgICAgIHk6ICgoZG90bGlzdC5nZXRIZWlnaHQoKSAqIDEuNSkgLSBkb3RsaXN0LmdldEhlaWdodCgpKSAqIDAuNSwNCiAgICAgICAgICAgICAgIHg6IChzY3JlZW5zaXplLndpZHRoIC0gZG90bGlzdC5nZXRXaWR0aCgpKSAqIDAuNSwNCiAgICAgICAgICAgIH0pOw0KICAgICAgICAgICAgbGV0IHJlY3QgPSBkb3RsaXN0LmJvdW5kaW5nOw0KICAgICAgICAgICAgbmV3IERvdCh7IHg6IHJlY3QueDEsIHk6IHJlY3QueTEsIHNpemU6IGRvdHNpemUgKiAzIH0sIGRyYXcpOw0KICAgICAgICAgICAgbmV3IERvdCh7IHg6IHJlY3QueDIsIHk6IHJlY3QueTEsIHNpemU6IGRvdHNpemUgKiAzIH0sIGRyYXcpOw0KICAgICAgICAgICAgbmV3IERvdCh7IHg6IHJlY3QueDIsIHk6IHJlY3QueTIsIHNpemU6IGRvdHNpemUgKiAzIH0sIGRyYXcpOw0KICAgICAgICAgICAgbmV3IERvdCh7IHg6IHJlY3QueDEsIHk6IHJlY3QueTIsIHNpemU6IGRvdHNpemUgKiAzIH0sIGRyYXcpOw0KICAgICAgICAgICAgbGV0IGNibGlzdCA9IFtdOw0KICAgICAgICAgICAgZG90bGlzdC5kb3Rncm91cC5mb3JFYWNoKHBhdGggPT4gew0KICAgICAgICAgICAgICAgbGV0IHBsaW5lOw0KICAgICAgICAgICAgICAgbGV0IGZpcnN0X2RvdDsNCiAgICAgICAgICAgICAgIHBhdGguZm9yRWFjaCgoZG90LCBpZHgpID0+IHsNCiAgICAgICAgICAgICAgICAgIGxldCBuZXh0ID0gaWR4ICsgMTsNCiAgICAgICAgICAgICAgICAgIGlmICghcGF0aFtpZHggKyAxXSkgeyBuZXh0ID0gMDsgfQ0KICAgICAgICAgICAgICAgICAgdmFyIGxpbmVzdmcgPSBkcmF3LmxpbmUoZG90LngsIGRvdC55LCBwYXRoW25leHRdLngsIHBhdGhbbmV4dF0ueSk7DQogICAgICAgICAgICAgICAgICBsaW5lc3ZnLnN0cm9rZSh7IGNvbG9yOiAncmVkJywgd2lkdGg6IHNjcmVlbnNpemUud2lkdGggKiAwLjAwMiwgbGluZWNhcDogJ3JvdW5kJyB9KQ0KICAgICAgICAgICAgICAgICAgbGV0IF9kb3QgPSBuZXcgRG90KHsgLi4uZG90LCBzaXplOiBkb3RzaXplLCBsaW5lOiBsaW5lc3ZnLCBwcmV2bGluZTogcGxpbmUgfSwgZHJhdyk7DQogICAgICAgICAgICAgICAgICBpZiAoIWZpcnN0X2RvdCkgew0KICAgICAgICAgICAgICAgICAgICAgZmlyc3RfZG90ID0gX2RvdDsNCiAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgIGxldCByZG4gPSBNYXRoLnJhbmRvbSgpOw0KICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gYW5pKCkgew0KICAgICAgICAgICAgICAgICAgICAgX2RvdC5zZXRZKF9kb3Qub3JpZ2luYWxfeSArIChNYXRoLnJhbmRvbSgpICogc2NyZWVuc2l6ZS53aWR0aCAqIDAuMDA3KSAqIHJkbikNCiAgICAgICAgICAgICAgICAgICAgIF9kb3Quc2V0WChfZG90Lm9yaWdpbmFsX3ggKyAoTWF0aC5yYW5kb20oKSAqIHNjcmVlbnNpemUud2lkdGggKiAwLjAwNykgKiByZG4pDQogICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICBjYmxpc3QucHVzaChhbmkpOw0KICAgICAgICAgICAgICAgICAgcGxpbmUgPSBsaW5lc3ZnOw0KICAgICAgICAgICAgICAgfSk7DQogICAgICAgICAgICAgICBmaXJzdF9kb3QucHJldmxpbmUgPSBwbGluZTsNCiAgICAgICAgICAgIH0pOw0KICAgICAgICAgICAgZnVuY3Rpb24gYW5pbWF0aW9uKCkgew0KICAgICAgICAgICAgICAgY2JsaXN0LmZvckVhY2goYW5pID0+IHsNCiAgICAgICAgICAgICAgICAgIGFuaSgpOw0KICAgICAgICAgICAgICAgfSkNCiAgICAgICAgICAgICAgIHJlcXVlc3RBbmltYXRpb25GcmFtZShhbmltYXRpb24pOw0KICAgICAgICAgICAgfQ0KICAgICAgICAgICAgaWYgKGlkID09PSAnI2NvbnRhaW5lcl93aW50ZXInKSB7DQogICAgICAgICAgICAgICBhbmltYXRpb24oKTsNCiAgICAgICAgICAgIH0NCiAgICAgICAgIH0NCiAgICAgICAgIGFkZHRleHQoJ1dpbnRlcicsICcjY29udGFpbmVyX25vcm1hbDEnLCAxKTsNCiAgICAgICAgIGFkZHRleHQoJ1dpbnRlcicsICcjY29udGFpbmVyX25vcm1hbDInLCAyKTsNCiAgICAgICAgIGFkZHRleHQoJ1dpbnRlcicsICcjY29udGFpbmVyX25vcm1hbDMnLCAxMCk7DQogICAgICAgICBhZGR0ZXh0KCdXaW50ZXInLCAnI2NvbnRhaW5lcl93aW50ZXInKTsNCiAgICAgIH0NCiAgIH0pOw0KfSk7
window.addEventListener('load', e => { let screensize = document.querySelector('main').getBoundingClientRect(); function Dot(props, draw) { this.line = props.line; this.prevline = props.prevline; this.x = props.x; this.y = props.y; this.original_x = props.x; this.original_y = props.y; this.size = props.size || 10; var circle = draw.circle(this.size).fill('#000').move( this.x - (this.size / 2), this.y - (this.size / 2) ) this.setColor = c => { circle.fill(c); } this.setX = c => { this.x = c; circle.x(this.x - (this.size / 2)) if (this.line) { let path = this.line.array(); this.line.plot([[this.x, this.y], path[1]]); } if (this.prevline) { let path = this.prevline.array(); this.prevline.plot([path[0], [this.x, this.y]]); } } this.setY = c => { this.y = c; circle.y(this.y - (this.size / 2)); if (this.line) { let path = this.line.array(); this.line.plot([[this.x, this.y], path[1]]); } if (this.prevline) { let path = this.prevline.array(); this.prevline.plot([path[0], [this.x, this.y]]); } } } function opentype_to_dotlist(path, interpolate_rate) { function pickMid(dot1, dot2, t) { let x = (dot1.x + (dot2.x - dot1.x) * t); let y = (dot1.y + (dot2.y - dot1.y) * t); return { x, y } } function bezierCurve(path, t) { while (true) { let sub = []; path.forEach(line => { sub.push(pickMid(line[0], line[1], t)); }); let newline = sub.map((dot, idx) => { if (sub[idx + 1]) { return [dot, sub[idx + 1]]; } }).filter(line => line); path = newline; if (path.length === 1) { return pickMid(path[0][0], path[0][1], t) } if (path.length === 0) { break; } } } interpolate_rate = interpolate_rate || 5; let prevdot; let paths = []; let dots; path.commands.forEach(inf => { let currentdot = { x: inf.x, y: inf.y, }; if (inf.type === 'M') { dots = []; dots.push(currentdot); } else if (inf.type === 'L') { dots.push(currentdot) } else if (inf.type === 'C' || inf.type === 'Q') { let nprev = prevdot; let bline = []; let count = 1; while (true) { let x = `x${count}`; let y = `y${count}`; if (inf.hasOwnProperty(x)) { let dfe = { x: inf[x], y: inf[y] }; bline.push([nprev, dfe]); nprev = dfe; } else { break; } count++; } bline.push([nprev, currentdot]); let step = interpolate_rate; for (let i = 0; i <= 1; i += 1 / step) { dots.push(bezierCurve(bline, i)); } } else if (inf.type === 'Z') { paths.push(dots); } prevdot = currentdot; }); let bounding = path.getBoundingBox(); function addPos({ x, y }) { if (x) { bounding.x1 += x; bounding.x2 += x; } if (y) { bounding.y1 += y; bounding.y2 += y; } paths.forEach(path => { path.forEach(dot => { if (x) { dot.x += x; } if (y) { dot.y += y; } }); }); } function getPos() { return { x: bounding.x1, y: bounding.y1, } } function getWidth() { return bounding.x2 - bounding.x1; } function getHeight() { return bounding.y2 - bounding.y1; } return { dotgroup: paths, bounding, addPos, getPos, getWidth, getHeight } } opentype.load('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/Binggrae-Bold.woff', function (err, font) { if (err) { alert('Font could not be loaded: ' + err); } else { function addtext(text, id, interpoly) { let dotsize = screensize.width * 0.003; let inter = interpoly ? interpoly : screensize.width / 300; let fontsize = screensize.width / 2 / 2; let x = 0; let y = 0 + fontsize; const path = font.getPath(text, x, y, fontsize); let dotlist = opentype_to_dotlist(path, inter); dotlist.addPos({ x: -dotlist.getPos().x, y: -dotlist.getPos().y }); let container = document.createElement('div'); document.querySelector(id).appendChild(container); const draw = SVG().addTo(container).size(screensize.width, dotlist.getHeight() * 1.5); draw.rect(screensize.width, screensize.width).attr({ fill: '#eee' }); dotlist.addPos({ y: ((dotlist.getHeight() * 1.5) - dotlist.getHeight()) * 0.5, x: (screensize.width - dotlist.getWidth()) * 0.5, }); let rect = dotlist.bounding; new Dot({ x: rect.x1, y: rect.y1, size: dotsize * 3 }, draw); new Dot({ x: rect.x2, y: rect.y1, size: dotsize * 3 }, draw); new Dot({ x: rect.x2, y: rect.y2, size: dotsize * 3 }, draw); new Dot({ x: rect.x1, y: rect.y2, size: dotsize * 3 }, draw); let cblist = []; dotlist.dotgroup.forEach(path => { let pline; let first_dot; path.forEach((dot, idx) => { let next = idx + 1; if (!path[idx + 1]) { next = 0; } var linesvg = draw.line(dot.x, dot.y, path[next].x, path[next].y); linesvg.stroke({ color: 'red', width: screensize.width * 0.002, linecap: 'round' }) let _dot = new Dot({ ...dot, size: dotsize, line: linesvg, prevline: pline }, draw); if (!first_dot) { first_dot = _dot; } let rdn = Math.random(); function ani() { _dot.setY(_dot.original_y + (Math.random() * screensize.width * 0.007) * rdn) _dot.setX(_dot.original_x + (Math.random() * screensize.width * 0.007) * rdn) } cblist.push(ani); pline = linesvg; }); first_dot.prevline = pline; }); function animation() { cblist.forEach(ani => { ani(); }) requestAnimationFrame(animation); } if (id === '#container_winter') { animation(); } } addtext('Winter', '#container_normal1', 1); addtext('Winter', '#container_normal2', 2); addtext('Winter', '#container_normal3', 10); addtext('Winter', '#container_winter'); } }); });