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');
}
});
});