diff --git a/pugconfig.js b/pugconfig.js index 3bd1f927..25421535 100644 --- a/pugconfig.js +++ b/pugconfig.js @@ -20,7 +20,10 @@ const entries = ['index.pug']; const assets = [ [websiteDir, buildDir, ['css/*.css', 'js/*.js', 'img/*.{svg,png}', 'fonts/*', '*.{svg,png,ico,xml,json}'], [], false], ['./source', buildDir, ['data/{colorbrewer,smithwalt}.json'], [], false], - ['./node_modules/rxjs/bundles/', `${buildDir}/js`, ['rxjs.umd.min.js'], [], false] + ['./node_modules/rxjs/bundles/', `${buildDir}/js`, ['rxjs.umd.min.js'], [], false], + ['./source/data/', `${buildDir}/data`, ['*'], [], false], + ['./source/data/opensansr144', `${buildDir}/data/opensansr144`, ['*'], [], false], + ['./source/data/verdana', `${buildDir}/data/verdana`, ['*'], [], false], ]; diff --git a/source/data/opensansr144/opensansr144.fnt b/source/data/opensansr144/opensansr144.fnt new file mode 100644 index 00000000..c09ecb7a --- /dev/null +++ b/source/data/opensansr144/opensansr144.fnt @@ -0,0 +1,1845 @@ +info face=opensansr144 size=144 bold=0 italic=0 charset= unicode= stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=0,0 outline=0 +common lineHeight=196.1 base=135 ascent=110 descent=-34 scaleW=2048 scaleH=2048 pages=1 packed=0 +page id=0 file="opensansr144.png" +chars count=217 +char id=33 x=0 y=0 width=25 height=112 xoffset=14.69 yoffset=31.29 xadvance=38.46 page=0 chnl=15 +char id=34 x=25 y=0 width=47 height=45 xoffset=13.35 yoffset=31.29 xadvance=57.73 page=0 chnl=15 +char id=35 x=72 y=0 width=93 height=110 xoffset=7.59 yoffset=31.29 xadvance=93.02 page=0 chnl=15 +char id=36 x=165 y=0 width=71 height=125 xoffset=13.21 yoffset=24.82 xadvance=82.34 page=0 chnl=15 +char id=37 x=236 y=0 width=111 height=113 xoffset=11.31 yoffset=29.81 xadvance=118.55 page=0 chnl=15 +char id=38 x=347 y=0 width=104 height=113 xoffset=11.95 yoffset=29.67 xadvance=105.12 page=0 chnl=15 +char id=39 x=451 y=0 width=21 height=45 xoffset=13.35 yoffset=31.29 xadvance=31.85 page=0 chnl=15 +char id=40 x=472 y=0 width=40 height=133 xoffset=9.77 yoffset=31.29 xadvance=42.61 page=0 chnl=15 +char id=41 x=512 y=0 width=40 height=133 xoffset=8.29 yoffset=31.29 xadvance=42.61 page=0 chnl=15 +char id=42 x=552 y=0 width=75 height=72 xoffset=10.05 yoffset=24.68 xadvance=79.45 page=0 chnl=15 +char id=43 x=627 y=0 width=75 height=77 xoffset=11.31 yoffset=48.38 xadvance=82.34 page=0 chnl=15 +char id=44 x=702 y=0 width=29 height=43 xoffset=8.43 yoffset=117.35 xadvance=35.3 page=0 chnl=15 +char id=45 x=731 y=0 width=42 height=18 xoffset=9.91 yoffset=90.14 xadvance=46.34 page=0 chnl=15 +char id=46 x=773 y=0 width=25 height=27 xoffset=14.69 yoffset=117.07 xadvance=38.32 page=0 chnl=15 +char id=47 x=798 y=0 width=58 height=110 xoffset=5.41 yoffset=31.29 xadvance=52.88 page=0 chnl=15 +char id=48 x=856 y=0 width=76 height=113 xoffset=11.17 yoffset=29.67 xadvance=82.34 page=0 chnl=15 +char id=49 x=932 y=0 width=45 height=110 xoffset=17.22 yoffset=31.29 xadvance=82.34 page=0 chnl=15 +char id=50 x=977 y=0 width=75 height=112 xoffset=11.03 yoffset=29.81 xadvance=82.34 page=0 chnl=15 +char id=51 x=1052 y=0 width=75 height=113 xoffset=10.61 yoffset=29.81 xadvance=82.34 page=0 chnl=15 +char id=52 x=1127 y=0 width=84 height=111 xoffset=7.02 yoffset=30.73 xadvance=82.34 page=0 chnl=15 +char id=53 x=1211 y=0 width=72 height=112 xoffset=13.35 yoffset=31.29 xadvance=82.34 page=0 chnl=15 +char id=54 x=1283 y=0 width=75 height=113 xoffset=12.23 yoffset=29.81 xadvance=82.34 page=0 chnl=15 +char id=55 x=1358 y=0 width=76 height=110 xoffset=10.61 yoffset=31.29 xadvance=82.34 page=0 chnl=15 +char id=56 x=1434 y=0 width=75 height=113 xoffset=11.31 yoffset=29.81 xadvance=82.34 page=0 chnl=15 +char id=57 x=1509 y=0 width=75 height=113 xoffset=11.45 yoffset=29.81 xadvance=82.34 page=0 chnl=15 +char id=58 x=1584 y=0 width=25 height=89 xoffset=14.69 yoffset=55.05 xadvance=38.32 page=0 chnl=15 +char id=59 x=1609 y=0 width=31 height=105 xoffset=8.43 yoffset=55.05 xadvance=38.32 page=0 chnl=15 +char id=60 x=1640 y=0 width=75 height=78 xoffset=11.31 yoffset=46.83 xadvance=82.34 page=0 chnl=15 +char id=61 x=1715 y=0 width=73 height=46 xoffset=12.37 yoffset=64.13 xadvance=82.34 page=0 chnl=15 +char id=62 x=1788 y=0 width=75 height=78 xoffset=11.31 yoffset=46.83 xadvance=82.34 page=0 chnl=15 +char id=63 x=1863 y=0 width=64 height=114 xoffset=5.9 yoffset=29.81 xadvance=61.8 page=0 chnl=15 +char id=64 x=1927 y=0 width=120 height=123 xoffset=12.51 yoffset=31.43 xadvance=129.45 page=0 chnl=15 +char id=65 x=552 y=77 width=99 height=111 xoffset=4 yoffset=30.87 xadvance=91.13 page=0 chnl=15 +char id=66 x=702 y=43 width=79 height=110 xoffset=18.13 yoffset=31.29 xadvance=93.3 page=0 chnl=15 +char id=67 x=1640 y=78 width=85 height=113 xoffset=12.79 yoffset=29.81 xadvance=90.84 page=0 chnl=15 +char id=68 x=1725 y=78 width=90 height=110 xoffset=18.13 yoffset=31.29 xadvance=104.98 page=0 chnl=15 +char id=69 x=25 y=110 width=65 height=110 xoffset=18.13 yoffset=31.29 xadvance=80.09 page=0 chnl=15 +char id=70 x=90 y=110 width=65 height=110 xoffset=18.13 yoffset=31.29 xadvance=74.32 page=0 chnl=15 +char id=71 x=932 y=112 width=93 height=113 xoffset=12.79 yoffset=29.81 xadvance=104.84 page=0 chnl=15 +char id=72 x=1127 y=112 width=86 height=110 xoffset=18.13 yoffset=31.29 xadvance=106.24 page=0 chnl=15 +char id=73 x=451 y=45 width=20 height=110 xoffset=18.13 yoffset=31.29 xadvance=40.15 page=0 chnl=15 +char id=74 x=651 y=77 width=44 height=137 xoffset=-7.25 yoffset=31.29 xadvance=38.46 page=0 chnl=15 +char id=75 x=236 y=113 width=82 height=110 xoffset=18.13 yoffset=31.29 xadvance=88.38 page=0 chnl=15 +char id=76 x=781 y=110 width=65 height=110 xoffset=18.13 yoffset=31.29 xadvance=74.74 page=0 chnl=15 +char id=77 x=318 y=113 width=109 height=110 xoffset=18.13 yoffset=31.29 xadvance=130.01 page=0 chnl=15 +char id=78 x=1025 y=113 width=88 height=110 xoffset=18.13 yoffset=31.29 xadvance=108.56 page=0 chnl=15 +char id=79 x=1213 y=113 width=102 height=113 xoffset=12.79 yoffset=29.67 xadvance=112.15 page=0 chnl=15 +char id=80 x=1358 y=110 width=73 height=110 xoffset=18.13 yoffset=31.29 xadvance=86.7 page=0 chnl=15 +char id=81 x=1431 y=113 width=102 height=136 xoffset=12.79 yoffset=29.67 xadvance=112.15 page=0 chnl=15 +char id=82 x=846 y=113 width=80 height=110 xoffset=18.13 yoffset=31.29 xadvance=89.02 page=0 chnl=15 +char id=83 x=1533 y=113 width=72 height=113 xoffset=11.45 yoffset=29.81 xadvance=79.03 page=0 chnl=15 +char id=84 x=1815 y=114 width=85 height=110 xoffset=5.27 yoffset=31.29 xadvance=79.66 page=0 chnl=15 +char id=85 x=1900 y=123 width=86 height=112 xoffset=17.08 yoffset=31.29 xadvance=104.84 page=0 chnl=15 +char id=86 x=427 y=155 width=93 height=110 xoffset=4 yoffset=31.29 xadvance=85.71 page=0 chnl=15 +char id=87 x=1605 y=191 width=137 height=110 xoffset=5.9 yoffset=31.29 xadvance=133.31 page=0 chnl=15 +char id=88 x=520 y=188 width=89 height=110 xoffset=4.56 yoffset=31.29 xadvance=83.11 page=0 chnl=15 +char id=89 x=609 y=214 width=88 height=110 xoffset=4 yoffset=31.29 xadvance=80.65 page=0 chnl=15 +char id=90 x=155 y=125 width=78 height=110 xoffset=9.77 yoffset=31.29 xadvance=82.2 page=0 chnl=15 +char id=91 x=1315 y=113 width=40 height=133 xoffset=15.67 yoffset=31.29 xadvance=47.39 page=0 chnl=15 +char id=93 x=1986 y=123 width=40 height=133 xoffset=7.59 yoffset=31.29 xadvance=47.39 page=0 chnl=15 +char id=94 x=697 y=153 width=78 height=72 xoffset=7.45 yoffset=30.52 xadvance=78.05 page=0 chnl=15 +char id=95 x=1715 y=46 width=73 height=17 xoffset=3.72 yoffset=147.02 xadvance=64.55 page=0 chnl=15 +char id=96 x=25 y=45 width=35 height=31 xoffset=31.63 yoffset=23.77 xadvance=83.11 page=0 chnl=15 +char id=97 x=1742 y=188 width=69 height=87 xoffset=10.61 yoffset=55.76 xadvance=80.09 page=0 chnl=15 +char id=98 x=0 y=220 width=75 height=118 xoffset=16.38 yoffset=24.68 xadvance=88.24 page=0 chnl=15 +char id=99 x=75 y=220 width=63 height=87 xoffset=12.09 yoffset=55.62 xadvance=68.55 page=0 chnl=15 +char id=100 x=1355 y=220 width=75 height=118 xoffset=12.09 yoffset=24.68 xadvance=88.24 page=0 chnl=15 +char id=101 x=1113 y=222 width=73 height=87 xoffset=12.09 yoffset=55.62 xadvance=80.79 page=0 chnl=15 +char id=102 x=775 y=220 width=61 height=118 xoffset=6.04 yoffset=23.91 xadvance=48.8 page=0 chnl=15 +char id=103 x=233 y=223 width=80 height=121 xoffset=6.74 yoffset=55.62 xadvance=78.89 page=0 chnl=15 +char id=104 x=313 y=223 width=72 height=117 xoffset=16.38 yoffset=24.68 xadvance=88.38 page=0 chnl=15 +char id=105 x=2026 y=123 width=21 height=113 xoffset=15.39 yoffset=28.41 xadvance=36.42 page=0 chnl=15 +char id=106 x=385 y=223 width=41 height=148 xoffset=-3.8 yoffset=28.41 xadvance=36.42 page=0 chnl=15 +char id=107 x=836 y=223 width=69 height=117 xoffset=16.38 yoffset=24.68 xadvance=75.59 page=0 chnl=15 +char id=108 x=1186 y=222 width=19 height=117 xoffset=16.38 yoffset=24.68 xadvance=36.42 page=0 chnl=15 +char id=109 x=905 y=225 width=117 height=86 xoffset=16.38 yoffset=55.62 xadvance=133.95 page=0 chnl=15 +char id=110 x=1025 y=223 width=72 height=86 xoffset=16.38 yoffset=55.62 xadvance=88.38 page=0 chnl=15 +char id=111 x=1811 y=224 width=78 height=87 xoffset=12.09 yoffset=55.62 xadvance=86.98 page=0 chnl=15 +char id=112 x=697 y=225 width=75 height=121 xoffset=16.38 yoffset=55.62 xadvance=88.24 page=0 chnl=15 +char id=113 x=1205 y=226 width=75 height=121 xoffset=12.09 yoffset=55.62 xadvance=88.24 page=0 chnl=15 +char id=114 x=1533 y=226 width=52 height=86 xoffset=16.38 yoffset=55.62 xadvance=58.78 page=0 chnl=15 +char id=115 x=138 y=235 width=62 height=87 xoffset=11.45 yoffset=55.62 xadvance=68.7 page=0 chnl=15 +char id=116 x=1889 y=235 width=53 height=104 xoffset=6.18 yoffset=39.16 xadvance=50.84 page=0 chnl=15 +char id=117 x=1280 y=246 width=72 height=86 xoffset=15.53 yoffset=57.02 xadvance=88.38 page=0 chnl=15 +char id=118 x=1430 y=249 width=80 height=85 xoffset=4 yoffset=57.02 xadvance=72.14 page=0 chnl=15 +char id=119 x=426 y=298 width=116 height=85 xoffset=5.62 yoffset=57.02 xadvance=112.01 page=0 chnl=15 +char id=120 x=1942 y=256 width=77 height=85 xoffset=6.74 yoffset=57.02 xadvance=75.45 page=0 chnl=15 +char id=121 x=1585 y=301 width=80 height=119 xoffset=4.14 yoffset=57.02 xadvance=72.56 page=0 chnl=15 +char id=122 x=1742 y=275 width=63 height=85 xoffset=9.77 yoffset=57.02 xadvance=67.36 page=0 chnl=15 +char id=123 x=542 y=298 width=53 height=133 xoffset=8.29 yoffset=31.29 xadvance=54.56 page=0 chnl=15 +char id=124 x=200 y=235 width=18 height=152 xoffset=38.73 yoffset=24.68 xadvance=79.31 page=0 chnl=15 +char id=125 x=1665 y=301 width=53 height=133 xoffset=9.06 yoffset=31.29 xadvance=54.56 page=0 chnl=15 +char id=126 x=426 y=265 width=75 height=26 xoffset=11.31 yoffset=74.18 xadvance=82.34 page=0 chnl=15 +char id=127 x=1022 y=309 width=67 height=110 xoffset=17.57 yoffset=31.29 xadvance=86.41 page=0 chnl=15 +char id=8364 x=1089 y=309 width=85 height=113 xoffset=8.43 yoffset=29.81 xadvance=84.94 page=0 chnl=15 +char id=8218 x=1605 y=105 width=29 height=43 xoffset=8.43 yoffset=117.35 xadvance=35.3 page=0 chnl=15 +char id=402 x=905 y=311 width=67 height=146 xoffset=17.71 yoffset=29.81 xadvance=83.11 page=0 chnl=15 +char id=8222 x=75 y=307 width=54 height=43 xoffset=5.76 yoffset=117.35 xadvance=58.29 page=0 chnl=15 +char id=8230 x=595 y=324 width=99 height=27 xoffset=14.69 yoffset=117.07 xadvance=112.92 page=0 chnl=15 +char id=8224 x=1805 y=311 width=63 height=117 xoffset=12.65 yoffset=24.68 xadvance=72.28 page=0 chnl=15 +char id=8225 x=1510 y=312 width=64 height=117 xoffset=12.65 yoffset=24.68 xadvance=73.41 page=0 chnl=15 +char id=710 x=129 y=322 width=55 height=31 xoffset=22.84 yoffset=23.77 xadvance=85.22 page=0 chnl=15 +char id=8240 x=1280 y=338 width=167 height=113 xoffset=11.03 yoffset=29.81 xadvance=173.11 page=0 chnl=15 +char id=352 x=0 y=338 width=72 height=143 xoffset=11.45 yoffset=0 xadvance=79.03 page=0 chnl=15 +char id=8249 x=972 y=311 width=40 height=67 xoffset=9.77 yoffset=66.73 xadvance=43.8 page=0 chnl=15 +char id=338 x=772 y=340 width=123 height=113 xoffset=12.79 yoffset=29.67 xadvance=132.89 page=0 chnl=15 +char id=381 x=1868 y=341 width=78 height=142 xoffset=9.77 yoffset=0 xadvance=82.2 page=0 chnl=15 +char id=8216 x=520 y=133 width=29 height=43 xoffset=5.76 yoffset=31.29 xadvance=24.47 page=0 chnl=15 +char id=8217 x=1605 y=148 width=29 height=43 xoffset=5.76 yoffset=31.29 xadvance=24.47 page=0 chnl=15 +char id=8220 x=1447 y=334 width=54 height=43 xoffset=5.76 yoffset=31.29 xadvance=50.41 page=0 chnl=15 +char id=8221 x=313 y=340 width=54 height=43 xoffset=5.76 yoffset=31.29 xadvance=50.41 page=0 chnl=15 +char id=8226 x=1946 y=341 width=39 height=42 xoffset=15.53 yoffset=64.13 xadvance=54.14 page=0 chnl=15 +char id=8211 x=218 y=344 width=68 height=18 xoffset=9.77 yoffset=90.14 xadvance=72 page=0 chnl=15 +char id=8212 x=595 y=351 width=140 height=18 xoffset=9.77 yoffset=90.14 xadvance=144 page=0 chnl=15 +char id=732 x=1985 y=341 width=60 height=26 xoffset=22.56 yoffset=28.55 xadvance=85.22 page=0 chnl=15 +char id=8482 x=1174 y=347 width=104 height=58 xoffset=6.6 yoffset=31.29 xadvance=111.73 page=0 chnl=15 +char id=353 x=72 y=353 width=62 height=119 xoffset=11.45 yoffset=23.77 xadvance=68.7 page=0 chnl=15 +char id=8250 x=134 y=353 width=40 height=67 xoffset=9.63 yoffset=66.73 xadvance=43.8 page=0 chnl=15 +char id=339 x=595 y=369 width=128 height=87 xoffset=11.95 yoffset=55.76 xadvance=135.63 page=0 chnl=15 +char id=382 x=1718 y=360 width=63 height=118 xoffset=9.77 yoffset=23.77 xadvance=67.36 page=0 chnl=15 +char id=376 x=218 y=362 width=88 height=136 xoffset=4 yoffset=5.48 xadvance=80.65 page=0 chnl=15 +char id=32 x=2047 y=0 width=0 height=0 xoffset=0 yoffset=130.09 xadvance=37.41 page=0 chnl=15 +char id=161 x=735 y=346 width=25 height=112 xoffset=14.69 yoffset=55.48 xadvance=38.46 page=0 chnl=15 +char id=162 x=306 y=383 width=64 height=113 xoffset=17.36 yoffset=29.81 xadvance=82.34 page=0 chnl=15 +char id=163 x=370 y=383 width=80 height=112 xoffset=8.43 yoffset=29.95 xadvance=82.34 page=0 chnl=15 +char id=164 x=450 y=383 width=73 height=72 xoffset=12.65 yoffset=50.84 xadvance=82.34 page=0 chnl=15 +char id=165 x=1946 y=383 width=85 height=110 xoffset=6.18 yoffset=31.29 xadvance=82.34 page=0 chnl=15 +char id=166 x=174 y=353 width=18 height=152 xoffset=38.73 yoffset=24.68 xadvance=79.31 page=0 chnl=15 +char id=167 x=1447 y=377 width=63 height=118 xoffset=12.65 yoffset=24.05 xadvance=74.32 page=0 chnl=15 +char id=168 x=25 y=76 width=47 height=21 xoffset=25.73 yoffset=29.25 xadvance=83.11 page=0 chnl=15 +char id=169 x=972 y=419 width=113 height=113 xoffset=11.03 yoffset=29.81 xadvance=119.81 page=0 chnl=15 +char id=170 x=1174 y=405 width=47 height=56 xoffset=8.92 yoffset=30.09 xadvance=50.98 page=0 chnl=15 +char id=171 x=1574 y=420 width=68 height=67 xoffset=9.77 yoffset=66.73 xadvance=71.58 page=0 chnl=15 +char id=172 x=1085 y=422 width=75 height=45 xoffset=11.31 yoffset=78.47 xadvance=82.34 page=0 chnl=15 +char id=173 x=731 y=18 width=42 height=18 xoffset=9.91 yoffset=90.14 xadvance=46.34 page=0 chnl=15 +char id=174 x=1221 y=451 width=113 height=113 xoffset=11.03 yoffset=29.81 xadvance=119.81 page=0 chnl=15 +char id=175 x=1781 y=428 width=80 height=17 xoffset=3.58 yoffset=15.75 xadvance=72 page=0 chnl=15 +char id=176 x=1510 y=429 width=51 height=51 xoffset=12.93 yoffset=29.81 xadvance=61.66 page=0 chnl=15 +char id=177 x=1642 y=434 width=75 height=93 xoffset=11.31 yoffset=48.38 xadvance=82.34 page=0 chnl=15 +char id=178 x=523 y=431 width=50 height=71 xoffset=7.45 yoffset=29.95 xadvance=49.99 page=0 chnl=15 +char id=179 x=1781 y=445 width=51 height=72 xoffset=6.32 yoffset=29.95 xadvance=49.99 page=0 chnl=15 +char id=180 x=1815 y=78 width=35 height=31 xoffset=31.63 yoffset=23.77 xadvance=83.11 page=0 chnl=15 +char id=181 x=1334 y=451 width=72 height=119 xoffset=16.38 yoffset=57.02 xadvance=89.16 page=0 chnl=15 +char id=182 x=760 y=453 width=78 height=135 xoffset=11.95 yoffset=24.68 xadvance=94.29 page=0 chnl=15 +char id=183 x=0 y=112 width=25 height=27 xoffset=14.69 yoffset=73.76 xadvance=38.32 page=0 chnl=15 +char id=184 x=1221 y=405 width=36 height=42 xoffset=6.6 yoffset=134.09 xadvance=32.7 page=0 chnl=15 +char id=185 x=134 y=420 width=36 height=69 xoffset=9.34 yoffset=31.29 xadvance=49.99 page=0 chnl=15 +char id=186 x=838 y=453 width=52 height=56 xoffset=8.64 yoffset=30.09 xadvance=54 page=0 chnl=15 +char id=187 x=450 y=455 width=68 height=67 xoffset=9.63 yoffset=66.73 xadvance=71.58 page=0 chnl=15 +char id=188 x=573 y=456 width=107 height=110 xoffset=9.27 yoffset=31.29 xadvance=112.29 page=0 chnl=15 +char id=189 x=1085 y=467 width=110 height=110 xoffset=7.23 yoffset=31.29 xadvance=112.29 page=0 chnl=15 +char id=190 x=0 y=481 width=116 height=112 xoffset=5.83 yoffset=29.95 xadvance=112.29 page=0 chnl=15 +char id=191 x=890 y=457 width=64 height=114 xoffset=7.59 yoffset=55.48 xadvance=61.8 page=0 chnl=15 +char id=192 x=1832 y=483 width=99 height=142 xoffset=4 yoffset=0 xadvance=91.13 page=0 chnl=15 +char id=193 x=1510 y=487 width=99 height=142 xoffset=4 yoffset=0 xadvance=91.13 page=0 chnl=15 +char id=194 x=1931 y=493 width=99 height=142 xoffset=4 yoffset=0 xadvance=91.13 page=0 chnl=15 +char id=195 x=1406 y=495 width=99 height=137 xoffset=4 yoffset=4.78 xadvance=91.13 page=0 chnl=15 +char id=196 x=306 y=496 width=99 height=136 xoffset=4 yoffset=5.48 xadvance=91.13 page=0 chnl=15 +char id=197 x=192 y=498 width=99 height=134 xoffset=4 yoffset=7.66 xadvance=91.13 page=0 chnl=15 +char id=198 x=405 y=522 width=125 height=110 xoffset=3.86 yoffset=31.29 xadvance=125.72 page=0 chnl=15 +char id=199 x=1717 y=517 width=85 height=146 xoffset=12.79 yoffset=29.81 xadvance=90.84 page=0 chnl=15 +char id=200 x=680 y=458 width=65 height=142 xoffset=18.13 yoffset=0 xadvance=80.09 page=0 chnl=15 +char id=201 x=116 y=505 width=65 height=142 xoffset=18.13 yoffset=0 xadvance=80.09 page=0 chnl=15 +char id=202 x=1609 y=527 width=65 height=142 xoffset=18.13 yoffset=0 xadvance=80.09 page=0 chnl=15 +char id=203 x=954 y=532 width=65 height=136 xoffset=18.13 yoffset=5.48 xadvance=80.09 page=0 chnl=15 +char id=204 x=530 y=502 width=35 height=142 xoffset=4.35 yoffset=0 xadvance=40.15 page=0 chnl=15 +char id=205 x=838 y=509 width=35 height=142 xoffset=16.59 yoffset=0 xadvance=40.15 page=0 chnl=15 +char id=206 x=1019 y=532 width=55 height=142 xoffset=-0.01 yoffset=0 xadvance=40.15 page=0 chnl=15 +char id=207 x=1195 y=564 width=47 height=136 xoffset=4.35 yoffset=5.48 xadvance=40.15 page=0 chnl=15 +char id=208 x=565 y=566 width=99 height=110 xoffset=7.3 yoffset=31.29 xadvance=103.99 page=0 chnl=15 +char id=209 x=1242 y=564 width=88 height=137 xoffset=18.13 yoffset=4.78 xadvance=108.56 page=0 chnl=15 +char id=210 x=1074 y=577 width=102 height=143 xoffset=12.79 yoffset=0 xadvance=112.15 page=0 chnl=15 +char id=211 x=0 y=593 width=102 height=143 xoffset=12.79 yoffset=0 xadvance=112.15 page=0 chnl=15 +char id=212 x=664 y=600 width=102 height=143 xoffset=12.79 yoffset=0 xadvance=112.15 page=0 chnl=15 +char id=213 x=1802 y=625 width=102 height=138 xoffset=12.79 yoffset=4.78 xadvance=112.15 page=0 chnl=15 +char id=214 x=1505 y=629 width=102 height=138 xoffset=12.79 yoffset=5.48 xadvance=112.15 page=0 chnl=15 +char id=215 x=1330 y=570 width=71 height=71 xoffset=13.35 yoffset=51.4 xadvance=82.34 page=0 chnl=15 +char id=216 x=181 y=632 width=102 height=119 xoffset=12.79 yoffset=26.79 xadvance=112.15 page=0 chnl=15 +char id=217 x=283 y=632 width=86 height=143 xoffset=17.08 yoffset=0 xadvance=104.84 page=0 chnl=15 +char id=218 x=369 y=632 width=86 height=143 xoffset=17.08 yoffset=0 xadvance=104.84 page=0 chnl=15 +char id=219 x=1401 y=632 width=86 height=143 xoffset=17.08 yoffset=0 xadvance=104.84 page=0 chnl=15 +char id=220 x=1904 y=635 width=86 height=138 xoffset=17.08 yoffset=5.48 xadvance=104.84 page=0 chnl=15 +char id=221 x=455 y=644 width=88 height=142 xoffset=4 yoffset=0 xadvance=80.65 page=0 chnl=15 +char id=222 x=873 y=571 width=74 height=110 xoffset=18.13 yoffset=31.29 xadvance=87.96 page=0 chnl=15 +char id=223 x=102 y=647 width=78 height=119 xoffset=16.38 yoffset=23.91 xadvance=89.58 page=0 chnl=15 +char id=224 x=766 y=588 width=69 height=119 xoffset=10.61 yoffset=23.77 xadvance=80.09 page=0 chnl=15 +char id=225 x=1330 y=641 width=69 height=119 xoffset=10.61 yoffset=23.77 xadvance=80.09 page=0 chnl=15 +char id=226 x=1674 y=663 width=69 height=119 xoffset=10.61 yoffset=23.77 xadvance=80.09 page=0 chnl=15 +char id=227 x=947 y=668 width=69 height=115 xoffset=10.61 yoffset=28.55 xadvance=80.09 page=0 chnl=15 +char id=228 x=543 y=676 width=69 height=114 xoffset=10.61 yoffset=29.25 xadvance=80.09 page=0 chnl=15 +char id=229 x=835 y=681 width=69 height=126 xoffset=10.61 yoffset=16.73 xadvance=80.09 page=0 chnl=15 +char id=230 x=1176 y=701 width=117 height=87 xoffset=10.61 yoffset=55.62 xadvance=123.54 page=0 chnl=15 +char id=231 x=1607 y=669 width=63 height=121 xoffset=12.09 yoffset=55.62 xadvance=68.55 page=0 chnl=15 +char id=232 x=1016 y=720 width=73 height=119 xoffset=12.09 yoffset=23.77 xadvance=80.79 page=0 chnl=15 +char id=233 x=1089 y=720 width=73 height=119 xoffset=12.09 yoffset=23.77 xadvance=80.79 page=0 chnl=15 +char id=234 x=0 y=736 width=73 height=119 xoffset=12.09 yoffset=23.77 xadvance=80.79 page=0 chnl=15 +char id=235 x=612 y=743 width=73 height=114 xoffset=12.09 yoffset=29.25 xadvance=80.79 page=0 chnl=15 +char id=236 x=1674 y=527 width=35 height=118 xoffset=1.33 yoffset=23.77 xadvance=36.42 page=0 chnl=15 +char id=237 x=1990 y=635 width=35 height=118 xoffset=15.88 yoffset=23.77 xadvance=36.42 page=0 chnl=15 +char id=238 x=1743 y=663 width=55 height=118 xoffset=-1.41 yoffset=23.77 xadvance=36.42 page=0 chnl=15 +char id=239 x=766 y=707 width=47 height=112 xoffset=2.59 yoffset=29.25 xadvance=36.42 page=0 chnl=15 +char id=240 x=685 y=743 width=79 height=119 xoffset=11.95 yoffset=23.77 xadvance=85.85 page=0 chnl=15 +char id=241 x=180 y=751 width=72 height=113 xoffset=16.38 yoffset=28.55 xadvance=88.38 page=0 chnl=15 +char id=242 x=1293 y=760 width=78 height=119 xoffset=12.09 yoffset=23.77 xadvance=86.98 page=0 chnl=15 +char id=243 x=1798 y=763 width=78 height=119 xoffset=12.09 yoffset=23.77 xadvance=86.98 page=0 chnl=15 +char id=244 x=73 y=766 width=78 height=119 xoffset=12.09 yoffset=23.77 xadvance=86.98 page=0 chnl=15 +char id=245 x=1487 y=767 width=78 height=115 xoffset=12.09 yoffset=28.55 xadvance=86.98 page=0 chnl=15 +char id=246 x=1876 y=773 width=78 height=114 xoffset=12.09 yoffset=29.25 xadvance=86.98 page=0 chnl=15 +char id=247 x=1954 y=773 width=75 height=74 xoffset=11.31 yoffset=50.27 xadvance=82.34 page=0 chnl=15 +char id=248 x=252 y=775 width=78 height=94 xoffset=12.09 yoffset=52.59 xadvance=86.98 page=0 chnl=15 +char id=249 x=330 y=775 width=72 height=119 xoffset=15.53 yoffset=23.77 xadvance=88.38 page=0 chnl=15 +char id=250 x=1371 y=775 width=72 height=119 xoffset=15.53 yoffset=23.77 xadvance=88.38 page=0 chnl=15 +char id=251 x=1670 y=782 width=72 height=119 xoffset=15.53 yoffset=23.77 xadvance=88.38 page=0 chnl=15 +char id=252 x=904 y=783 width=72 height=114 xoffset=15.53 yoffset=29.25 xadvance=88.38 page=0 chnl=15 +char id=253 x=402 y=786 width=80 height=153 xoffset=4.14 yoffset=23.77 xadvance=72.56 page=0 chnl=15 +char id=254 x=1162 y=788 width=75 height=152 xoffset=16.38 yoffset=24.68 xadvance=88.24 page=0 chnl=15 +char id=255 x=482 y=790 width=80 height=147 xoffset=4.14 yoffset=29.25 xadvance=72.56 page=0 chnl=15 +kernings count=1623 +kerning first=34 second=65 amount=-10.05 +kerning first=34 second=84 amount=2.88 +kerning first=34 second=86 amount=2.88 +kerning first=34 second=87 amount=2.88 +kerning first=34 second=89 amount=1.41 +kerning first=34 second=97 amount=-5.77 +kerning first=34 second=99 amount=-8.65 +kerning first=34 second=100 amount=-8.65 +kerning first=34 second=101 amount=-8.65 +kerning first=34 second=103 amount=-4.29 +kerning first=34 second=109 amount=-4.29 +kerning first=34 second=110 amount=-4.29 +kerning first=34 second=111 amount=-8.65 +kerning first=34 second=112 amount=-4.29 +kerning first=34 second=113 amount=-8.65 +kerning first=34 second=114 amount=-4.29 +kerning first=34 second=115 amount=-4.29 +kerning first=34 second=117 amount=-4.29 +kerning first=34 second=192 amount=-10.05 +kerning first=34 second=193 amount=-10.05 +kerning first=34 second=194 amount=-10.05 +kerning first=34 second=195 amount=-10.05 +kerning first=34 second=196 amount=-10.05 +kerning first=34 second=197 amount=-10.05 +kerning first=34 second=221 amount=1.41 +kerning first=34 second=224 amount=-8.65 +kerning first=34 second=225 amount=-5.77 +kerning first=34 second=226 amount=-5.77 +kerning first=34 second=227 amount=-5.77 +kerning first=34 second=228 amount=-5.77 +kerning first=34 second=229 amount=-5.77 +kerning first=34 second=230 amount=-5.77 +kerning first=34 second=231 amount=-8.65 +kerning first=34 second=232 amount=-8.65 +kerning first=34 second=233 amount=-8.65 +kerning first=34 second=234 amount=-8.65 +kerning first=34 second=235 amount=-8.65 +kerning first=34 second=242 amount=-8.65 +kerning first=34 second=243 amount=-8.65 +kerning first=34 second=244 amount=-8.65 +kerning first=34 second=245 amount=-8.65 +kerning first=34 second=246 amount=-8.65 +kerning first=34 second=248 amount=-8.65 +kerning first=34 second=249 amount=-4.29 +kerning first=34 second=250 amount=-4.29 +kerning first=34 second=251 amount=-4.29 +kerning first=34 second=252 amount=-4.29 +kerning first=34 second=339 amount=-8.65 +kerning first=34 second=376 amount=1.41 +kerning first=39 second=65 amount=-10.05 +kerning first=39 second=84 amount=2.88 +kerning first=39 second=86 amount=2.88 +kerning first=39 second=87 amount=2.88 +kerning first=39 second=89 amount=1.41 +kerning first=39 second=97 amount=-5.77 +kerning first=39 second=99 amount=-8.65 +kerning first=39 second=100 amount=-8.65 +kerning first=39 second=101 amount=-8.65 +kerning first=39 second=103 amount=-4.29 +kerning first=39 second=109 amount=-4.29 +kerning first=39 second=110 amount=-4.29 +kerning first=39 second=111 amount=-8.65 +kerning first=39 second=112 amount=-4.29 +kerning first=39 second=113 amount=-8.65 +kerning first=39 second=114 amount=-4.29 +kerning first=39 second=115 amount=-4.29 +kerning first=39 second=117 amount=-4.29 +kerning first=39 second=192 amount=-10.05 +kerning first=39 second=193 amount=-10.05 +kerning first=39 second=194 amount=-10.05 +kerning first=39 second=195 amount=-10.05 +kerning first=39 second=196 amount=-10.05 +kerning first=39 second=197 amount=-10.05 +kerning first=39 second=221 amount=1.41 +kerning first=39 second=224 amount=-8.65 +kerning first=39 second=225 amount=-5.77 +kerning first=39 second=226 amount=-5.77 +kerning first=39 second=227 amount=-5.77 +kerning first=39 second=228 amount=-5.77 +kerning first=39 second=229 amount=-5.77 +kerning first=39 second=230 amount=-5.77 +kerning first=39 second=231 amount=-8.65 +kerning first=39 second=232 amount=-8.65 +kerning first=39 second=233 amount=-8.65 +kerning first=39 second=234 amount=-8.65 +kerning first=39 second=235 amount=-8.65 +kerning first=39 second=242 amount=-8.65 +kerning first=39 second=243 amount=-8.65 +kerning first=39 second=244 amount=-8.65 +kerning first=39 second=245 amount=-8.65 +kerning first=39 second=246 amount=-8.65 +kerning first=39 second=248 amount=-8.65 +kerning first=39 second=249 amount=-4.29 +kerning first=39 second=250 amount=-4.29 +kerning first=39 second=251 amount=-4.29 +kerning first=39 second=252 amount=-4.29 +kerning first=39 second=339 amount=-8.65 +kerning first=39 second=376 amount=1.41 +kerning first=40 second=74 amount=12.94 +kerning first=44 second=67 amount=-7.17 +kerning first=44 second=71 amount=-7.17 +kerning first=44 second=79 amount=-7.17 +kerning first=44 second=81 amount=-7.17 +kerning first=44 second=84 amount=-10.05 +kerning first=44 second=85 amount=-2.88 +kerning first=44 second=86 amount=-8.65 +kerning first=44 second=87 amount=-8.65 +kerning first=44 second=89 amount=-8.65 +kerning first=44 second=199 amount=-7.17 +kerning first=44 second=210 amount=-7.17 +kerning first=44 second=211 amount=-7.17 +kerning first=44 second=212 amount=-7.17 +kerning first=44 second=213 amount=-7.17 +kerning first=44 second=214 amount=-7.17 +kerning first=44 second=216 amount=-7.17 +kerning first=44 second=217 amount=-2.88 +kerning first=44 second=218 amount=-2.88 +kerning first=44 second=219 amount=-2.88 +kerning first=44 second=220 amount=-2.88 +kerning first=44 second=221 amount=-8.65 +kerning first=44 second=338 amount=-7.17 +kerning first=44 second=376 amount=-8.65 +kerning first=45 second=84 amount=-5.77 +kerning first=46 second=67 amount=-7.17 +kerning first=46 second=71 amount=-7.17 +kerning first=46 second=79 amount=-7.17 +kerning first=46 second=81 amount=-7.17 +kerning first=46 second=84 amount=-10.05 +kerning first=46 second=85 amount=-2.88 +kerning first=46 second=86 amount=-8.65 +kerning first=46 second=87 amount=-8.65 +kerning first=46 second=89 amount=-8.65 +kerning first=46 second=199 amount=-7.17 +kerning first=46 second=210 amount=-7.17 +kerning first=46 second=211 amount=-7.17 +kerning first=46 second=212 amount=-7.17 +kerning first=46 second=213 amount=-7.17 +kerning first=46 second=214 amount=-7.17 +kerning first=46 second=216 amount=-7.17 +kerning first=46 second=217 amount=-2.88 +kerning first=46 second=218 amount=-2.88 +kerning first=46 second=219 amount=-2.88 +kerning first=46 second=220 amount=-2.88 +kerning first=46 second=221 amount=-8.65 +kerning first=46 second=338 amount=-7.17 +kerning first=46 second=376 amount=-8.65 +kerning first=65 second=34 amount=-10.05 +kerning first=65 second=39 amount=-10.05 +kerning first=65 second=67 amount=-2.88 +kerning first=65 second=71 amount=-2.88 +kerning first=65 second=74 amount=18.7 +kerning first=65 second=79 amount=-2.88 +kerning first=65 second=81 amount=-2.88 +kerning first=65 second=84 amount=-10.05 +kerning first=65 second=86 amount=-5.77 +kerning first=65 second=87 amount=-5.77 +kerning first=65 second=89 amount=-8.65 +kerning first=65 second=199 amount=-2.88 +kerning first=65 second=210 amount=-2.88 +kerning first=65 second=211 amount=-2.88 +kerning first=65 second=212 amount=-2.88 +kerning first=65 second=213 amount=-2.88 +kerning first=65 second=214 amount=-2.88 +kerning first=65 second=216 amount=-2.88 +kerning first=65 second=221 amount=-8.65 +kerning first=65 second=338 amount=-2.88 +kerning first=65 second=376 amount=-8.65 +kerning first=65 second=8217 amount=-10.05 +kerning first=65 second=8221 amount=-10.05 +kerning first=66 second=44 amount=-5.77 +kerning first=66 second=46 amount=-5.77 +kerning first=66 second=65 amount=-2.88 +kerning first=66 second=84 amount=-4.29 +kerning first=66 second=86 amount=-1.41 +kerning first=66 second=87 amount=-1.41 +kerning first=66 second=88 amount=-2.88 +kerning first=66 second=89 amount=-1.41 +kerning first=66 second=90 amount=-1.41 +kerning first=66 second=192 amount=-2.88 +kerning first=66 second=193 amount=-2.88 +kerning first=66 second=194 amount=-2.88 +kerning first=66 second=195 amount=-2.88 +kerning first=66 second=196 amount=-2.88 +kerning first=66 second=197 amount=-2.88 +kerning first=66 second=221 amount=-1.41 +kerning first=66 second=376 amount=-1.41 +kerning first=66 second=381 amount=-1.41 +kerning first=66 second=8218 amount=-5.77 +kerning first=66 second=8222 amount=-5.77 +kerning first=67 second=67 amount=-2.88 +kerning first=67 second=71 amount=-2.88 +kerning first=67 second=79 amount=-2.88 +kerning first=67 second=81 amount=-2.88 +kerning first=67 second=199 amount=-2.88 +kerning first=67 second=210 amount=-2.88 +kerning first=67 second=211 amount=-2.88 +kerning first=67 second=212 amount=-2.88 +kerning first=67 second=213 amount=-2.88 +kerning first=67 second=214 amount=-2.88 +kerning first=67 second=216 amount=-2.88 +kerning first=67 second=338 amount=-2.88 +kerning first=68 second=44 amount=-5.77 +kerning first=68 second=46 amount=-5.77 +kerning first=68 second=65 amount=-2.88 +kerning first=68 second=84 amount=-4.29 +kerning first=68 second=86 amount=-1.41 +kerning first=68 second=87 amount=-1.41 +kerning first=68 second=88 amount=-2.88 +kerning first=68 second=89 amount=-1.41 +kerning first=68 second=90 amount=-1.41 +kerning first=68 second=192 amount=-2.88 +kerning first=68 second=193 amount=-2.88 +kerning first=68 second=194 amount=-2.88 +kerning first=68 second=195 amount=-2.88 +kerning first=68 second=196 amount=-2.88 +kerning first=68 second=197 amount=-2.88 +kerning first=68 second=221 amount=-1.41 +kerning first=68 second=376 amount=-1.41 +kerning first=68 second=381 amount=-1.41 +kerning first=68 second=8218 amount=-5.77 +kerning first=68 second=8222 amount=-5.77 +kerning first=69 second=74 amount=8.65 +kerning first=70 second=44 amount=-8.65 +kerning first=70 second=46 amount=-8.65 +kerning first=70 second=63 amount=2.88 +kerning first=70 second=65 amount=-2.88 +kerning first=70 second=192 amount=-2.88 +kerning first=70 second=193 amount=-2.88 +kerning first=70 second=194 amount=-2.88 +kerning first=70 second=195 amount=-2.88 +kerning first=70 second=196 amount=-2.88 +kerning first=70 second=197 amount=-2.88 +kerning first=70 second=8218 amount=-8.65 +kerning first=70 second=8222 amount=-8.65 +kerning first=75 second=67 amount=-2.88 +kerning first=75 second=71 amount=-2.88 +kerning first=75 second=79 amount=-2.88 +kerning first=75 second=81 amount=-2.88 +kerning first=75 second=199 amount=-2.88 +kerning first=75 second=210 amount=-2.88 +kerning first=75 second=211 amount=-2.88 +kerning first=75 second=212 amount=-2.88 +kerning first=75 second=213 amount=-2.88 +kerning first=75 second=214 amount=-2.88 +kerning first=75 second=216 amount=-2.88 +kerning first=75 second=338 amount=-2.88 +kerning first=76 second=34 amount=-11.53 +kerning first=76 second=39 amount=-11.53 +kerning first=76 second=67 amount=-2.88 +kerning first=76 second=71 amount=-2.88 +kerning first=76 second=79 amount=-2.88 +kerning first=76 second=81 amount=-2.88 +kerning first=76 second=84 amount=-2.88 +kerning first=76 second=85 amount=-1.41 +kerning first=76 second=86 amount=-2.88 +kerning first=76 second=87 amount=-2.88 +kerning first=76 second=89 amount=-4.29 +kerning first=76 second=199 amount=-2.88 +kerning first=76 second=210 amount=-2.88 +kerning first=76 second=211 amount=-2.88 +kerning first=76 second=212 amount=-2.88 +kerning first=76 second=213 amount=-2.88 +kerning first=76 second=214 amount=-2.88 +kerning first=76 second=216 amount=-2.88 +kerning first=76 second=217 amount=-1.41 +kerning first=76 second=218 amount=-1.41 +kerning first=76 second=219 amount=-1.41 +kerning first=76 second=220 amount=-1.41 +kerning first=76 second=221 amount=-4.29 +kerning first=76 second=338 amount=-2.88 +kerning first=76 second=376 amount=-4.29 +kerning first=76 second=8217 amount=-11.53 +kerning first=76 second=8221 amount=-11.53 +kerning first=79 second=44 amount=-5.77 +kerning first=79 second=46 amount=-5.77 +kerning first=79 second=65 amount=-2.88 +kerning first=79 second=84 amount=-4.29 +kerning first=79 second=86 amount=-1.41 +kerning first=79 second=87 amount=-1.41 +kerning first=79 second=88 amount=-2.88 +kerning first=79 second=89 amount=-1.41 +kerning first=79 second=90 amount=-1.41 +kerning first=79 second=192 amount=-2.88 +kerning first=79 second=193 amount=-2.88 +kerning first=79 second=194 amount=-2.88 +kerning first=79 second=195 amount=-2.88 +kerning first=79 second=196 amount=-2.88 +kerning first=79 second=197 amount=-2.88 +kerning first=79 second=221 amount=-1.41 +kerning first=79 second=376 amount=-1.41 +kerning first=79 second=381 amount=-1.41 +kerning first=79 second=8218 amount=-5.77 +kerning first=79 second=8222 amount=-5.77 +kerning first=80 second=44 amount=-18.7 +kerning first=80 second=46 amount=-18.7 +kerning first=80 second=65 amount=-7.17 +kerning first=80 second=88 amount=-2.88 +kerning first=80 second=90 amount=-1.41 +kerning first=80 second=192 amount=-7.17 +kerning first=80 second=193 amount=-7.17 +kerning first=80 second=194 amount=-7.17 +kerning first=80 second=195 amount=-7.17 +kerning first=80 second=196 amount=-7.17 +kerning first=80 second=197 amount=-7.17 +kerning first=80 second=381 amount=-1.41 +kerning first=80 second=8218 amount=-18.7 +kerning first=80 second=8222 amount=-18.7 +kerning first=81 second=44 amount=-5.77 +kerning first=81 second=46 amount=-5.77 +kerning first=81 second=65 amount=-2.88 +kerning first=81 second=84 amount=-4.29 +kerning first=81 second=86 amount=-1.41 +kerning first=81 second=87 amount=-1.41 +kerning first=81 second=88 amount=-2.88 +kerning first=81 second=89 amount=-1.41 +kerning first=81 second=90 amount=-1.41 +kerning first=81 second=192 amount=-2.88 +kerning first=81 second=193 amount=-2.88 +kerning first=81 second=194 amount=-2.88 +kerning first=81 second=195 amount=-2.88 +kerning first=81 second=196 amount=-2.88 +kerning first=81 second=197 amount=-2.88 +kerning first=81 second=221 amount=-1.41 +kerning first=81 second=376 amount=-1.41 +kerning first=81 second=381 amount=-1.41 +kerning first=81 second=8218 amount=-5.77 +kerning first=81 second=8222 amount=-5.77 +kerning first=84 second=44 amount=-8.65 +kerning first=84 second=45 amount=-5.77 +kerning first=84 second=46 amount=-8.65 +kerning first=84 second=63 amount=2.88 +kerning first=84 second=65 amount=-10.05 +kerning first=84 second=67 amount=-2.88 +kerning first=84 second=71 amount=-2.88 +kerning first=84 second=79 amount=-2.88 +kerning first=84 second=81 amount=-2.88 +kerning first=84 second=84 amount=2.88 +kerning first=84 second=97 amount=-11.53 +kerning first=84 second=99 amount=-10.05 +kerning first=84 second=100 amount=-10.05 +kerning first=84 second=101 amount=-10.05 +kerning first=84 second=103 amount=-10.05 +kerning first=84 second=109 amount=-7.17 +kerning first=84 second=110 amount=-7.17 +kerning first=84 second=111 amount=-10.05 +kerning first=84 second=112 amount=-7.17 +kerning first=84 second=113 amount=-10.05 +kerning first=84 second=114 amount=-7.17 +kerning first=84 second=115 amount=-8.65 +kerning first=84 second=117 amount=-7.17 +kerning first=84 second=118 amount=-2.88 +kerning first=84 second=119 amount=-2.88 +kerning first=84 second=120 amount=-2.88 +kerning first=84 second=121 amount=-2.88 +kerning first=84 second=122 amount=-5.77 +kerning first=84 second=192 amount=-10.05 +kerning first=84 second=193 amount=-10.05 +kerning first=84 second=194 amount=-10.05 +kerning first=84 second=195 amount=-10.05 +kerning first=84 second=196 amount=-10.05 +kerning first=84 second=197 amount=-10.05 +kerning first=84 second=199 amount=-2.88 +kerning first=84 second=210 amount=-2.88 +kerning first=84 second=211 amount=-2.88 +kerning first=84 second=212 amount=-2.88 +kerning first=84 second=213 amount=-2.88 +kerning first=84 second=214 amount=-2.88 +kerning first=84 second=216 amount=-2.88 +kerning first=84 second=224 amount=-10.05 +kerning first=84 second=225 amount=-11.53 +kerning first=84 second=226 amount=-11.53 +kerning first=84 second=227 amount=-11.53 +kerning first=84 second=228 amount=-11.53 +kerning first=84 second=229 amount=-11.53 +kerning first=84 second=230 amount=-11.53 +kerning first=84 second=231 amount=-10.05 +kerning first=84 second=232 amount=-10.05 +kerning first=84 second=233 amount=-10.05 +kerning first=84 second=234 amount=-10.05 +kerning first=84 second=235 amount=-10.05 +kerning first=84 second=242 amount=-10.05 +kerning first=84 second=243 amount=-10.05 +kerning first=84 second=244 amount=-10.05 +kerning first=84 second=245 amount=-10.05 +kerning first=84 second=246 amount=-10.05 +kerning first=84 second=248 amount=-10.05 +kerning first=84 second=249 amount=-7.17 +kerning first=84 second=250 amount=-7.17 +kerning first=84 second=251 amount=-7.17 +kerning first=84 second=252 amount=-7.17 +kerning first=84 second=253 amount=-2.88 +kerning first=84 second=338 amount=-2.88 +kerning first=84 second=339 amount=-10.05 +kerning first=84 second=382 amount=-5.77 +kerning first=84 second=8211 amount=-5.77 +kerning first=84 second=8212 amount=-5.77 +kerning first=84 second=8218 amount=-8.65 +kerning first=84 second=8222 amount=-8.65 +kerning first=85 second=44 amount=-2.88 +kerning first=85 second=46 amount=-2.88 +kerning first=85 second=65 amount=-1.41 +kerning first=85 second=192 amount=-1.41 +kerning first=85 second=193 amount=-1.41 +kerning first=85 second=194 amount=-1.41 +kerning first=85 second=195 amount=-1.41 +kerning first=85 second=196 amount=-1.41 +kerning first=85 second=197 amount=-1.41 +kerning first=85 second=8218 amount=-2.88 +kerning first=85 second=8222 amount=-2.88 +kerning first=86 second=44 amount=-7.17 +kerning first=86 second=46 amount=-7.17 +kerning first=86 second=63 amount=2.88 +kerning first=86 second=65 amount=-5.77 +kerning first=86 second=67 amount=-1.41 +kerning first=86 second=71 amount=-1.41 +kerning first=86 second=79 amount=-1.41 +kerning first=86 second=81 amount=-1.41 +kerning first=86 second=97 amount=-2.88 +kerning first=86 second=99 amount=-2.88 +kerning first=86 second=100 amount=-2.88 +kerning first=86 second=101 amount=-2.88 +kerning first=86 second=103 amount=-1.41 +kerning first=86 second=109 amount=-1.41 +kerning first=86 second=110 amount=-1.41 +kerning first=86 second=111 amount=-2.88 +kerning first=86 second=112 amount=-1.41 +kerning first=86 second=113 amount=-2.88 +kerning first=86 second=114 amount=-1.41 +kerning first=86 second=115 amount=-1.41 +kerning first=86 second=117 amount=-1.41 +kerning first=86 second=192 amount=-5.77 +kerning first=86 second=193 amount=-5.77 +kerning first=86 second=194 amount=-5.77 +kerning first=86 second=195 amount=-5.77 +kerning first=86 second=196 amount=-5.77 +kerning first=86 second=197 amount=-5.77 +kerning first=86 second=199 amount=-1.41 +kerning first=86 second=210 amount=-1.41 +kerning first=86 second=211 amount=-1.41 +kerning first=86 second=212 amount=-1.41 +kerning first=86 second=213 amount=-1.41 +kerning first=86 second=214 amount=-1.41 +kerning first=86 second=216 amount=-1.41 +kerning first=86 second=224 amount=-2.88 +kerning first=86 second=225 amount=-2.88 +kerning first=86 second=226 amount=-2.88 +kerning first=86 second=227 amount=-2.88 +kerning first=86 second=228 amount=-2.88 +kerning first=86 second=229 amount=-2.88 +kerning first=86 second=230 amount=-2.88 +kerning first=86 second=231 amount=-2.88 +kerning first=86 second=232 amount=-2.88 +kerning first=86 second=233 amount=-2.88 +kerning first=86 second=234 amount=-2.88 +kerning first=86 second=235 amount=-2.88 +kerning first=86 second=242 amount=-2.88 +kerning first=86 second=243 amount=-2.88 +kerning first=86 second=244 amount=-2.88 +kerning first=86 second=245 amount=-2.88 +kerning first=86 second=246 amount=-2.88 +kerning first=86 second=248 amount=-2.88 +kerning first=86 second=249 amount=-1.41 +kerning first=86 second=250 amount=-1.41 +kerning first=86 second=251 amount=-1.41 +kerning first=86 second=252 amount=-1.41 +kerning first=86 second=338 amount=-1.41 +kerning first=86 second=339 amount=-2.88 +kerning first=86 second=8218 amount=-7.17 +kerning first=86 second=8222 amount=-7.17 +kerning first=87 second=44 amount=-7.17 +kerning first=87 second=46 amount=-7.17 +kerning first=87 second=63 amount=2.88 +kerning first=87 second=65 amount=-5.77 +kerning first=87 second=67 amount=-1.41 +kerning first=87 second=71 amount=-1.41 +kerning first=87 second=79 amount=-1.41 +kerning first=87 second=81 amount=-1.41 +kerning first=87 second=97 amount=-2.88 +kerning first=87 second=99 amount=-2.88 +kerning first=87 second=100 amount=-2.88 +kerning first=87 second=101 amount=-2.88 +kerning first=87 second=103 amount=-1.41 +kerning first=87 second=109 amount=-1.41 +kerning first=87 second=110 amount=-1.41 +kerning first=87 second=111 amount=-2.88 +kerning first=87 second=112 amount=-1.41 +kerning first=87 second=113 amount=-2.88 +kerning first=87 second=114 amount=-1.41 +kerning first=87 second=115 amount=-1.41 +kerning first=87 second=117 amount=-1.41 +kerning first=87 second=192 amount=-5.77 +kerning first=87 second=193 amount=-5.77 +kerning first=87 second=194 amount=-5.77 +kerning first=87 second=195 amount=-5.77 +kerning first=87 second=196 amount=-5.77 +kerning first=87 second=197 amount=-5.77 +kerning first=87 second=199 amount=-1.41 +kerning first=87 second=210 amount=-1.41 +kerning first=87 second=211 amount=-1.41 +kerning first=87 second=212 amount=-1.41 +kerning first=87 second=213 amount=-1.41 +kerning first=87 second=214 amount=-1.41 +kerning first=87 second=216 amount=-1.41 +kerning first=87 second=224 amount=-2.88 +kerning first=87 second=225 amount=-2.88 +kerning first=87 second=226 amount=-2.88 +kerning first=87 second=227 amount=-2.88 +kerning first=87 second=228 amount=-2.88 +kerning first=87 second=229 amount=-2.88 +kerning first=87 second=230 amount=-2.88 +kerning first=87 second=231 amount=-2.88 +kerning first=87 second=232 amount=-2.88 +kerning first=87 second=233 amount=-2.88 +kerning first=87 second=234 amount=-2.88 +kerning first=87 second=235 amount=-2.88 +kerning first=87 second=242 amount=-2.88 +kerning first=87 second=243 amount=-2.88 +kerning first=87 second=244 amount=-2.88 +kerning first=87 second=245 amount=-2.88 +kerning first=87 second=246 amount=-2.88 +kerning first=87 second=248 amount=-2.88 +kerning first=87 second=249 amount=-1.41 +kerning first=87 second=250 amount=-1.41 +kerning first=87 second=251 amount=-1.41 +kerning first=87 second=252 amount=-1.41 +kerning first=87 second=338 amount=-1.41 +kerning first=87 second=339 amount=-2.88 +kerning first=87 second=8218 amount=-7.17 +kerning first=87 second=8222 amount=-7.17 +kerning first=88 second=67 amount=-2.88 +kerning first=88 second=71 amount=-2.88 +kerning first=88 second=79 amount=-2.88 +kerning first=88 second=81 amount=-2.88 +kerning first=88 second=199 amount=-2.88 +kerning first=88 second=210 amount=-2.88 +kerning first=88 second=211 amount=-2.88 +kerning first=88 second=212 amount=-2.88 +kerning first=88 second=213 amount=-2.88 +kerning first=88 second=214 amount=-2.88 +kerning first=88 second=216 amount=-2.88 +kerning first=88 second=338 amount=-2.88 +kerning first=89 second=44 amount=-8.65 +kerning first=89 second=46 amount=-8.65 +kerning first=89 second=63 amount=2.88 +kerning first=89 second=65 amount=-8.65 +kerning first=89 second=67 amount=-2.88 +kerning first=89 second=71 amount=-2.88 +kerning first=89 second=79 amount=-2.88 +kerning first=89 second=81 amount=-2.88 +kerning first=89 second=97 amount=-7.17 +kerning first=89 second=99 amount=-7.17 +kerning first=89 second=100 amount=-7.17 +kerning first=89 second=101 amount=-7.17 +kerning first=89 second=103 amount=-2.88 +kerning first=89 second=109 amount=-4.29 +kerning first=89 second=110 amount=-4.29 +kerning first=89 second=111 amount=-7.17 +kerning first=89 second=112 amount=-4.29 +kerning first=89 second=113 amount=-7.17 +kerning first=89 second=114 amount=-4.29 +kerning first=89 second=115 amount=-5.77 +kerning first=89 second=117 amount=-4.29 +kerning first=89 second=122 amount=-2.88 +kerning first=89 second=192 amount=-8.65 +kerning first=89 second=193 amount=-8.65 +kerning first=89 second=194 amount=-8.65 +kerning first=89 second=195 amount=-8.65 +kerning first=89 second=196 amount=-8.65 +kerning first=89 second=197 amount=-8.65 +kerning first=89 second=199 amount=-2.88 +kerning first=89 second=210 amount=-2.88 +kerning first=89 second=211 amount=-2.88 +kerning first=89 second=212 amount=-2.88 +kerning first=89 second=213 amount=-2.88 +kerning first=89 second=214 amount=-2.88 +kerning first=89 second=216 amount=-2.88 +kerning first=89 second=224 amount=-7.17 +kerning first=89 second=225 amount=-7.17 +kerning first=89 second=226 amount=-7.17 +kerning first=89 second=227 amount=-7.17 +kerning first=89 second=228 amount=-7.17 +kerning first=89 second=229 amount=-7.17 +kerning first=89 second=230 amount=-7.17 +kerning first=89 second=231 amount=-7.17 +kerning first=89 second=232 amount=-7.17 +kerning first=89 second=233 amount=-7.17 +kerning first=89 second=234 amount=-7.17 +kerning first=89 second=235 amount=-7.17 +kerning first=89 second=242 amount=-7.17 +kerning first=89 second=243 amount=-7.17 +kerning first=89 second=244 amount=-7.17 +kerning first=89 second=245 amount=-7.17 +kerning first=89 second=246 amount=-7.17 +kerning first=89 second=248 amount=-7.17 +kerning first=89 second=249 amount=-4.29 +kerning first=89 second=250 amount=-4.29 +kerning first=89 second=251 amount=-4.29 +kerning first=89 second=252 amount=-4.29 +kerning first=89 second=338 amount=-2.88 +kerning first=89 second=339 amount=-7.17 +kerning first=89 second=382 amount=-2.88 +kerning first=89 second=8218 amount=-8.65 +kerning first=89 second=8222 amount=-8.65 +kerning first=90 second=67 amount=-1.41 +kerning first=90 second=71 amount=-1.41 +kerning first=90 second=79 amount=-1.41 +kerning first=90 second=81 amount=-1.41 +kerning first=90 second=199 amount=-1.41 +kerning first=90 second=210 amount=-1.41 +kerning first=90 second=211 amount=-1.41 +kerning first=90 second=212 amount=-1.41 +kerning first=90 second=213 amount=-1.41 +kerning first=90 second=214 amount=-1.41 +kerning first=90 second=216 amount=-1.41 +kerning first=90 second=338 amount=-1.41 +kerning first=91 second=74 amount=12.94 +kerning first=97 second=34 amount=-1.41 +kerning first=97 second=39 amount=-1.41 +kerning first=97 second=8217 amount=-1.41 +kerning first=97 second=8221 amount=-1.41 +kerning first=98 second=34 amount=-1.41 +kerning first=98 second=39 amount=-1.41 +kerning first=98 second=118 amount=-2.88 +kerning first=98 second=119 amount=-2.88 +kerning first=98 second=120 amount=-2.88 +kerning first=98 second=121 amount=-2.88 +kerning first=98 second=122 amount=-1.41 +kerning first=98 second=253 amount=-2.88 +kerning first=98 second=382 amount=-1.41 +kerning first=98 second=8217 amount=-1.41 +kerning first=98 second=8221 amount=-1.41 +kerning first=99 second=34 amount=2.88 +kerning first=99 second=39 amount=2.88 +kerning first=99 second=8217 amount=2.88 +kerning first=99 second=8221 amount=2.88 +kerning first=101 second=34 amount=-1.41 +kerning first=101 second=39 amount=-1.41 +kerning first=101 second=118 amount=-2.88 +kerning first=101 second=119 amount=-2.88 +kerning first=101 second=120 amount=-2.88 +kerning first=101 second=121 amount=-2.88 +kerning first=101 second=122 amount=-1.41 +kerning first=101 second=253 amount=-2.88 +kerning first=101 second=382 amount=-1.41 +kerning first=101 second=8217 amount=-1.41 +kerning first=101 second=8221 amount=-1.41 +kerning first=102 second=34 amount=8.65 +kerning first=102 second=39 amount=8.65 +kerning first=102 second=8217 amount=8.65 +kerning first=102 second=8221 amount=8.65 +kerning first=104 second=34 amount=-1.41 +kerning first=104 second=39 amount=-1.41 +kerning first=104 second=8217 amount=-1.41 +kerning first=104 second=8221 amount=-1.41 +kerning first=107 second=99 amount=-2.88 +kerning first=107 second=100 amount=-2.88 +kerning first=107 second=101 amount=-2.88 +kerning first=107 second=111 amount=-2.88 +kerning first=107 second=113 amount=-2.88 +kerning first=107 second=224 amount=-2.88 +kerning first=107 second=231 amount=-2.88 +kerning first=107 second=232 amount=-2.88 +kerning first=107 second=233 amount=-2.88 +kerning first=107 second=234 amount=-2.88 +kerning first=107 second=235 amount=-2.88 +kerning first=107 second=242 amount=-2.88 +kerning first=107 second=243 amount=-2.88 +kerning first=107 second=244 amount=-2.88 +kerning first=107 second=245 amount=-2.88 +kerning first=107 second=246 amount=-2.88 +kerning first=107 second=248 amount=-2.88 +kerning first=107 second=339 amount=-2.88 +kerning first=109 second=34 amount=-1.41 +kerning first=109 second=39 amount=-1.41 +kerning first=109 second=8217 amount=-1.41 +kerning first=109 second=8221 amount=-1.41 +kerning first=110 second=34 amount=-1.41 +kerning first=110 second=39 amount=-1.41 +kerning first=110 second=8217 amount=-1.41 +kerning first=110 second=8221 amount=-1.41 +kerning first=111 second=34 amount=-1.41 +kerning first=111 second=39 amount=-1.41 +kerning first=111 second=118 amount=-2.88 +kerning first=111 second=119 amount=-2.88 +kerning first=111 second=120 amount=-2.88 +kerning first=111 second=121 amount=-2.88 +kerning first=111 second=122 amount=-1.41 +kerning first=111 second=253 amount=-2.88 +kerning first=111 second=382 amount=-1.41 +kerning first=111 second=8217 amount=-1.41 +kerning first=111 second=8221 amount=-1.41 +kerning first=112 second=34 amount=-1.41 +kerning first=112 second=39 amount=-1.41 +kerning first=112 second=118 amount=-2.88 +kerning first=112 second=119 amount=-2.88 +kerning first=112 second=120 amount=-2.88 +kerning first=112 second=121 amount=-2.88 +kerning first=112 second=122 amount=-1.41 +kerning first=112 second=253 amount=-2.88 +kerning first=112 second=382 amount=-1.41 +kerning first=112 second=8217 amount=-1.41 +kerning first=112 second=8221 amount=-1.41 +kerning first=114 second=34 amount=5.77 +kerning first=114 second=39 amount=5.77 +kerning first=114 second=97 amount=-2.88 +kerning first=114 second=99 amount=-2.88 +kerning first=114 second=100 amount=-2.88 +kerning first=114 second=101 amount=-2.88 +kerning first=114 second=103 amount=-1.41 +kerning first=114 second=111 amount=-2.88 +kerning first=114 second=113 amount=-2.88 +kerning first=114 second=224 amount=-2.88 +kerning first=114 second=225 amount=-2.88 +kerning first=114 second=226 amount=-2.88 +kerning first=114 second=227 amount=-2.88 +kerning first=114 second=228 amount=-2.88 +kerning first=114 second=229 amount=-2.88 +kerning first=114 second=230 amount=-2.88 +kerning first=114 second=231 amount=-2.88 +kerning first=114 second=232 amount=-2.88 +kerning first=114 second=233 amount=-2.88 +kerning first=114 second=234 amount=-2.88 +kerning first=114 second=235 amount=-2.88 +kerning first=114 second=242 amount=-2.88 +kerning first=114 second=243 amount=-2.88 +kerning first=114 second=244 amount=-2.88 +kerning first=114 second=245 amount=-2.88 +kerning first=114 second=246 amount=-2.88 +kerning first=114 second=248 amount=-2.88 +kerning first=114 second=339 amount=-2.88 +kerning first=114 second=8217 amount=5.77 +kerning first=114 second=8221 amount=5.77 +kerning first=116 second=34 amount=2.88 +kerning first=116 second=39 amount=2.88 +kerning first=116 second=8217 amount=2.88 +kerning first=116 second=8221 amount=2.88 +kerning first=118 second=34 amount=5.77 +kerning first=118 second=39 amount=5.77 +kerning first=118 second=44 amount=-5.77 +kerning first=118 second=46 amount=-5.77 +kerning first=118 second=63 amount=2.88 +kerning first=118 second=8217 amount=5.77 +kerning first=118 second=8218 amount=-5.77 +kerning first=118 second=8221 amount=5.77 +kerning first=118 second=8222 amount=-5.77 +kerning first=119 second=34 amount=5.77 +kerning first=119 second=39 amount=5.77 +kerning first=119 second=44 amount=-5.77 +kerning first=119 second=46 amount=-5.77 +kerning first=119 second=63 amount=2.88 +kerning first=119 second=8217 amount=5.77 +kerning first=119 second=8218 amount=-5.77 +kerning first=119 second=8221 amount=5.77 +kerning first=119 second=8222 amount=-5.77 +kerning first=120 second=99 amount=-2.88 +kerning first=120 second=100 amount=-2.88 +kerning first=120 second=101 amount=-2.88 +kerning first=120 second=111 amount=-2.88 +kerning first=120 second=113 amount=-2.88 +kerning first=120 second=224 amount=-2.88 +kerning first=120 second=231 amount=-2.88 +kerning first=120 second=232 amount=-2.88 +kerning first=120 second=233 amount=-2.88 +kerning first=120 second=234 amount=-2.88 +kerning first=120 second=235 amount=-2.88 +kerning first=120 second=242 amount=-2.88 +kerning first=120 second=243 amount=-2.88 +kerning first=120 second=244 amount=-2.88 +kerning first=120 second=245 amount=-2.88 +kerning first=120 second=246 amount=-2.88 +kerning first=120 second=248 amount=-2.88 +kerning first=120 second=339 amount=-2.88 +kerning first=121 second=34 amount=5.77 +kerning first=121 second=39 amount=5.77 +kerning first=121 second=44 amount=-5.77 +kerning first=121 second=46 amount=-5.77 +kerning first=121 second=63 amount=2.88 +kerning first=121 second=8217 amount=5.77 +kerning first=121 second=8218 amount=-5.77 +kerning first=121 second=8221 amount=5.77 +kerning first=121 second=8222 amount=-5.77 +kerning first=123 second=74 amount=12.94 +kerning first=192 second=34 amount=-10.05 +kerning first=192 second=39 amount=-10.05 +kerning first=192 second=67 amount=-2.88 +kerning first=192 second=71 amount=-2.88 +kerning first=192 second=74 amount=18.7 +kerning first=192 second=79 amount=-2.88 +kerning first=192 second=81 amount=-2.88 +kerning first=192 second=84 amount=-10.05 +kerning first=192 second=86 amount=-5.77 +kerning first=192 second=87 amount=-5.77 +kerning first=192 second=89 amount=-8.65 +kerning first=192 second=199 amount=-2.88 +kerning first=192 second=210 amount=-2.88 +kerning first=192 second=211 amount=-2.88 +kerning first=192 second=212 amount=-2.88 +kerning first=192 second=213 amount=-2.88 +kerning first=192 second=214 amount=-2.88 +kerning first=192 second=216 amount=-2.88 +kerning first=192 second=221 amount=-8.65 +kerning first=192 second=338 amount=-2.88 +kerning first=192 second=376 amount=-8.65 +kerning first=192 second=8217 amount=-10.05 +kerning first=192 second=8221 amount=-10.05 +kerning first=193 second=34 amount=-10.05 +kerning first=193 second=39 amount=-10.05 +kerning first=193 second=67 amount=-2.88 +kerning first=193 second=71 amount=-2.88 +kerning first=193 second=74 amount=18.7 +kerning first=193 second=79 amount=-2.88 +kerning first=193 second=81 amount=-2.88 +kerning first=193 second=84 amount=-10.05 +kerning first=193 second=86 amount=-5.77 +kerning first=193 second=87 amount=-5.77 +kerning first=193 second=89 amount=-8.65 +kerning first=193 second=199 amount=-2.88 +kerning first=193 second=210 amount=-2.88 +kerning first=193 second=211 amount=-2.88 +kerning first=193 second=212 amount=-2.88 +kerning first=193 second=213 amount=-2.88 +kerning first=193 second=214 amount=-2.88 +kerning first=193 second=216 amount=-2.88 +kerning first=193 second=221 amount=-8.65 +kerning first=193 second=338 amount=-2.88 +kerning first=193 second=376 amount=-8.65 +kerning first=193 second=8217 amount=-10.05 +kerning first=193 second=8221 amount=-10.05 +kerning first=194 second=34 amount=-10.05 +kerning first=194 second=39 amount=-10.05 +kerning first=194 second=67 amount=-2.88 +kerning first=194 second=71 amount=-2.88 +kerning first=194 second=74 amount=18.7 +kerning first=194 second=79 amount=-2.88 +kerning first=194 second=81 amount=-2.88 +kerning first=194 second=84 amount=-10.05 +kerning first=194 second=86 amount=-5.77 +kerning first=194 second=87 amount=-5.77 +kerning first=194 second=89 amount=-8.65 +kerning first=194 second=199 amount=-2.88 +kerning first=194 second=210 amount=-2.88 +kerning first=194 second=211 amount=-2.88 +kerning first=194 second=212 amount=-2.88 +kerning first=194 second=213 amount=-2.88 +kerning first=194 second=214 amount=-2.88 +kerning first=194 second=216 amount=-2.88 +kerning first=194 second=221 amount=-8.65 +kerning first=194 second=338 amount=-2.88 +kerning first=194 second=376 amount=-8.65 +kerning first=194 second=8217 amount=-10.05 +kerning first=194 second=8221 amount=-10.05 +kerning first=195 second=34 amount=-10.05 +kerning first=195 second=39 amount=-10.05 +kerning first=195 second=67 amount=-2.88 +kerning first=195 second=71 amount=-2.88 +kerning first=195 second=74 amount=18.7 +kerning first=195 second=79 amount=-2.88 +kerning first=195 second=81 amount=-2.88 +kerning first=195 second=84 amount=-10.05 +kerning first=195 second=86 amount=-5.77 +kerning first=195 second=87 amount=-5.77 +kerning first=195 second=89 amount=-8.65 +kerning first=195 second=199 amount=-2.88 +kerning first=195 second=210 amount=-2.88 +kerning first=195 second=211 amount=-2.88 +kerning first=195 second=212 amount=-2.88 +kerning first=195 second=213 amount=-2.88 +kerning first=195 second=214 amount=-2.88 +kerning first=195 second=216 amount=-2.88 +kerning first=195 second=221 amount=-8.65 +kerning first=195 second=338 amount=-2.88 +kerning first=195 second=376 amount=-8.65 +kerning first=195 second=8217 amount=-10.05 +kerning first=195 second=8221 amount=-10.05 +kerning first=196 second=34 amount=-10.05 +kerning first=196 second=39 amount=-10.05 +kerning first=196 second=67 amount=-2.88 +kerning first=196 second=71 amount=-2.88 +kerning first=196 second=74 amount=18.7 +kerning first=196 second=79 amount=-2.88 +kerning first=196 second=81 amount=-2.88 +kerning first=196 second=84 amount=-10.05 +kerning first=196 second=86 amount=-5.77 +kerning first=196 second=87 amount=-5.77 +kerning first=196 second=89 amount=-8.65 +kerning first=196 second=199 amount=-2.88 +kerning first=196 second=210 amount=-2.88 +kerning first=196 second=211 amount=-2.88 +kerning first=196 second=212 amount=-2.88 +kerning first=196 second=213 amount=-2.88 +kerning first=196 second=214 amount=-2.88 +kerning first=196 second=216 amount=-2.88 +kerning first=196 second=221 amount=-8.65 +kerning first=196 second=338 amount=-2.88 +kerning first=196 second=376 amount=-8.65 +kerning first=196 second=8217 amount=-10.05 +kerning first=196 second=8221 amount=-10.05 +kerning first=197 second=34 amount=-10.05 +kerning first=197 second=39 amount=-10.05 +kerning first=197 second=67 amount=-2.88 +kerning first=197 second=71 amount=-2.88 +kerning first=197 second=74 amount=18.7 +kerning first=197 second=79 amount=-2.88 +kerning first=197 second=81 amount=-2.88 +kerning first=197 second=84 amount=-10.05 +kerning first=197 second=86 amount=-5.77 +kerning first=197 second=87 amount=-5.77 +kerning first=197 second=89 amount=-8.65 +kerning first=197 second=199 amount=-2.88 +kerning first=197 second=210 amount=-2.88 +kerning first=197 second=211 amount=-2.88 +kerning first=197 second=212 amount=-2.88 +kerning first=197 second=213 amount=-2.88 +kerning first=197 second=214 amount=-2.88 +kerning first=197 second=216 amount=-2.88 +kerning first=197 second=221 amount=-8.65 +kerning first=197 second=338 amount=-2.88 +kerning first=197 second=376 amount=-8.65 +kerning first=197 second=8217 amount=-10.05 +kerning first=197 second=8221 amount=-10.05 +kerning first=198 second=74 amount=8.65 +kerning first=199 second=67 amount=-2.88 +kerning first=199 second=71 amount=-2.88 +kerning first=199 second=79 amount=-2.88 +kerning first=199 second=81 amount=-2.88 +kerning first=199 second=199 amount=-2.88 +kerning first=199 second=210 amount=-2.88 +kerning first=199 second=211 amount=-2.88 +kerning first=199 second=212 amount=-2.88 +kerning first=199 second=213 amount=-2.88 +kerning first=199 second=214 amount=-2.88 +kerning first=199 second=216 amount=-2.88 +kerning first=199 second=338 amount=-2.88 +kerning first=200 second=74 amount=8.65 +kerning first=201 second=74 amount=8.65 +kerning first=202 second=74 amount=8.65 +kerning first=203 second=74 amount=8.65 +kerning first=208 second=44 amount=-5.77 +kerning first=208 second=46 amount=-5.77 +kerning first=208 second=65 amount=-2.88 +kerning first=208 second=84 amount=-4.29 +kerning first=208 second=86 amount=-1.41 +kerning first=208 second=87 amount=-1.41 +kerning first=208 second=88 amount=-2.88 +kerning first=208 second=89 amount=-1.41 +kerning first=208 second=90 amount=-1.41 +kerning first=208 second=192 amount=-2.88 +kerning first=208 second=193 amount=-2.88 +kerning first=208 second=194 amount=-2.88 +kerning first=208 second=195 amount=-2.88 +kerning first=208 second=196 amount=-2.88 +kerning first=208 second=197 amount=-2.88 +kerning first=208 second=221 amount=-1.41 +kerning first=208 second=376 amount=-1.41 +kerning first=208 second=381 amount=-1.41 +kerning first=208 second=8218 amount=-5.77 +kerning first=208 second=8222 amount=-5.77 +kerning first=210 second=44 amount=-5.77 +kerning first=210 second=46 amount=-5.77 +kerning first=210 second=65 amount=-2.88 +kerning first=210 second=84 amount=-4.29 +kerning first=210 second=86 amount=-1.41 +kerning first=210 second=87 amount=-1.41 +kerning first=210 second=88 amount=-2.88 +kerning first=210 second=89 amount=-1.41 +kerning first=210 second=90 amount=-1.41 +kerning first=210 second=192 amount=-2.88 +kerning first=210 second=193 amount=-2.88 +kerning first=210 second=194 amount=-2.88 +kerning first=210 second=195 amount=-2.88 +kerning first=210 second=196 amount=-2.88 +kerning first=210 second=197 amount=-2.88 +kerning first=210 second=221 amount=-1.41 +kerning first=210 second=376 amount=-1.41 +kerning first=210 second=381 amount=-1.41 +kerning first=210 second=8218 amount=-5.77 +kerning first=210 second=8222 amount=-5.77 +kerning first=211 second=44 amount=-5.77 +kerning first=211 second=46 amount=-5.77 +kerning first=211 second=65 amount=-2.88 +kerning first=211 second=84 amount=-4.29 +kerning first=211 second=86 amount=-1.41 +kerning first=211 second=87 amount=-1.41 +kerning first=211 second=88 amount=-2.88 +kerning first=211 second=89 amount=-1.41 +kerning first=211 second=90 amount=-1.41 +kerning first=211 second=192 amount=-2.88 +kerning first=211 second=193 amount=-2.88 +kerning first=211 second=194 amount=-2.88 +kerning first=211 second=195 amount=-2.88 +kerning first=211 second=196 amount=-2.88 +kerning first=211 second=197 amount=-2.88 +kerning first=211 second=221 amount=-1.41 +kerning first=211 second=376 amount=-1.41 +kerning first=211 second=381 amount=-1.41 +kerning first=211 second=8218 amount=-5.77 +kerning first=211 second=8222 amount=-5.77 +kerning first=212 second=44 amount=-5.77 +kerning first=212 second=46 amount=-5.77 +kerning first=212 second=65 amount=-2.88 +kerning first=212 second=84 amount=-4.29 +kerning first=212 second=86 amount=-1.41 +kerning first=212 second=87 amount=-1.41 +kerning first=212 second=88 amount=-2.88 +kerning first=212 second=89 amount=-1.41 +kerning first=212 second=90 amount=-1.41 +kerning first=212 second=192 amount=-2.88 +kerning first=212 second=193 amount=-2.88 +kerning first=212 second=194 amount=-2.88 +kerning first=212 second=195 amount=-2.88 +kerning first=212 second=196 amount=-2.88 +kerning first=212 second=197 amount=-2.88 +kerning first=212 second=221 amount=-1.41 +kerning first=212 second=376 amount=-1.41 +kerning first=212 second=381 amount=-1.41 +kerning first=212 second=8218 amount=-5.77 +kerning first=212 second=8222 amount=-5.77 +kerning first=213 second=44 amount=-5.77 +kerning first=213 second=46 amount=-5.77 +kerning first=213 second=65 amount=-2.88 +kerning first=213 second=84 amount=-4.29 +kerning first=213 second=86 amount=-1.41 +kerning first=213 second=87 amount=-1.41 +kerning first=213 second=88 amount=-2.88 +kerning first=213 second=89 amount=-1.41 +kerning first=213 second=90 amount=-1.41 +kerning first=213 second=192 amount=-2.88 +kerning first=213 second=193 amount=-2.88 +kerning first=213 second=194 amount=-2.88 +kerning first=213 second=195 amount=-2.88 +kerning first=213 second=196 amount=-2.88 +kerning first=213 second=197 amount=-2.88 +kerning first=213 second=221 amount=-1.41 +kerning first=213 second=376 amount=-1.41 +kerning first=213 second=381 amount=-1.41 +kerning first=213 second=8218 amount=-5.77 +kerning first=213 second=8222 amount=-5.77 +kerning first=214 second=44 amount=-5.77 +kerning first=214 second=46 amount=-5.77 +kerning first=214 second=65 amount=-2.88 +kerning first=214 second=84 amount=-4.29 +kerning first=214 second=86 amount=-1.41 +kerning first=214 second=87 amount=-1.41 +kerning first=214 second=88 amount=-2.88 +kerning first=214 second=89 amount=-1.41 +kerning first=214 second=90 amount=-1.41 +kerning first=214 second=192 amount=-2.88 +kerning first=214 second=193 amount=-2.88 +kerning first=214 second=194 amount=-2.88 +kerning first=214 second=195 amount=-2.88 +kerning first=214 second=196 amount=-2.88 +kerning first=214 second=197 amount=-2.88 +kerning first=214 second=221 amount=-1.41 +kerning first=214 second=376 amount=-1.41 +kerning first=214 second=381 amount=-1.41 +kerning first=214 second=8218 amount=-5.77 +kerning first=214 second=8222 amount=-5.77 +kerning first=216 second=44 amount=-5.77 +kerning first=216 second=46 amount=-5.77 +kerning first=216 second=65 amount=-2.88 +kerning first=216 second=84 amount=-4.29 +kerning first=216 second=86 amount=-1.41 +kerning first=216 second=87 amount=-1.41 +kerning first=216 second=88 amount=-2.88 +kerning first=216 second=89 amount=-1.41 +kerning first=216 second=90 amount=-1.41 +kerning first=216 second=192 amount=-2.88 +kerning first=216 second=193 amount=-2.88 +kerning first=216 second=194 amount=-2.88 +kerning first=216 second=195 amount=-2.88 +kerning first=216 second=196 amount=-2.88 +kerning first=216 second=197 amount=-2.88 +kerning first=216 second=221 amount=-1.41 +kerning first=216 second=376 amount=-1.41 +kerning first=216 second=381 amount=-1.41 +kerning first=216 second=8218 amount=-5.77 +kerning first=216 second=8222 amount=-5.77 +kerning first=217 second=44 amount=-2.88 +kerning first=217 second=46 amount=-2.88 +kerning first=217 second=65 amount=-1.41 +kerning first=217 second=192 amount=-1.41 +kerning first=217 second=193 amount=-1.41 +kerning first=217 second=194 amount=-1.41 +kerning first=217 second=195 amount=-1.41 +kerning first=217 second=196 amount=-1.41 +kerning first=217 second=197 amount=-1.41 +kerning first=217 second=8218 amount=-2.88 +kerning first=217 second=8222 amount=-2.88 +kerning first=218 second=44 amount=-2.88 +kerning first=218 second=46 amount=-2.88 +kerning first=218 second=65 amount=-1.41 +kerning first=218 second=192 amount=-1.41 +kerning first=218 second=193 amount=-1.41 +kerning first=218 second=194 amount=-1.41 +kerning first=218 second=195 amount=-1.41 +kerning first=218 second=196 amount=-1.41 +kerning first=218 second=197 amount=-1.41 +kerning first=218 second=8218 amount=-2.88 +kerning first=218 second=8222 amount=-2.88 +kerning first=219 second=44 amount=-2.88 +kerning first=219 second=46 amount=-2.88 +kerning first=219 second=65 amount=-1.41 +kerning first=219 second=192 amount=-1.41 +kerning first=219 second=193 amount=-1.41 +kerning first=219 second=194 amount=-1.41 +kerning first=219 second=195 amount=-1.41 +kerning first=219 second=196 amount=-1.41 +kerning first=219 second=197 amount=-1.41 +kerning first=219 second=8218 amount=-2.88 +kerning first=219 second=8222 amount=-2.88 +kerning first=220 second=44 amount=-2.88 +kerning first=220 second=46 amount=-2.88 +kerning first=220 second=65 amount=-1.41 +kerning first=220 second=192 amount=-1.41 +kerning first=220 second=193 amount=-1.41 +kerning first=220 second=194 amount=-1.41 +kerning first=220 second=195 amount=-1.41 +kerning first=220 second=196 amount=-1.41 +kerning first=220 second=197 amount=-1.41 +kerning first=220 second=8218 amount=-2.88 +kerning first=220 second=8222 amount=-2.88 +kerning first=221 second=44 amount=-8.65 +kerning first=221 second=46 amount=-8.65 +kerning first=221 second=63 amount=2.88 +kerning first=221 second=65 amount=-8.65 +kerning first=221 second=67 amount=-2.88 +kerning first=221 second=71 amount=-2.88 +kerning first=221 second=79 amount=-2.88 +kerning first=221 second=81 amount=-2.88 +kerning first=221 second=97 amount=-7.17 +kerning first=221 second=99 amount=-7.17 +kerning first=221 second=100 amount=-7.17 +kerning first=221 second=101 amount=-7.17 +kerning first=221 second=103 amount=-2.88 +kerning first=221 second=109 amount=-4.29 +kerning first=221 second=110 amount=-4.29 +kerning first=221 second=111 amount=-7.17 +kerning first=221 second=112 amount=-4.29 +kerning first=221 second=113 amount=-7.17 +kerning first=221 second=114 amount=-4.29 +kerning first=221 second=115 amount=-5.77 +kerning first=221 second=117 amount=-4.29 +kerning first=221 second=122 amount=-2.88 +kerning first=221 second=192 amount=-8.65 +kerning first=221 second=193 amount=-8.65 +kerning first=221 second=194 amount=-8.65 +kerning first=221 second=195 amount=-8.65 +kerning first=221 second=196 amount=-8.65 +kerning first=221 second=197 amount=-8.65 +kerning first=221 second=199 amount=-2.88 +kerning first=221 second=210 amount=-2.88 +kerning first=221 second=211 amount=-2.88 +kerning first=221 second=212 amount=-2.88 +kerning first=221 second=213 amount=-2.88 +kerning first=221 second=214 amount=-2.88 +kerning first=221 second=216 amount=-2.88 +kerning first=221 second=224 amount=-7.17 +kerning first=221 second=225 amount=-7.17 +kerning first=221 second=226 amount=-7.17 +kerning first=221 second=227 amount=-7.17 +kerning first=221 second=228 amount=-7.17 +kerning first=221 second=229 amount=-7.17 +kerning first=221 second=230 amount=-7.17 +kerning first=221 second=231 amount=-7.17 +kerning first=221 second=232 amount=-7.17 +kerning first=221 second=233 amount=-7.17 +kerning first=221 second=234 amount=-7.17 +kerning first=221 second=235 amount=-7.17 +kerning first=221 second=242 amount=-7.17 +kerning first=221 second=243 amount=-7.17 +kerning first=221 second=244 amount=-7.17 +kerning first=221 second=245 amount=-7.17 +kerning first=221 second=246 amount=-7.17 +kerning first=221 second=248 amount=-7.17 +kerning first=221 second=249 amount=-4.29 +kerning first=221 second=250 amount=-4.29 +kerning first=221 second=251 amount=-4.29 +kerning first=221 second=252 amount=-4.29 +kerning first=221 second=338 amount=-2.88 +kerning first=221 second=339 amount=-7.17 +kerning first=221 second=382 amount=-2.88 +kerning first=221 second=8218 amount=-8.65 +kerning first=221 second=8222 amount=-8.65 +kerning first=222 second=44 amount=-18.7 +kerning first=222 second=46 amount=-18.7 +kerning first=222 second=65 amount=-7.17 +kerning first=222 second=88 amount=-2.88 +kerning first=222 second=90 amount=-1.41 +kerning first=222 second=192 amount=-7.17 +kerning first=222 second=193 amount=-7.17 +kerning first=222 second=194 amount=-7.17 +kerning first=222 second=195 amount=-7.17 +kerning first=222 second=196 amount=-7.17 +kerning first=222 second=197 amount=-7.17 +kerning first=222 second=381 amount=-1.41 +kerning first=222 second=8218 amount=-18.7 +kerning first=222 second=8222 amount=-18.7 +kerning first=224 second=34 amount=-1.41 +kerning first=224 second=39 amount=-1.41 +kerning first=224 second=8217 amount=-1.41 +kerning first=224 second=8221 amount=-1.41 +kerning first=225 second=34 amount=-1.41 +kerning first=225 second=39 amount=-1.41 +kerning first=225 second=8217 amount=-1.41 +kerning first=225 second=8221 amount=-1.41 +kerning first=226 second=34 amount=-1.41 +kerning first=226 second=39 amount=-1.41 +kerning first=226 second=8217 amount=-1.41 +kerning first=226 second=8221 amount=-1.41 +kerning first=227 second=34 amount=-1.41 +kerning first=227 second=39 amount=-1.41 +kerning first=227 second=8217 amount=-1.41 +kerning first=227 second=8221 amount=-1.41 +kerning first=228 second=34 amount=-1.41 +kerning first=228 second=39 amount=-1.41 +kerning first=228 second=8217 amount=-1.41 +kerning first=228 second=8221 amount=-1.41 +kerning first=229 second=34 amount=-1.41 +kerning first=229 second=39 amount=-1.41 +kerning first=229 second=8217 amount=-1.41 +kerning first=229 second=8221 amount=-1.41 +kerning first=232 second=34 amount=-1.41 +kerning first=232 second=39 amount=-1.41 +kerning first=232 second=118 amount=-2.88 +kerning first=232 second=119 amount=-2.88 +kerning first=232 second=120 amount=-2.88 +kerning first=232 second=121 amount=-2.88 +kerning first=232 second=122 amount=-1.41 +kerning first=232 second=253 amount=-2.88 +kerning first=232 second=382 amount=-1.41 +kerning first=232 second=8217 amount=-1.41 +kerning first=232 second=8221 amount=-1.41 +kerning first=233 second=34 amount=-1.41 +kerning first=233 second=39 amount=-1.41 +kerning first=233 second=118 amount=-2.88 +kerning first=233 second=119 amount=-2.88 +kerning first=233 second=120 amount=-2.88 +kerning first=233 second=121 amount=-2.88 +kerning first=233 second=122 amount=-1.41 +kerning first=233 second=253 amount=-2.88 +kerning first=233 second=382 amount=-1.41 +kerning first=233 second=8217 amount=-1.41 +kerning first=233 second=8221 amount=-1.41 +kerning first=234 second=34 amount=-1.41 +kerning first=234 second=39 amount=-1.41 +kerning first=234 second=118 amount=-2.88 +kerning first=234 second=119 amount=-2.88 +kerning first=234 second=120 amount=-2.88 +kerning first=234 second=121 amount=-2.88 +kerning first=234 second=122 amount=-1.41 +kerning first=234 second=253 amount=-2.88 +kerning first=234 second=382 amount=-1.41 +kerning first=234 second=8217 amount=-1.41 +kerning first=234 second=8221 amount=-1.41 +kerning first=235 second=34 amount=-1.41 +kerning first=235 second=39 amount=-1.41 +kerning first=235 second=118 amount=-2.88 +kerning first=235 second=119 amount=-2.88 +kerning first=235 second=120 amount=-2.88 +kerning first=235 second=121 amount=-2.88 +kerning first=235 second=122 amount=-1.41 +kerning first=235 second=253 amount=-2.88 +kerning first=235 second=382 amount=-1.41 +kerning first=235 second=8217 amount=-1.41 +kerning first=235 second=8221 amount=-1.41 +kerning first=240 second=34 amount=-1.41 +kerning first=240 second=39 amount=-1.41 +kerning first=240 second=118 amount=-2.88 +kerning first=240 second=119 amount=-2.88 +kerning first=240 second=120 amount=-2.88 +kerning first=240 second=121 amount=-2.88 +kerning first=240 second=122 amount=-1.41 +kerning first=240 second=253 amount=-2.88 +kerning first=240 second=382 amount=-1.41 +kerning first=240 second=8217 amount=-1.41 +kerning first=240 second=8221 amount=-1.41 +kerning first=242 second=34 amount=-1.41 +kerning first=242 second=39 amount=-1.41 +kerning first=242 second=118 amount=-2.88 +kerning first=242 second=119 amount=-2.88 +kerning first=242 second=120 amount=-2.88 +kerning first=242 second=121 amount=-2.88 +kerning first=242 second=122 amount=-1.41 +kerning first=242 second=253 amount=-2.88 +kerning first=242 second=382 amount=-1.41 +kerning first=242 second=8217 amount=-1.41 +kerning first=242 second=8221 amount=-1.41 +kerning first=243 second=34 amount=-1.41 +kerning first=243 second=39 amount=-1.41 +kerning first=243 second=118 amount=-2.88 +kerning first=243 second=119 amount=-2.88 +kerning first=243 second=120 amount=-2.88 +kerning first=243 second=121 amount=-2.88 +kerning first=243 second=122 amount=-1.41 +kerning first=243 second=253 amount=-2.88 +kerning first=243 second=382 amount=-1.41 +kerning first=243 second=8217 amount=-1.41 +kerning first=243 second=8221 amount=-1.41 +kerning first=244 second=34 amount=-1.41 +kerning first=244 second=39 amount=-1.41 +kerning first=244 second=118 amount=-2.88 +kerning first=244 second=119 amount=-2.88 +kerning first=244 second=120 amount=-2.88 +kerning first=244 second=121 amount=-2.88 +kerning first=244 second=122 amount=-1.41 +kerning first=244 second=253 amount=-2.88 +kerning first=244 second=382 amount=-1.41 +kerning first=244 second=8217 amount=-1.41 +kerning first=244 second=8221 amount=-1.41 +kerning first=246 second=34 amount=-2.88 +kerning first=246 second=39 amount=-2.88 +kerning first=246 second=8217 amount=-2.88 +kerning first=246 second=8221 amount=-2.88 +kerning first=248 second=34 amount=-1.41 +kerning first=248 second=39 amount=-1.41 +kerning first=248 second=118 amount=-2.88 +kerning first=248 second=119 amount=-2.88 +kerning first=248 second=120 amount=-2.88 +kerning first=248 second=121 amount=-2.88 +kerning first=248 second=122 amount=-1.41 +kerning first=248 second=253 amount=-2.88 +kerning first=248 second=382 amount=-1.41 +kerning first=248 second=8217 amount=-1.41 +kerning first=248 second=8221 amount=-1.41 +kerning first=253 second=34 amount=5.77 +kerning first=253 second=39 amount=5.77 +kerning first=253 second=44 amount=-5.77 +kerning first=253 second=46 amount=-5.77 +kerning first=253 second=63 amount=2.88 +kerning first=253 second=8217 amount=5.77 +kerning first=253 second=8218 amount=-5.77 +kerning first=253 second=8221 amount=5.77 +kerning first=253 second=8222 amount=-5.77 +kerning first=254 second=34 amount=-1.41 +kerning first=254 second=39 amount=-1.41 +kerning first=254 second=118 amount=-2.88 +kerning first=254 second=119 amount=-2.88 +kerning first=254 second=120 amount=-2.88 +kerning first=254 second=121 amount=-2.88 +kerning first=254 second=122 amount=-1.41 +kerning first=254 second=253 amount=-2.88 +kerning first=254 second=382 amount=-1.41 +kerning first=254 second=8217 amount=-1.41 +kerning first=254 second=8221 amount=-1.41 +kerning first=255 second=34 amount=5.77 +kerning first=255 second=39 amount=5.77 +kerning first=255 second=44 amount=-5.77 +kerning first=255 second=46 amount=-5.77 +kerning first=255 second=63 amount=2.88 +kerning first=255 second=8217 amount=5.77 +kerning first=255 second=8218 amount=-5.77 +kerning first=255 second=8221 amount=5.77 +kerning first=255 second=8222 amount=-5.77 +kerning first=338 second=74 amount=8.65 +kerning first=376 second=44 amount=-8.65 +kerning first=376 second=46 amount=-8.65 +kerning first=376 second=63 amount=2.88 +kerning first=376 second=65 amount=-8.65 +kerning first=376 second=67 amount=-2.88 +kerning first=376 second=71 amount=-2.88 +kerning first=376 second=79 amount=-2.88 +kerning first=376 second=81 amount=-2.88 +kerning first=376 second=97 amount=-7.17 +kerning first=376 second=99 amount=-7.17 +kerning first=376 second=100 amount=-7.17 +kerning first=376 second=101 amount=-7.17 +kerning first=376 second=103 amount=-2.88 +kerning first=376 second=109 amount=-4.29 +kerning first=376 second=110 amount=-4.29 +kerning first=376 second=111 amount=-7.17 +kerning first=376 second=112 amount=-4.29 +kerning first=376 second=113 amount=-7.17 +kerning first=376 second=114 amount=-4.29 +kerning first=376 second=115 amount=-5.77 +kerning first=376 second=117 amount=-4.29 +kerning first=376 second=122 amount=-2.88 +kerning first=376 second=192 amount=-8.65 +kerning first=376 second=193 amount=-8.65 +kerning first=376 second=194 amount=-8.65 +kerning first=376 second=195 amount=-8.65 +kerning first=376 second=196 amount=-8.65 +kerning first=376 second=197 amount=-8.65 +kerning first=376 second=199 amount=-2.88 +kerning first=376 second=210 amount=-2.88 +kerning first=376 second=211 amount=-2.88 +kerning first=376 second=212 amount=-2.88 +kerning first=376 second=213 amount=-2.88 +kerning first=376 second=214 amount=-2.88 +kerning first=376 second=216 amount=-2.88 +kerning first=376 second=224 amount=-7.17 +kerning first=376 second=225 amount=-7.17 +kerning first=376 second=226 amount=-7.17 +kerning first=376 second=227 amount=-7.17 +kerning first=376 second=228 amount=-7.17 +kerning first=376 second=229 amount=-7.17 +kerning first=376 second=230 amount=-7.17 +kerning first=376 second=231 amount=-7.17 +kerning first=376 second=232 amount=-7.17 +kerning first=376 second=233 amount=-7.17 +kerning first=376 second=234 amount=-7.17 +kerning first=376 second=235 amount=-7.17 +kerning first=376 second=242 amount=-7.17 +kerning first=376 second=243 amount=-7.17 +kerning first=376 second=244 amount=-7.17 +kerning first=376 second=245 amount=-7.17 +kerning first=376 second=246 amount=-7.17 +kerning first=376 second=248 amount=-7.17 +kerning first=376 second=249 amount=-4.29 +kerning first=376 second=250 amount=-4.29 +kerning first=376 second=251 amount=-4.29 +kerning first=376 second=252 amount=-4.29 +kerning first=376 second=338 amount=-2.88 +kerning first=376 second=339 amount=-7.17 +kerning first=376 second=382 amount=-2.88 +kerning first=376 second=8218 amount=-8.65 +kerning first=376 second=8222 amount=-8.65 +kerning first=381 second=67 amount=-1.41 +kerning first=381 second=71 amount=-1.41 +kerning first=381 second=79 amount=-1.41 +kerning first=381 second=81 amount=-1.41 +kerning first=381 second=199 amount=-1.41 +kerning first=381 second=210 amount=-1.41 +kerning first=381 second=211 amount=-1.41 +kerning first=381 second=212 amount=-1.41 +kerning first=381 second=213 amount=-1.41 +kerning first=381 second=214 amount=-1.41 +kerning first=381 second=216 amount=-1.41 +kerning first=381 second=338 amount=-1.41 +kerning first=8211 second=84 amount=-5.77 +kerning first=8212 second=84 amount=-5.77 +kerning first=8216 second=65 amount=-10.05 +kerning first=8216 second=84 amount=2.88 +kerning first=8216 second=86 amount=2.88 +kerning first=8216 second=87 amount=2.88 +kerning first=8216 second=89 amount=1.41 +kerning first=8216 second=97 amount=-5.77 +kerning first=8216 second=99 amount=-8.65 +kerning first=8216 second=100 amount=-8.65 +kerning first=8216 second=101 amount=-8.65 +kerning first=8216 second=103 amount=-4.29 +kerning first=8216 second=109 amount=-4.29 +kerning first=8216 second=110 amount=-4.29 +kerning first=8216 second=111 amount=-8.65 +kerning first=8216 second=112 amount=-4.29 +kerning first=8216 second=113 amount=-8.65 +kerning first=8216 second=114 amount=-4.29 +kerning first=8216 second=115 amount=-4.29 +kerning first=8216 second=117 amount=-4.29 +kerning first=8216 second=192 amount=-10.05 +kerning first=8216 second=193 amount=-10.05 +kerning first=8216 second=194 amount=-10.05 +kerning first=8216 second=195 amount=-10.05 +kerning first=8216 second=196 amount=-10.05 +kerning first=8216 second=197 amount=-10.05 +kerning first=8216 second=221 amount=1.41 +kerning first=8216 second=224 amount=-8.65 +kerning first=8216 second=225 amount=-5.77 +kerning first=8216 second=226 amount=-5.77 +kerning first=8216 second=227 amount=-5.77 +kerning first=8216 second=228 amount=-5.77 +kerning first=8216 second=229 amount=-5.77 +kerning first=8216 second=230 amount=-5.77 +kerning first=8216 second=231 amount=-8.65 +kerning first=8216 second=232 amount=-8.65 +kerning first=8216 second=233 amount=-8.65 +kerning first=8216 second=234 amount=-8.65 +kerning first=8216 second=235 amount=-8.65 +kerning first=8216 second=242 amount=-8.65 +kerning first=8216 second=243 amount=-8.65 +kerning first=8216 second=244 amount=-8.65 +kerning first=8216 second=245 amount=-8.65 +kerning first=8216 second=246 amount=-8.65 +kerning first=8216 second=248 amount=-8.65 +kerning first=8216 second=249 amount=-4.29 +kerning first=8216 second=250 amount=-4.29 +kerning first=8216 second=251 amount=-4.29 +kerning first=8216 second=252 amount=-4.29 +kerning first=8216 second=339 amount=-8.65 +kerning first=8216 second=376 amount=1.41 +kerning first=8217 second=65 amount=-10.05 +kerning first=8217 second=84 amount=2.88 +kerning first=8217 second=86 amount=2.88 +kerning first=8217 second=87 amount=2.88 +kerning first=8217 second=89 amount=1.41 +kerning first=8217 second=97 amount=-5.77 +kerning first=8217 second=99 amount=-8.65 +kerning first=8217 second=100 amount=-8.65 +kerning first=8217 second=101 amount=-8.65 +kerning first=8217 second=103 amount=-4.29 +kerning first=8217 second=109 amount=-4.29 +kerning first=8217 second=110 amount=-4.29 +kerning first=8217 second=111 amount=-8.65 +kerning first=8217 second=112 amount=-4.29 +kerning first=8217 second=113 amount=-8.65 +kerning first=8217 second=114 amount=-4.29 +kerning first=8217 second=115 amount=-4.29 +kerning first=8217 second=117 amount=-4.29 +kerning first=8217 second=192 amount=-10.05 +kerning first=8217 second=193 amount=-10.05 +kerning first=8217 second=194 amount=-10.05 +kerning first=8217 second=195 amount=-10.05 +kerning first=8217 second=196 amount=-10.05 +kerning first=8217 second=197 amount=-10.05 +kerning first=8217 second=221 amount=1.41 +kerning first=8217 second=224 amount=-8.65 +kerning first=8217 second=225 amount=-5.77 +kerning first=8217 second=226 amount=-5.77 +kerning first=8217 second=227 amount=-5.77 +kerning first=8217 second=228 amount=-5.77 +kerning first=8217 second=229 amount=-5.77 +kerning first=8217 second=230 amount=-5.77 +kerning first=8217 second=231 amount=-8.65 +kerning first=8217 second=232 amount=-8.65 +kerning first=8217 second=233 amount=-8.65 +kerning first=8217 second=234 amount=-8.65 +kerning first=8217 second=235 amount=-8.65 +kerning first=8217 second=242 amount=-8.65 +kerning first=8217 second=243 amount=-8.65 +kerning first=8217 second=244 amount=-8.65 +kerning first=8217 second=245 amount=-8.65 +kerning first=8217 second=246 amount=-8.65 +kerning first=8217 second=248 amount=-8.65 +kerning first=8217 second=249 amount=-4.29 +kerning first=8217 second=250 amount=-4.29 +kerning first=8217 second=251 amount=-4.29 +kerning first=8217 second=252 amount=-4.29 +kerning first=8217 second=339 amount=-8.65 +kerning first=8217 second=376 amount=1.41 +kerning first=8218 second=67 amount=-7.17 +kerning first=8218 second=71 amount=-7.17 +kerning first=8218 second=79 amount=-7.17 +kerning first=8218 second=81 amount=-7.17 +kerning first=8218 second=84 amount=-10.05 +kerning first=8218 second=85 amount=-2.88 +kerning first=8218 second=86 amount=-8.65 +kerning first=8218 second=87 amount=-8.65 +kerning first=8218 second=89 amount=-8.65 +kerning first=8218 second=199 amount=-7.17 +kerning first=8218 second=210 amount=-7.17 +kerning first=8218 second=211 amount=-7.17 +kerning first=8218 second=212 amount=-7.17 +kerning first=8218 second=213 amount=-7.17 +kerning first=8218 second=214 amount=-7.17 +kerning first=8218 second=216 amount=-7.17 +kerning first=8218 second=217 amount=-2.88 +kerning first=8218 second=218 amount=-2.88 +kerning first=8218 second=219 amount=-2.88 +kerning first=8218 second=220 amount=-2.88 +kerning first=8218 second=221 amount=-8.65 +kerning first=8218 second=338 amount=-7.17 +kerning first=8218 second=376 amount=-8.65 +kerning first=8220 second=65 amount=-10.05 +kerning first=8220 second=84 amount=2.88 +kerning first=8220 second=86 amount=2.88 +kerning first=8220 second=87 amount=2.88 +kerning first=8220 second=89 amount=1.41 +kerning first=8220 second=97 amount=-5.77 +kerning first=8220 second=99 amount=-8.65 +kerning first=8220 second=100 amount=-8.65 +kerning first=8220 second=101 amount=-8.65 +kerning first=8220 second=103 amount=-4.29 +kerning first=8220 second=109 amount=-4.29 +kerning first=8220 second=110 amount=-4.29 +kerning first=8220 second=111 amount=-8.65 +kerning first=8220 second=112 amount=-4.29 +kerning first=8220 second=113 amount=-8.65 +kerning first=8220 second=114 amount=-4.29 +kerning first=8220 second=115 amount=-4.29 +kerning first=8220 second=117 amount=-4.29 +kerning first=8220 second=192 amount=-10.05 +kerning first=8220 second=193 amount=-10.05 +kerning first=8220 second=194 amount=-10.05 +kerning first=8220 second=195 amount=-10.05 +kerning first=8220 second=196 amount=-10.05 +kerning first=8220 second=197 amount=-10.05 +kerning first=8220 second=221 amount=1.41 +kerning first=8220 second=224 amount=-8.65 +kerning first=8220 second=225 amount=-5.77 +kerning first=8220 second=226 amount=-5.77 +kerning first=8220 second=227 amount=-5.77 +kerning first=8220 second=228 amount=-5.77 +kerning first=8220 second=229 amount=-5.77 +kerning first=8220 second=230 amount=-5.77 +kerning first=8220 second=231 amount=-8.65 +kerning first=8220 second=232 amount=-8.65 +kerning first=8220 second=233 amount=-8.65 +kerning first=8220 second=234 amount=-8.65 +kerning first=8220 second=235 amount=-8.65 +kerning first=8220 second=242 amount=-8.65 +kerning first=8220 second=243 amount=-8.65 +kerning first=8220 second=244 amount=-8.65 +kerning first=8220 second=245 amount=-8.65 +kerning first=8220 second=246 amount=-8.65 +kerning first=8220 second=248 amount=-8.65 +kerning first=8220 second=249 amount=-4.29 +kerning first=8220 second=250 amount=-4.29 +kerning first=8220 second=251 amount=-4.29 +kerning first=8220 second=252 amount=-4.29 +kerning first=8220 second=339 amount=-8.65 +kerning first=8220 second=376 amount=1.41 +kerning first=8222 second=67 amount=-7.17 +kerning first=8222 second=71 amount=-7.17 +kerning first=8222 second=79 amount=-7.17 +kerning first=8222 second=81 amount=-7.17 +kerning first=8222 second=84 amount=-10.05 +kerning first=8222 second=85 amount=-2.88 +kerning first=8222 second=86 amount=-8.65 +kerning first=8222 second=87 amount=-8.65 +kerning first=8222 second=89 amount=-8.65 +kerning first=8222 second=199 amount=-7.17 +kerning first=8222 second=210 amount=-7.17 +kerning first=8222 second=211 amount=-7.17 +kerning first=8222 second=212 amount=-7.17 +kerning first=8222 second=213 amount=-7.17 +kerning first=8222 second=214 amount=-7.17 +kerning first=8222 second=216 amount=-7.17 +kerning first=8222 second=217 amount=-2.88 +kerning first=8222 second=218 amount=-2.88 +kerning first=8222 second=219 amount=-2.88 +kerning first=8222 second=220 amount=-2.88 +kerning first=8222 second=221 amount=-8.65 +kerning first=8222 second=338 amount=-7.17 +kerning first=8222 second=376 amount=-8.65 diff --git a/source/data/opensansr144/opensansr144.png b/source/data/opensansr144/opensansr144.png new file mode 100644 index 00000000..376ad15f Binary files /dev/null and b/source/data/opensansr144/opensansr144.png differ diff --git a/source/fontface.ts b/source/fontface.ts new file mode 100644 index 00000000..a7658a3e --- /dev/null +++ b/source/fontface.ts @@ -0,0 +1,293 @@ + +import { assert } from './auxiliaries'; + +import { Context } from './context'; +import { Glyph } from './glyph'; +import { Texture2 } from './texture2'; +import { GLfloat2, GLfloat4, GLsizei2 } from './tuples'; +import { Wizard } from './wizard'; + + +/** + * Font related data for glyph based text rendering. The glyph-based font face is described by, e.g., font-size, + * line spacing, a glyph catalogue, as well as kerning information. The glyph catalogue is based on a set of glyphs + * referring to a texture atlas (@see {@link Glyph}). All measures are provided in float even though most + * glyph-textures and associated font data is encoded via integer values. A font face explicitly relies on floating + * values to reduce the need of casting as well as to simplify the use for dpi aware text rendering. Most measures can + * be interpreted as points (by means of the unit pt), again, easing the use for arbitrary dpi. + * The font face interface is designed to access most basic font settings ascent, descent, and line gap (leading). + * Additional font settings are provided via interface but are derived from or mapped to the above mentioned three + * settings, e.g., font size is the sum of descent and ascent. This is to provide as much convenience measures for type + * setting/font rendering as possible. + * Note: This class does not provide dpi awareness, which has to be handled outside of this class, e.g., during + * layouting and rendering. + */ +export class FontFace { + + /** @see {@link base} */ + protected _base: number; + + /** @see {@link ascent} */ + protected _ascent = 0.0; + + /** @see {@link descent} */ + protected _descent = 0.0; + + /** @see {@link lineGap} */ + protected _lineGap = 0.0; + + /** @see {@link glyphTextureExtent} */ + protected _glyphTextureExtent: GLfloat2 = [0.0, 0.0]; + + /** @see {@link glyphTexturePadding} */ + protected _glyphTexturePadding: GLfloat4 = [0.0, 0.0, 0.0, 0.0]; + + /** @see {@link glyphTexturePadding} */ + protected _glyphTexture: Texture2; + + /** + * Map associating a glyph index to a glyph (sub image of a texture). + * @see {@link glyph}, @see {@link hasGlyph}, @see {@link addGlyph} + */ + protected _glyphs = new Map(); + + protected _context: Context; + + /** + * Constructs an unconfigured, empty font face specification. The appropriate setters should be used for configuring + * the font face. Alternatively, the font importer (@see {@link FontImporter}) provides the import of bitmap-font + * base configuration file ({@link http://www.angelcode.com/products/bmfont/}). + * @param context - Valid context to create the object for. + * @param identifier - Meaningful name for identification of this instances VAO and VBOs. + */ + constructor(context: Context, identifier?: string) { + this._context = context; + const gl = context.gl; + + identifier = identifier !== undefined && identifier !== `` ? identifier : this.constructor.name; + this._glyphTexture = new Texture2(context, `${identifier}GlyphAtlas`); + const internalFormat = Wizard.queryInternalTextureFormat(context, gl.RGBA, Wizard.Precision.byte); + this._glyphTexture.initialize(1, 1, internalFormat[0], gl.RGBA, internalFormat[1]); + } + + /** + * The size of the font in pt. The font size is the measure from the tops of the tallest glyphs (ascenders) to the + * bottom of the lowest descenders in pt. It is derived via the sum of ascent and descent. + * @return - The font size in pt (ascent + descent). + */ + get size(): number { + /* Note: this._descent is usually negative. */ + return this._ascent - this._descent; + } + + /** + * Set the font's base in pt. The base is the distance from the baseline to the top of the line in pt. + * @param base - The distance from the baseline to the top of the line in pt. + */ + set base(base: number) { + assert(base > 0.0, 'base should be larger than zero.'); + this._base = base; + } + get base(): number { + return this._base; + } + + /** + * Set the font's ascent in pt. The ascent is the distance from the baseline to the tops of the tallest glyphs + * (ascenders) in pt. + * @param ascent - The distance from the baseline to the topmost ascenders in pt. + */ + set ascent(ascent: number) { + assert(ascent > 0.0, 'ascent should be larger than zero.'); + this._ascent = ascent; + } + get ascent(): number { + return this._ascent; + } + + /** + * Set the font's descent in pt. The descent is the distance from the baseline to the lowest descenders in pt. + * Please note that this value is usually negative (if the fonts lowest descenders are below the baseline). + * @param descent - The distance from the baseline to the lowest descenders in pt. + */ + set descent(descent: number) { + /* No assert here: there might be fonts with their lowest descender above baseline. */ + // assert(descent < 0.f, ...); + this._descent = descent; + } + get descent(): number { + return this._descent; + } + + /** + * Set the font's leading/linegap in pt. The leading is the distance from the lowest descenders to the topmost + * ascenders of a subsequent text line in pt. + * @param lineGap - The gap between two subsequent lines of text in pt. + */ + set lineGap(lineGap: number) { + this._lineGap = lineGap; + } + get lineGap(): number { + return this._lineGap; + } + + /** + * Set the baseline-to-baseline distance in pt. Negative values will result in negative linegap. The line height is + * derived as follows: line_height = size + line_gap, or alternatively: line_height = size * line_space + * @param lineHeight - The line height (baseline-to-baseline distance) in pt. + */ + set lineHeight(lineHeight: number) { + this._lineGap = lineHeight - this.size; + } + get lineHeight(): number { + return this.size + this.lineGap; + } + + /** + * Set the relative baseline-to-baseline distance w.r.t. the font's size. The line space is mapped to line gap as + * follows: line_gap = size * (line_space - 1). A space < 1.0 will result in a negative line gap. + * @param lineSpace - The relative baseline-to-baseline distance w.r.t. the font's size. + */ + set lineSpace(lineSpace: number) { + this._lineGap = this.size * (lineSpace - 1); + } + /** + * The relative baseline-to-baseline distance w.r.t. the font's size. The relative line space is derived as follows: + * line_space = size / line_height; Note that the descent is usually a negative value. + * @return - The relative baseline-to-baseline distance w.r.t. the font's size. + */ + get lineSpace(): number { + if (this.lineHeight === 0.0) { + return this.lineHeight; + } + return this.size / this.lineHeight; + } + + /** + * Sets the glyph texture atlas extent. + * @param extent - The texture extent in px + */ + set glyphTextureExtent(extent: GLsizei2) { + assert(extent[0] > 0, 'expected extent.x to be larger than zero.'); + assert(extent[1] > 0, 'expected extent.y to be larger than zero.'); + this._glyphTextureExtent = extent; + } + /** + * The size/extent of the glyph texture in px. + * @return - The size/extent of the glyph texture in px. + */ + get glyphTextureExtent(): GLsizei2 { + return this._glyphTextureExtent; + } + + /** + * The padding applied to every glyph in px. This can only be set via setGlyphTexture. + * @param padding - CSS style (top, right, bottom, left) padding applied to every glyph within the texture in px. + */ + set glyphTexturePadding(padding: GLfloat4) { + assert(padding[0] >= 0.0, 'expected padding[0] to be larger than zero.'); + assert(padding[1] >= 0.0, 'expected padding[1] to be larger than zero.'); + assert(padding[2] >= 0.0, 'expected padding[2] to be larger than zero.'); + assert(padding[3] >= 0.0, 'expected padding[3] to be larger than zero.'); + this._glyphTexturePadding = padding; + } + get glyphTexturePadding(): GLfloat4 { + return this._glyphTexturePadding; + } + + /** + * The font face's associated glyph atlas. All glyph data is associated to this texture atlas. + * @param texture - The new texture atlas for all glyphs + */ + set glyphTexture(texture: Texture2) { + this._glyphTexture = texture; + } + get glyphTexture(): Texture2 { + return this._glyphTexture; + } + + /** + * Check if a glyph of a specific index is available. + * @return - True if a glyph for the provided index was added. + */ + hasGlyph(index: GLsizei): boolean { + return !!this._glyphs.get(index); + } + + /** + * Direct access to an indexed glyph. If the glyph does not exist, an empty glyph is returned without adding it to + * glyphs. The glyph atlas might be loaded asynchronously, thus, new glyphs are expected to be added via addGlyph. + * @param index - Index of the glyph to access. + * @return - Glyph with the matching index or an empty glyph, if index has not match + */ + glyph(index: GLsizei): Glyph { + const existingGlyph = this._glyphs.get(index); + if (existingGlyph) { + return existingGlyph; + } + const glyph = new Glyph(); + glyph.index = index; + return glyph; + } + + /** + * Add a glyph to the font face's set of glyphs. If the glyph already exists, the existing glyph remains. + * @param glyph - The glyph to add to the set of glyphs. + */ + addGlyph(glyph: Glyph): void { + assert(!(this._glyphs.get(glyph.index)), 'expected glyph to not already exist'); + this._glyphs.set(glyph.index, glyph); + } + + /** + * Generates aan array of all comprised glyph indices. + * @return - An array of all glyph indices available to this font face. + */ + arrayOfGlyphIndices(): Array { + return Array.from(this._glyphs.keys()); + } + + /** + * Check if a glyph is depictable/renderable. If the glyph's sub-texture vertical or horizontal extent is zero the + * glyph does not need to be depicted/rendered. E.g., spaces, line feeds, other control sequences as well as + * unknown glyphs do not need to be processed for rendering. + * @param index - Index of the glyph to access. + * @return - Returns true if the glyph needs to be depicted/rendered. + */ + depictable(index: GLsizei): boolean { + return this.glyph(index).depictable(); + } + + /** + * Kerning for a glyph and a subsequent glyph in pt. If the glyph or the subsequent glyph are unknown to this font + * face (assertion), 0.f will be returned. For more details on kerning, refer to the Glyph class. + * @param index - The current glyph index (e.g., of the current pen-position). + * @param subsequentIndex - The glyph index of the subsequent/next glyph. + * @return - The kerning (usually negative) between the two glyphs in pt. If either on of the glyphs is unknown to + * this font face or no specific kerning for the glyph pair is available a zero kerning is returned. + */ + kerning(index: GLsizei, subsequentIndex: GLsizei): number { + const glyph = this._glyphs.get(index); + if (!glyph) { + return 0.0; + } + return glyph.kerning(subsequentIndex); + } + + /** + * Set the kerning for a glyph w.r.t. to a subsequent glyph in pt. If the glyph is known to this font face, the + * values are forwarded to the glyphs kerning setter (see Glyph for more information). + * @param index - The target glyph index. + * @param subsequentIndex - The glyph index of the respective subsequent/next glyph. + * @param kerning - Kerning of the two glyphs in pt. + */ + setKerning(index: GLsizei, subsequentIndex: GLsizei, kerning: number): void { + const glyph = this._glyphs.get(index); + if (!glyph || !this.hasGlyph(subsequentIndex)) { + assert(false, 'expected glyph or glyph of subsequent index to exist.'); + return; + } + glyph.setKerning(subsequentIndex, kerning); + } + +} diff --git a/source/fontloader.ts b/source/fontloader.ts new file mode 100644 index 00000000..f5ea0bea --- /dev/null +++ b/source/fontloader.ts @@ -0,0 +1,317 @@ + +import { log, logIf, LogLevel } from './auxiliaries'; + +import { Context } from './context'; +import { fetchAsync } from './fetch'; +import { FontFace } from './fontface'; +import { Glyph } from './glyph'; +import { GLfloat2, GLfloat4 } from './tuples'; + +import Path = require('path'); + + +type StringPairs = Map; + +/** + * Loads the png image that displays the glyph atlas, that was prepared using a Distance Field Transform. It also + * loads all needed data to use that image from a fnt-file with the same name as the png image. It thus creates + * a font face (@see {@link FontFace}). + * + * Example: + * ``` + * let fontFace: FontFace = FontLoader.load(this.context, 'font/yourFont.fnt', false, callbackFunction); + * ``` + */ +export class FontLoader { + + /** + * False when the current loading process gets invalid. + */ + protected static _valid: boolean; + + /** + * Parses the info fields for padding values and stores them in the font face + * @param stream The stream of the 'info' identifier. + * @param fontFace The font face in which the padding is stored. + */ + protected static processInfo(stream: Array, fontFace: FontFace): void { + + const pairs: StringPairs = new Map(); + const success = this.readKeyValuePairs(stream, ['padding'], pairs); + + if (!success) { + this._valid = false; + return; + } + + const values = pairs.get('padding')!.split(','); + + if (values.length !== 4) { + log(LogLevel.Warning, `expected 4 values for padding, given ${values} (${values.length})`); + this._valid = false; + return; + } + + const padding: GLfloat4 = [ + /* top */ + parseFloat(values[2]), + /* right */ + parseFloat(values[1]), + /* bottom */ + parseFloat(values[3]), + /* left */ + parseFloat(values[0]), + ]; + + fontFace.glyphTexturePadding = padding; + } + + /** + * Parses the common fields for lineHeight, base, ascent, descent, scaleW and scaleH to store them + * in the font face. + * @param stream The stream of the 'common' identifier. + * @param fontFace The font face in which the parsed values are stored. + */ + protected static processCommon(stream: Array, fontFace: FontFace): void { + + const pairs: StringPairs = new Map(); + const success = this.readKeyValuePairs(stream, + ['lineHeight', 'base', 'ascent', 'descent', 'scaleW', 'scaleH'], pairs); + + if (!success) { + this._valid = false; + return; + } + + fontFace.base = parseFloat(pairs.get('base')!); + fontFace.ascent = parseFloat(pairs.get('ascent')!); + fontFace.descent = parseFloat(pairs.get('descent')!); + + if (fontFace.size <= 0.0) { + log(LogLevel.Warning, `expected fontFace.size to be greater than 0, given ${fontFace.size}`); + this._valid = false; + return; + } + + fontFace.lineHeight = parseFloat(pairs.get('lineHeight')!); + + fontFace.glyphTextureExtent = [ + parseFloat(pairs.get('scaleW')!), + parseFloat(pairs.get('scaleH')!), + ]; + } + + /** + * Parses a page to load the associated png-file, i.e., the glyph atlas. + * @param stream The stream of the 'page' identifier. + * @param fontFace The font face in which the loaded glyph texture is stored. + * @param filename The file name to find the png-file. + * @returns Promise for handling image load status. + */ + protected static processPage( + stream: Array, fontFace: FontFace, filepath: string): Promise { + + const pairs: StringPairs = new Map(); + const success = this.readKeyValuePairs(stream, ['file'], pairs); + + if (!success) { + log(LogLevel.Warning, `Could not read texture filename from fnt file.`); + this._valid = false; + } + + const path = Path.dirname(filepath) + `/`; + const filename = Path.basename(filepath, `.fnt`); + + const pngPath: string = path + filename + `.png`; + + return fontFace.glyphTexture.load(pngPath).catch((error) => { + log(LogLevel.Warning, `${error}. Could not load glyphTexture: ${pngPath}`); + this._valid = false; + }); + } + + /** + * Parses the char fields for character id (codepoint), x, y, width, height, xoffset, yoffset, xadvance to + * store them in the font face as instances of Glyph. + * This relies on fontFace.base and fontFace.glyphTextureExtent, so execute processCommon() first. + * @param stream The stream of the 'char' identifier. + * @param fontFace The font face in which the loaded glyph texture is stored. + */ + protected static processChar(stream: Array, fontFace: FontFace): void { + const pairs: StringPairs = new Map(); + const success = this.readKeyValuePairs(stream, + ['id', 'x', 'y', 'width', 'height', 'xoffset', 'yoffset', 'xadvance'], pairs); + + if (!success) { + this._valid = false; + return; + } + + const index: number = parseInt(pairs.get('id')!, 10); + logIf(index <= 0.0, LogLevel.Warning, + `expected glyph index to be greater than 0, given ${index}`); + + const glyph = new Glyph(); + + glyph.index = index; + + const extentScale: GLfloat2 = [ + 1.0 / fontFace.glyphTextureExtent[0], + 1.0 / fontFace.glyphTextureExtent[1], + ]; + const extent: GLfloat2 = [ + parseFloat(pairs.get('width')!), + parseFloat(pairs.get('height')!), + ]; + + glyph.subTextureOrigin = [ + parseFloat(pairs.get('x')!) * extentScale[0], + 1.0 - (parseFloat(pairs.get('y')!) + extent[1]) * extentScale[1], + ]; + + glyph.extent = extent; + + glyph.subTextureExtent[0] = extent[0] * extentScale[0]; + glyph.subTextureExtent[1] = extent[1] * extentScale[1]; + + glyph.bearingFromFontBaseAndOffset(fontFace.base, + parseFloat(pairs.get('xoffset')!), + parseFloat(pairs.get('yoffset')!), + ); + + glyph.advance = parseFloat(pairs.get('xadvance')!); + + fontFace.addGlyph(glyph); + } + + /** + * Parses the kerning fields for first and second character and the amount, to store them in the font face. + * @param stream The stream of the 'kerning' identifier. + * @param fontFace The font face in which the kerning tuples are stored. + */ + protected static processKerning(stream: Array, fontFace: FontFace): void { + const pairs: StringPairs = new Map(); + const success = this.readKeyValuePairs(stream, ['first', 'second', 'amount'], pairs); + + if (!success) { + this._valid = false; + return; + } + + const first: number = parseInt(pairs.get('first')!, 10); + if (first <= 0.0) { + log(LogLevel.Warning, `expected kerning's first to be greater than 0, given ${first}`); + this._valid = false; + return; + } + + const second: number = parseInt(pairs.get('second')!, 10); + if (second <= 0.0) { + log(LogLevel.Warning, `expected kerning's second to be greater than 0, given ${second}`); + this._valid = false; + return; + } + + const kerning: number = parseFloat(pairs.get('amount')!); + + fontFace.setKerning(first, second, kerning); + + return; + } + + /** + * Parses to find key-value pairs for given mandatory keys. + * @param stream The stream from which the pairs should be read. + * @param mandatoryKeys The found pairs are only valid if the mandatory keys are found. + * @param result key-value pairs, or undefined if not all mandatory keys are found. + * @returns success + */ + protected static readKeyValuePairs(stream: Array, mandatoryKeys: Array, + resultPairs: StringPairs): boolean { + + let key: string; + let value: string; + + for (const s of stream) { + const pair = s.split('='); + key = pair[0]; + value = pair[1]; + resultPairs.set(key, value); + } + + /* check if all required keys are provided */ + let valid = true; + mandatoryKeys.forEach((key) => valid = valid && resultPairs.has(key)); + + if (!valid) { + log(LogLevel.Warning, `Not all required keys are provided! Mandatory keys: ${mandatoryKeys}`); + } + + return valid; + } + + /** + * Asynchronously loads a fnt-file and a png-file of the same name, to create a font face from them. + * @param context The WebGL rendering context. + * @param filename The path to the fnt-file. + * @param headless Boolean for headless mode. + * @param onImageLoad Callback is called when the glyph atlas is loaded. + */ + static async load(context: Context, filename: string, headless: boolean): Promise { + const fontFace = new FontFace(context); + + this._valid = true; + + let text; + try { + text = await fetchAsync(filename, '', (text) => text); + } catch (e) { + /* promise rejected */ + log(LogLevel.Warning, `Could not load font file. filename is: ${filename}`); + this._valid = false; + } + + /* promise fulfilled */ + const lines = text.split('\n'); + + const promises = []; + for (const l of lines) { + if (!this._valid) { + break; + } + let line = l.split(' '); + const identifier = line[0]; + line = line.slice(1); + + /* tslint:disable-next-line:switch-default */ + switch (identifier) { + case 'info': + this.processInfo(line, fontFace); + break; + case 'common': + this.processCommon(line, fontFace); + break; + case 'page': + if (!headless) { + promises.push(this.processPage(line, fontFace, filename)); + } + break; + case 'char': + this.processChar(line, fontFace); + break; + case 'kerning': + this.processKerning(line, fontFace); + break; + } + } + + await Promise.all(promises); + + if (this._valid) { + return fontFace; + } else { + log(LogLevel.Warning, `No valid FontFace created.`); + return new FontFace(context, `invalid`); + } + } +} diff --git a/source/geometry.ts b/source/geometry.ts index 82900c52..3366a63a 100644 --- a/source/geometry.ts +++ b/source/geometry.ts @@ -34,7 +34,7 @@ export abstract class Geometry extends Initializable implements Bindable { super(); identifier = identifier !== undefined && identifier !== `` ? identifier : this.constructor.name; - this._vertexArray = new VertexArray(context, identifier + 'VAO'); + this._vertexArray = new VertexArray(context, `${identifier}VAO`); } diff --git a/source/gl-matrix-extensions.ts b/source/gl-matrix-extensions.ts index 33bb8126..9f3c4fef 100644 --- a/source/gl-matrix-extensions.ts +++ b/source/gl-matrix-extensions.ts @@ -217,7 +217,8 @@ namespace gl_matrix_extensions { } /** - * Constructs a vec3 from a vec4 with division by the w component applied. + * Constructs a vec3 from a vec4 with division by the w component applied. If the w component is zero, division + * skipped. * ``` * const v4: vec4 = vec4.fromValues(2, 4, 6, 2); * const v3: vec3 = fromVec4(v4); // v3 is [1, 2, 3] @@ -226,6 +227,9 @@ namespace gl_matrix_extensions { * @returns - Three component vector based on x. */ export function fromVec4(x: vec4): vec3 { + if (x[3] === 0) { + return vec3.fromValues(x[0], x[1], x[2]); + } return vec3.fromValues(x[0] / x[3], x[1] / x[3], x[2] / x[3]); } diff --git a/source/glyph.ts b/source/glyph.ts new file mode 100644 index 00000000..0ca88658 --- /dev/null +++ b/source/glyph.ts @@ -0,0 +1,167 @@ + +import { clampf2, GLclampf2, GLfloat2 } from './tuples'; + + +/** + * Glyph related data for glyph based text rendering. Most of the glyph data (except the advance) refers to the font + * face's glyph-texture. This class does not provide dpi awareness. This has to be handled outside of this class, e.g., + * during layouting and rendering. + */ +export class Glyph { + + /** @see {@link advance} */ + protected _advance: GLfloat; + + /** @see {@link bearing} */ + protected _bearing: GLfloat2 = [0.0, 0.0]; + + /** @see {@link extent} */ + protected _extent: GLfloat2 = [0.0, 0.0]; + + /** @see {@link index} */ + protected _index: GLsizei; + + /** @see {@link kernings} */ + protected _kernings = new Map(); + + /** @see {@link subTextureOrigin} */ + protected _subTextureOrigin: GLclampf2 = [0.0, 0.0]; + + /** @see {@link subTextureExtent} */ + protected _subTextureExtent: GLclampf2 = [0.0, 0.0]; + + constructor(index: GLsizei = 0, advance: GLfloat = 0) { + this._index = index; + this._advance = advance; + } + + /** + * Check if a glyph is depictable/renderable. If the glyph's sub texture vertical or horizontal extent is zero the + * glyph does not need to be depicted/rendered. E.g., spaces, line feeds, other control sequences as well as + * unknown glyphs do not need to be processed for rendering. + * @return - True if the glyph needs to be depicted/rendered. + */ + depictable(): boolean { + return this._subTextureExtent[0] > 0 && this._subTextureExtent[1] > 0; + } + + /** + * The glyph's kernel w.r.t. a subsequent glyph in pt. The kerning provides a(usually negative) offset along the + * baseline that can be used to move the pen-position respectively, i.e., the subsequent pen-position is computed + * as follows: pen-position + advance + kerning + * @param subsequentIndex - The subsequent glyph's index. + * @return - The kerning w.r.t. to the subsequent glyph in pt. If no kerning data is available for the subsequent + * glyph, the return value is zero indicating no kerning. + */ + kerning(subsequentIndex: GLsizei): GLfloat { + const kerning = this._kernings.get(subsequentIndex); + if (kerning !== undefined) { + return kerning; + } + return 0.0; + } + + /** + * Set the glyph's kernel w.r.t. a subsequent glyph in pt. @see {@link kerning} + * @param subsequentIndex - The subsequent glyph's index. + * @param kerning - The kerning value w.r.t. to the subsequent glyph in pt. Note that the kerning should be a + * negative value but is not enforced to be in terms of assertion or clamping. If kerning data for the subsequent + * glyph is already available it will be updated to the provided value. + */ + setKerning(subsequentIndex: GLsizei, kerning: GLfloat): void { + this._kernings.set(subsequentIndex, kerning); + } + + + /** + * Set the index of one single distinguishable character. + */ + set index(index: GLsizei) { + this._index = index; + } + get index(): GLsizei { + return this._index; + } + + /** + * Upper left position of the glyph's sub-texture. The upper left position refers to the glyph-texture that is + * specified by a font face (@see {@link FontFace}). It is the u and v coordinates pointing to the glyphs + * sub-texture within the texture atlas. The coordinates are normalized in [0;1]. + * @param origin - Normalized coordinates pointing to the upper left texel of the glyph's sub-texture. + */ + set subTextureOrigin(origin: GLclampf2) { + this._subTextureOrigin = clampf2(origin, 'texture origin'); + } + get subTextureOrigin(): GLclampf2 { + return this._subTextureOrigin; + } + + /** + * Width and height of the glyph's sub-texture. In combination with the sub-texture offset (subTextureOffset) the + * sub-texture rectangle is implicitly fully specified in normalized texture coordinates. Note: the extent + * comprises the font face's padding. + * @param extent - Normalized width and height of the glyph's sub-texture. + */ + set subTextureExtent(extent: GLclampf2) { + this._subTextureExtent = clampf2(extent, 'texture extent'); + } + get subTextureExtent(): GLclampf2 { + return this._subTextureExtent; + } + + /** + * The x and y offsets w.r.t. to the pen-position on the baseline. The horizontal bearing does not comprise the + * glyph-texture's padding provided by the owning font face (@see {@link FontFace}). The vertical bearing also does + * not comprises the glyph texture's padding and is the measured w.r.t. baseline. + * @param bearing - Horizontal and vertical bearing based on the glyph's origin/pen-position placed on the + * baseline in pt. + */ + set bearing(bearing: GLfloat2) { + this._bearing = bearing; + } + get bearing(): GLfloat2 { + return this._bearing; + } + + /** + * Convenience setter for the x and y bearings. The horizontal bearing does not comprise the glyph-texture's + * padding provided by the owning font face (see FontFace). The vertical bearing also does not comprise the glyph- + * texture's padding and is the measured w.r.t. baseline. + * The vertical bearing is computed as follows: bearingY = fontBase - (yOffset - top padding) + * The horizontal bearing equals the xOffset: bearingX = xOffset - left padding: + * @param fontBase - The font face's (FontFace) base-to-top distance in pt. + * @param xOffset - The glyphs horizontal offset without left padding. + * @param yOffset - The glyphs vertical offset w.r.t. the font's topmost ascenders, without the font's top + * padding in pt. + */ + bearingFromFontBaseAndOffset(fontBase: GLfloat, xOffset: GLfloat, yOffset: GLfloat): void { + this._bearing[0] = xOffset; + this._bearing[1] = fontBase - yOffset; + } + + /** + * Width and height of the glyph in pt. + * @param extent - The glyph's extent by means of width and height in pt. + */ + set extent(extent: GLfloat2) { + this._extent = extent; + } + get extent(): GLfloat2 { + return this._extent; + } + + /** + * Set the glyph's horizontal overall advance in pt. The horizontal advance comprises the font face's left and + * right padding, the glyphs (inner) width as well as the horizontal bearing (and often a glyph specific gap). + * E.g., advance = subTextureExtent_width + xOffset (+ gap), or alternatively: + * advance = xOffset + padding_left + glyph_width + padding_right (+ gap) + * @param advance - The glyphs horizontal advance (along the baseline) in pt. + */ + set advance(advance: GLfloat) { + this._advance = advance; + } + get advance(): GLfloat { + return this._advance; + } + +} diff --git a/source/glyphvertices.ts b/source/glyphvertices.ts new file mode 100644 index 00000000..fc67ae53 --- /dev/null +++ b/source/glyphvertices.ts @@ -0,0 +1,58 @@ + +import { vec3, vec4 } from 'gl-matrix'; + + +/** + * Information required for rendering a single glyph. Technical this could be denoted as a vertex of a vertex cloud. + */ +export interface GlyphVertex { + + /** + * Position of the glyph in normalized device coordinates. + */ + origin: vec3; + + /** + * Tangent vector (usually the label's baseline direction). The length of this vector is expected to be the advance + * of this glyphs geometry in baseline direction, i.e., it is used to derive the vertices using simple addition. + */ + tangent: vec3; + + /** + * Bitangent vector (orthogonal to the label's baseline). The length of this vector is expected to be the height of + * this glyphs geometry, i.e., it is used to derive the glyph vertices using simple addition. + */ + up: vec3; + + /** + * Sub image rect of the glyph in the glyph texture (uv-coordinates). + */ + uvRect: vec4; +} + +/** + * Vertex cloud that describes each glyph that is to be rendered on the screen. + */ +export class GlyphVertices extends Array { + + constructor(numGlyphs: number) { + super(); + + for (let i = 0; i < numGlyphs; ++i) { + + const vertex: GlyphVertex = { + origin: vec3.create(), + tangent: vec3.create(), + up: vec3.create(), + /* vec2 lowerLeft and vec2 upperRight in glyph texture (uv) */ + uvRect: vec4.create(), + }; + this.push(vertex); + } + } + + // optimize() { + + // } + +} diff --git a/source/label.ts b/source/label.ts new file mode 100644 index 00000000..2871e164 --- /dev/null +++ b/source/label.ts @@ -0,0 +1,416 @@ + +import { mat4, vec3 } from 'gl-matrix'; + +import { ChangeLookup } from './changelookup'; +import { Color } from './color'; +import { FontFace } from './fontface'; +import { GlyphVertices } from './glyphvertices'; +import { Text } from './text'; + + +/** + * Object comprising a text reference, a font face, and additional typographic information for type setting, rendering, + * and interaction. Multiple labels might reference the same text, but could be placed at different locations or + * rendered applying different font faces, styles etc. + */ +export class Label { + + /** @see {@link text} */ + protected _text: Text | string; + + /** @see {@link wordWrap} */ + protected _wordWrap = false; + + /** @see {@link alignment} */ + protected _alignment: Label.Alignment = Label.Alignment.Left; + + /** @see {@link lineAnchor} */ + protected _lineAnchor: Label.LineAnchor = Label.LineAnchor.Baseline; + + /** @see {@link lineWidth} */ + protected _lineWidth = 0.0; + + /** @see {@link fontSize} */ + protected _fontSize = 0.05; + + /** @see {@link fontSizeUnit} */ + protected _fontSizeUnit: Label.SpaceUnit = Label.SpaceUnit.World; + + /** @see {@link fontFace} */ + protected _fontFace: FontFace; + + /** @see {@link color} */ + protected _color: Color; + + /** @see {@link background} */ + protected _backgroundColor: Color; + + /** @see {@link transform} */ + protected _transform: mat4; + + /** @see {@link userTransform} */ + protected _userTransform: mat4; + + /** @see {@link extent} */ + protected _extent: [number, number]; + + /** @see {@link altered} */ + protected readonly _altered = Object.assign(new ChangeLookup(), { + any: false, color: false, resources: false, text: false, typesetting: false, + transform: false, userTransform: false, + }); + + /** + * Constructs an unconfigured, empty label. + * @param text - Valid context to create the object for. + * @param identifier - Meaningful name for identification of this instances VAO and VBOs. + */ + constructor(text: Text, fontFace: FontFace) { + this._text = text; + this._fontFace = fontFace; + this._transform = mat4.create(); + this._userTransform = mat4.create(); + this._extent = [0, 0]; + } + + /** + * Creates an Array of glyph vertices with given length, ready to be used in the Typesetter. + */ + protected prepareVertexStorage(): GlyphVertices { + const vertices = new GlyphVertices(this.length); + return vertices; + } + + /** + * Returns the character at the specified index. + * @param index - The zero-based index of the desired character. + * @returns character at the specified index + */ + charAt(index: number): string { + if (this._text instanceof Text) { + return this._text.text.charAt(index); + } + return this._text.charAt(index); + } + + /** + * Returns the Unicode value (codepoint) of the character at the specified location. + * @param index - The zero-based index of the desired character. If there is no character at the specified index, + * NaN is returned. + * @returns codepoint of the char at given index or NaN + */ + charCodeAt(index: number): number { + if (this._text instanceof Text) { + return this._text.text.charCodeAt(index); + } + return this._text.charCodeAt(index); + } + + /** + * Returns, whether or not the character at a given index is equal to the default or the text's line feed character. + * @param index - The zero-based index of the desired character. If there is no character at the specified index, + * NaN is returned. + * @returns true if char at given index equals the text's line feed character + */ + lineFeedAt(index: number): boolean { + return this.charAt(index) === this.lineFeed; + } + + + /** + * Gets the kerning value before (i.e., left in left-to-right writing systems) the given glyph index. + * @param index index of the glyph in this label + * @returns kerning value before glyph at given index + */ + kerningBefore(index: number): number { + if (index < 1 || index > this.length) { + return NaN; + } + return this._fontFace.kerning(this.charCodeAt(index - 1), this.charCodeAt(index)); + } + + /** + * Gets the kerning value after (i.e., right in left-to-right writing systems) the given glyph index. + * @param index index of the glyph in this label + * @returns kerning value after glyph at given index + */ + kerningAfter(index: number): number { + if (index < 0 || index > this.length - 1) { + return NaN; + } + return this._fontFace.kerning(this.charCodeAt(index), this.charCodeAt(index + 1)); + } + + /** + * Returns the advancement of the glyph at given index. + * @param index - The zero-based index of the desired character. If there is no character at the specified index, + * NaN is returned. + * @returns advancement of the glyph at given index or NaN + */ + advance(index: number): number { + if (index < 0 || index > this.length) { + return NaN; + } + return this._fontFace.glyph(this.charCodeAt(index)).advance; + } + + + /** + * Text that is to be rendered. + */ + set text(text: Text | string) { + this._altered.alter('text'); + this._text = text; + } + get text(): Text | string { + return this._text; + } + + /** + * Length of the text, i.e., number of characters within the text. + */ + get length(): number { + return this._text.length; + } + + /** + * Character that is to be used for Line feed. + */ + get lineFeed(): string { + if (this._text instanceof Text) { + return this._text.lineFeed; + } + return Text.DEFAULT_LINEFEED; + } + + /** + * Whether or not words can be wrapped at the end of a line. + * @param wrap - `true` if word wrap is enabled, else `false` + */ + set wordWrap(wrap: boolean) { + if (this._wordWrap === wrap) { + return; + } + this._altered.alter('typesetting'); + this._wordWrap = wrap; + } + get wordWrap(): boolean { + return this._wordWrap; + } + + /** + * Horizontal text alignment for typesetting. + */ + set alignment(alignment: Label.Alignment) { + if (this._alignment === alignment) { + return; + } + this._altered.alter('typesetting'); + this._alignment = alignment; + } + get alignment(): Label.Alignment { + return this._alignment; + } + + /** + * Vertical text anchor point used for positional reference. + */ + set lineAnchor(anchor: Label.LineAnchor) { + if (this._lineAnchor === anchor) { + return; + } + this._altered.alter('typesetting'); + this._lineAnchor = anchor; + } + get lineAnchor(): Label.LineAnchor { + return this._lineAnchor; + } + + /** + * Width of a single line (in pt or w.r.t. font face scaling in world space respectively). The width of the line + * is not intended to be set explicitly, but implicitly via transformations/label placement. + */ + get lineWidth(): number { + return this._lineWidth; + } + + /** + * The currently used font size. + * (@see {@link fontSizeUnit}) + */ + set fontSize(newSize: number) { + if (this._fontSize === newSize) { + return; + } + this._altered.alter('typesetting'); + this._altered.alter('transform'); + this._fontSize = newSize; + } + get fontSize(): number { + return this._fontSize; + } + + /** + * This unit is used for the font size. + * (@see {@link fontSize}) + */ + set fontSizeUnit(newUnit: Label.SpaceUnit) { + if (this._fontSizeUnit === newUnit) { + return; + } + this._altered.alter('typesetting'); + this._altered.alter('transform'); + this._fontSizeUnit = newUnit; + } + get fontSizeUnit(): Label.SpaceUnit { + return this._fontSizeUnit; + } + + /** + * Font face used for typesetting, transformation, and rendering. + */ + set fontFace(fontFace: FontFace) { + if (this._fontFace === fontFace) { + return; + } + this._altered.alter('typesetting'); + this._altered.alter('resources'); + this._fontFace = fontFace; + } + get fontFace(): FontFace { + return this._fontFace; + } + + /** + * Color used for text rendering. + */ + set color(color: Color) { + if (this._color.equals(color)) { + return; + } + this._altered.alter('color'); + this._color = color; + } + get color(): Color { + return this._color; + } + + /** + * Color used for background of text rendering. + */ + set backgroundColor(color: Color) { + if (this._backgroundColor.equals(color)) { + return; + } + this._altered.alter('color'); + this._backgroundColor = color; + } + get backgroundColor(): Color { + return this._backgroundColor; + } + + + /** + * Transformation used to move, scale, rotate, skew, etc. the label into an arbitrary coordinate space (e.g., + * screen space, world space, ...). This can be set either explicitly or implicitly using various transformation + * utility functions. + */ + set transform(transform: mat4) { + if (mat4.equals(this._transform, transform)) { + return; + } + this._altered.alter('transform'); + this._transform = transform; + } + get transform(): mat4 { + + const s = this.fontSize / this._fontFace.size; + + const t: mat4 = mat4.create(); + mat4.scale(t, this._transform, vec3.fromValues(s, s, s)); + + return t; + } + + /** + * This just stores a transform for the user. The user takes care of using this appropriately + * (e.g., for calculations to the final transform). + */ + set userTransform(t: mat4) { + this._altered.alter('userTransform'); + this._userTransform = t; + } + get userTransform(): mat4 { + return this._userTransform; + } + + /** + * The typesetter sets this extent after typesetting and applying the transform. + */ + set extent(e: [number, number]) { + this._extent = e; + } + /** + * Returns the width and height of the typset label. Both are zero if not typeset yet. + */ + get extent(): [number, number] { + return this._extent; + } + + /** + * Convenience getter to the label's text as string. + * @returns the label's text as string + */ + toString(): string { + if (this._text instanceof Text) { + return this._text.text; + } + return this._text; + } + + /* + * Whether or not any property or the referenced text has changed requiring, e.g., the new typesetting. + * The alteration status can be reset using `reset` (@see {@link reset}). + */ + get altered(): boolean { + return this._altered.any || (this._text instanceof Text ? this._text.altered : false); + } + + /** + * Intended for resetting alteration status. + */ + reset(): void { + this._altered.reset(); + } + +} + +export namespace Label { + + export enum Alignment { + Left = 'left', + Center = 'center', + Right = 'right', + } + + export enum LineAnchor { + Top = 'top', + Ascent = 'ascent', + Center = 'center', + Baseline = 'baseline', + Descent = 'descent', + Bottom = 'bottom', + } + + /** + * This unit is used for the font size. + */ + export enum SpaceUnit { + /* abstract world unit */ + World = 'world', + /* screen pixel */ + Px = 'px', + /** @todo Pt for point unit */ + } + +} diff --git a/source/labelgeometry.ts b/source/labelgeometry.ts new file mode 100644 index 00000000..75edbbc9 --- /dev/null +++ b/source/labelgeometry.ts @@ -0,0 +1,212 @@ + +import { assert } from './auxiliaries'; + +import { Buffer } from './buffer'; +import { Context } from './context'; +import { Geometry } from './geometry'; +import { Initializable } from './initializable'; + + +/** + * Gathers vertices and other data needed for drawing all labels using the glyphquad-shaders. + * + * Example usage: + * + * const labelGeometry = new LabelGeometry(this._context); + * labelGeometry = new LabelGeometry(this._context); + * const aVertex = this._program.attribute('a_quadVertex', 0); + * const aTexCoord = this._program.attribute('a_texCoord', 1); + * const aOrigin = this._program.attribute('a_origin', 2); + * const aTangent = this._program.attribute('a_tangent', 3); + * const aUp = this._program.attribute('a_up', 4); + * + * labelGeometry.initialize(aVertex, aTexCoord, aOrigin, aTangent, aUp); + * labelGeometry.initialize(aVertex, aTexCoord, aOrigin, aTangent, aUp); + * + * .... + * + * labelGeometry.setTexCoords(Float32Array.from(texCoords)); + * labelGeometry.setGlyphCoords(Float32Array.from(origins), Float32Array.from(tangents), Float32Array.from(ups)); + * + * .... + * + * labelGeometry.bind(); + * labelGeometry.draw(); + * labelGeometry.unbind(); + */ +export class LabelGeometry extends Geometry { + + /** + * These 2D vertices are equal for all quads, used for instanced rendering. Their actual position will be changed + * in the vertex shader, based on origins, tangents and up-vector attributes. + * 2-------4 + * | \ | + * | \ | + * 1-------3 + */ + protected _vertices: Float32Array = new Float32Array(0); + /** + * Texture coordinates (uv) for every glyph, format: ll.x, ll.y, ur.x, ur.y + */ + protected _texCoords: Float32Array = new Float32Array(0); + /** + * The coordinates of the lower left corner of every glyph. Its interpretation depends on the shader, + * usually it's a 3-component vector in world space. + */ + protected _origins: Float32Array = new Float32Array(0); + /** + * The tangent vector for every glyph (direction along base line). Its interpretation depends on the shader, + * usually it's a 3-component vector in world space. + */ + protected _tangents: Float32Array = new Float32Array(0); + /** + * The up vector for every glyph (orthogonal to its tangent vector). Its interpretation depends on the shader, + * usually it's a 3-component vector in world space. + */ + protected _ups: Float32Array = new Float32Array(0); + + /** + * Object constructor, requires a context and an identifier. + * @param context - Valid context to create the object for. + * @param identifier - Meaningful name for identification of this instance. + */ + constructor(context: Context, identifier?: string) { + super(context, identifier); + + assert(context.isWebGL2 || context.supportsInstancedArrays, `Support for Instanced Arrays is required.`); + + /* Generate identifier from constructor name if none given. */ + identifier = identifier !== undefined && identifier !== `` ? identifier : this.constructor.name; + + const vertexVBO = new Buffer(context, `${identifier}VBO`); + this._buffers.push(vertexVBO); + const texCoordBuffer = new Buffer(context, `${identifier}TexCoordBuffer`); + this._buffers.push(texCoordBuffer); + const originBuffer = new Buffer(context, `${identifier}OriginBuffer`); + this._buffers.push(originBuffer); + const tangentBuffer = new Buffer(context, `${identifier}TangentBuffer`); + this._buffers.push(tangentBuffer); + const upBuffer = new Buffer(context, `${identifier}UpBuffer`); + this._buffers.push(upBuffer); + } + + /** + * Binds the vertex buffer object (VBO) to an attribute binding point of a given, pre-defined index. + * @param indices indices of buffers to bind + */ + protected bindBuffers(indices: Array): void { + const gl = this.context.gl; + const gl2facade = this.context.gl2facade; + + /* Please note the implicit bind in attribEnable */ + + /* quadVertex */ + this._buffers[0].attribEnable(indices[0], 2, gl.FLOAT, false, 0, 0, true, false); + gl2facade.vertexAttribDivisor(indices[0], 0); + /* texCoords */ + this._buffers[1].attribEnable(indices[1], 4, gl.FLOAT, false, 4 * 4, 0, true, false); + gl2facade.vertexAttribDivisor(indices[1], 1); + /* origin */ + this._buffers[2].attribEnable(indices[2], 3, gl.FLOAT, false, 3 * 4, 0, true, false); + gl2facade.vertexAttribDivisor(indices[2], 1); + /* tangent */ + this._buffers[3].attribEnable(indices[3], 3, gl.FLOAT, false, 3 * 4, 0, true, false); + gl2facade.vertexAttribDivisor(indices[3], 1); + /* up */ + this._buffers[4].attribEnable(indices[4], 3, gl.FLOAT, false, 3 * 4, 0, true, false); + gl2facade.vertexAttribDivisor(indices[4], 1); + } + + /** + * Unbinds the vertex buffer object (VBO) and disables the binding point. + * @param indices indices of buffers to unbind + */ + protected unbindBuffers(indices: Array): void { + /* Please note the implicit unbind in attribEnable is skipped */ + const l = this._buffers.length; + for (let i = 0; i < l; i++) { + this._buffers[i].attribDisable(indices[i], true, true); + } + } + + /** + * Specifies/invokes the draw of all labels. + */ + @Initializable.assert_initialized() + draw(): void { + const gl = this.context.gl; + const count = this._origins.length / 3; + + this.context.gl2facade.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count); + } + + /** + * Creates the vertex buffer object (VBO) and creates and initializes the buffer's data store. + * @param aQuadVertex - Attribute binding point for vertices. + * @param aTexCoord - Attribute binding point for texture coordinates. + * @param aOrigin - Attribute binding point for glyph origin coordinates + * @param aTangent - Attribute binding point for glyph tangent coordinates. + * @param aUp - Attribute binding point for glyph up-vector coordinates. + */ + initialize(aQuadVertex: GLuint, aTexCoord: GLuint, aOrigin: GLuint, aTangent: GLuint, aUp: GLuint): boolean { + + const gl = this.context.gl; + + const valid = super.initialize( + [gl.ARRAY_BUFFER, gl.ARRAY_BUFFER, gl.ARRAY_BUFFER, gl.ARRAY_BUFFER, gl.ARRAY_BUFFER] + , [aQuadVertex, aTexCoord, aOrigin, aTangent, aUp]); + + /** + * These vertices are equal for all quads. There actual position will be changed using + * origin, tangent and up(-vector). + */ + this._vertices = Float32Array.from([0, 0, 0, 1, 1, 0, 1, 1]); + this._buffers[0].data(this._vertices, gl.STATIC_DRAW); + + return valid; + } + + /** + * Use this method to set (or update) the glyph coordinates, e.g. after typesetting or after the calculations + * of a placement algorithm. The actuall interpretation of those buffers depends on the shader, + * usually they are 3-component vector in world space (provided as flat array.) + * @param origins coordinates of the lower left corner of every glyph + * @param tangents tangent vector for every glyph (direction along base line) + * @param ups up vector for every glyph (orthogonal to its tangent vector) + */ + setGlyphCoords(origins: Float32Array, tangents: Float32Array, ups: Float32Array): void { + + assert(this._buffers[2] !== undefined && this._buffers[0].object instanceof WebGLBuffer, + `expected valid WebGLBuffer`); + assert(this._buffers[3] !== undefined && this._buffers[0].object instanceof WebGLBuffer, + `expected valid WebGLBuffer`); + assert(this._buffers[4] !== undefined && this._buffers[0].object instanceof WebGLBuffer, + `expected valid WebGLBuffer`); + + this._origins = origins; + this._tangents = tangents; + this._ups = ups; + + const gl = this.context.gl; + /** @todo is DYNAMIC_DRAW more appropriate? */ + this._buffers[2].data(this._origins, gl.STATIC_DRAW); + this._buffers[3].data(this._tangents, gl.STATIC_DRAW); + this._buffers[4].data(this._ups, gl.STATIC_DRAW); + } + + /** + * Use this method to set (or update) the texture coordinates for every glyph, e.g. after typesetting. + * @param texCoords The texture coordinates for every glyph, format: ll.x, ll.y, ur.x, ur.y + */ + setTexCoords(texCoords: Float32Array): void { + + assert(this._buffers[1] !== undefined && this._buffers[1].object instanceof WebGLBuffer, + `expected valid WebGLBuffer`); + + this._texCoords = texCoords; + + const gl = this.context.gl; + /** @todo is DYNAMIC_DRAW more appropriate? */ + this._buffers[1].data(this._texCoords, gl.STATIC_DRAW); + } +} diff --git a/source/labelrenderer.ts b/source/labelrenderer.ts new file mode 100644 index 00000000..db0a659b --- /dev/null +++ b/source/labelrenderer.ts @@ -0,0 +1,426 @@ + +import { assert } from './auxiliaries'; + +import { mat4, vec3 } from 'gl-matrix'; + +import { AccumulatePass } from './accumulatepass'; +import { AntiAliasingKernel } from './antialiasingkernel'; +import { BlitPass } from './blitpass'; +import { Camera } from './camera'; +import { Context } from './context'; +import { DefaultFramebuffer } from './defaultframebuffer'; +import { Framebuffer } from './framebuffer'; +import { MouseEventProvider } from './mouseeventprovider'; +import { Navigation } from './navigation'; +import { Program } from './program'; +import { Renderbuffer } from './renderbuffer'; +import { Invalidate, Renderer } from './renderer'; +import { Shader } from './shader'; +import { Texture2 } from './texture2'; + +import { FontFace } from './fontface'; +import { FontLoader } from './fontloader'; +import { LabelGeometry } from './labelgeometry'; +import { Position2DLabel } from './position2dlabel'; +import { Position3DLabel } from './position3dlabel'; +import { Text } from './text'; + +import { TestNavigation } from './debug/testnavigation'; + + +/** + * This is ugly, but it should do the trick for now: + * Later, we want to have a labelrenderpass and a labelpositionpass. + * The first one bakes the geometry, the second one adapts the placement regarding dynamic placement algorithms. + * For now, we will have both as a labelrenderer, and split it up later. + * @todo implement label render pass + */ +export class LabelRenderer extends Renderer { + + protected _extensions = false; + protected _program: Program; + + protected _ndcOffsetKernel: AntiAliasingKernel; + protected _uNdcOffset: WebGLUniformLocation; + protected _uFrameNumber: WebGLUniformLocation; + + protected _accumulate: AccumulatePass; + protected _blit: BlitPass; + + protected _camera: Camera; + protected _uViewProjection: WebGLUniformLocation; + + protected _defaultFBO: DefaultFramebuffer; + protected _colorRenderTexture: Texture2; + protected _depthRenderbuffer: Renderbuffer; + protected _intermediateFBO: Framebuffer; + + protected _testNavigation: TestNavigation; + protected _navigation: Navigation; + + protected _fontFace: FontFace; + protected _2DLabelGeometry: LabelGeometry; + protected _3DLabelGeometry: LabelGeometry; + protected _uGlyphAtlas: WebGLUniformLocation; + + /** + * Initializes and sets up rendering passes, navigation, loads a font face and links shaders with program. + * @param context valid context to create the object for. + * @param identifier meaningful name for identification of this instance. + * @param mouseEventProvider required for mouse interaction + * @returns whether initialization was successful + */ + protected onInitialize(context: Context, callback: Invalidate, + mouseEventProvider: MouseEventProvider, + /* keyEventProvider: KeyEventProvider, */ + /* touchEventProvider: TouchEventProvider */): boolean { + + this.loadFont(context); + + const gl = this._context.gl; + const gl2facade = this._context.gl2facade; + + /* Enable required extensions. */ + + if (this._extensions === false && this._context.isWebGL1) { + assert(this._context.supportsStandardDerivatives, `expected OES_standard_derivatives support`); + /* tslint:disable-next-line:no-unused-expression */ + this._context.standardDerivatives; + this._extensions = true; + } + + /* Create and configure program and geometry. */ + + const vert = new Shader(this._context, gl.VERTEX_SHADER, 'glyphquad.vert'); + vert.initialize(require('./shaders/glyphquad.vert')); + + const frag = new Shader(this._context, gl.FRAGMENT_SHADER, 'glyphquad.frag'); + frag.initialize(require('./shaders/glyphquad.frag')); + + this._program = new Program(this._context); + this._program.initialize([vert, frag]); + + this._uNdcOffset = this._program.uniform('u_ndcOffset'); + this._uFrameNumber = this._program.uniform('u_frameNumber'); + this._uViewProjection = this._program.uniform('u_viewProjection'); + + this._uGlyphAtlas = this._program.uniform('u_glyphs'); + + this._2DLabelGeometry = new LabelGeometry(this._context); + this._3DLabelGeometry = new LabelGeometry(this._context); + const aVertex = this._program.attribute('a_quadVertex', 0); + const aTexCoord = this._program.attribute('a_texCoord', 1); + const aOrigin = this._program.attribute('a_origin', 2); + const aTangent = this._program.attribute('a_tangent', 3); + const aUp = this._program.attribute('a_up', 4); + + this._2DLabelGeometry.initialize(aVertex, aTexCoord, aOrigin, aTangent, aUp); + this._3DLabelGeometry.initialize(aVertex, aTexCoord, aOrigin, aTangent, aUp); + + this._ndcOffsetKernel = new AntiAliasingKernel(this._multiFrameNumber); + + /* Create framebuffers, textures, and render buffers. */ + + this._defaultFBO = new DefaultFramebuffer(this._context, 'DefaultFBO'); + this._defaultFBO.initialize(); + + this._colorRenderTexture = new Texture2(this._context, 'ColorRenderTexture'); + this._depthRenderbuffer = new Renderbuffer(this._context, 'DepthRenderbuffer'); + + this._intermediateFBO = new Framebuffer(this._context, 'IntermediateFBO'); + + /* Create and configure accumulation pass. */ + + this._accumulate = new AccumulatePass(this._context); + this._accumulate.initialize(); + this._accumulate.precision = this._framePrecision; + this._accumulate.texture = this._colorRenderTexture; + // this._accumulate.depthStencilAttachment = this._depthRenderbuffer; + + /* Create and configure blit pass. */ + + this._blit = new BlitPass(this._context); + this._blit.initialize(); + this._blit.readBuffer = gl2facade.COLOR_ATTACHMENT0; + this._blit.drawBuffer = gl.BACK; + this._blit.target = this._defaultFBO; + + /* Create and configure test navigation. */ + + this._camera = new Camera(); + this._camera.center = vec3.fromValues(0.0, 0.0, 0.0); + this._camera.up = vec3.fromValues(0.0, 1.0, 0.0); + this._camera.eye = vec3.fromValues(0.0, 0.0, 2.0); + this._camera.near = 0.1; + this._camera.far = 8.0; + + /* Initialize navigation */ + this._navigation = new Navigation(callback, mouseEventProvider); + this._navigation.camera = this._camera; + + return true; + } + + /** + * Uninitializes Buffers, Textures and and Programm. + */ + protected onUninitialize(): void { + super.uninitialize(); + + this._uNdcOffset = -1; + this._uFrameNumber = -1; + this._uGlyphAtlas = -1; + this._program.uninitialize(); + + this._2DLabelGeometry.uninitialize(); + this._3DLabelGeometry.uninitialize(); + + this._intermediateFBO.uninitialize(); + this._defaultFBO.uninitialize(); + this._colorRenderTexture.uninitialize(); + this._depthRenderbuffer.uninitialize(); + + this._blit.uninitialize(); + } + + /** + * This is invoked in order to check if rendering of a frame is required by means of implementation specific + * evaluation (e.g., lazy non continuous rendering). Regardless of the return value a new frame (preparation, + * frame, swap) might be invoked anyway, e.g., when update is forced or canvas or context properties have changed + * or the renderer was invalidated @see{@link invalidate}. + * Updates the navigaten and the AntiAliasingKernel. + * @returns whether to redraw + */ + protected onUpdate(): boolean { + + this._ndcOffsetKernel = new AntiAliasingKernel(this._multiFrameNumber); + + this._navigation.update(); + + return this._altered.any || this._camera.altered; + } + + /** + * This is invoked in order to prepare rendering of one or more frames, regarding multi-frame rendering and + * camera-updates. + */ + protected onPrepare(): void { + + const gl = this._context.gl; + const gl2facade = this._context.gl2facade; + + if (!this._intermediateFBO.initialized) { + this._colorRenderTexture.initialize(this._frameSize[0], this._frameSize[1], + this._context.isWebGL2 ? gl.RGBA8 : gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE); + this._depthRenderbuffer.initialize(this._frameSize[0], this._frameSize[1], gl.DEPTH_COMPONENT16); + this._intermediateFBO.initialize([[gl2facade.COLOR_ATTACHMENT0, this._colorRenderTexture] + , [gl.DEPTH_ATTACHMENT, this._depthRenderbuffer]]); + + this._camera.aspect = this._frameSize[0] / this._frameSize[1]; + + } else if (this._altered.frameSize) { + this._intermediateFBO.resize(this._frameSize[0], this._frameSize[1]); + this._camera.viewport = [this._frameSize[0], this._frameSize[1]]; + this._camera.aspect = this._frameSize[0] / this._frameSize[1]; + /** @todo + * update the geometry of the labels that use pt sizes (e.g. labels in screen space) + * and/or update: labels that get too small (to be readable) should not be rendered anymore + * (a.k.a. threshold for readability) + */ + // this.setupScene(); + } + + if (this._altered.canvasSize) { + this._camera.aspect = this._canvasSize[0] / this._canvasSize[1]; + } + + if (this._altered.multiFrameNumber) { + this._ndcOffsetKernel.width = this._multiFrameNumber; + } + + if (this._altered.framePrecision) { + this._accumulate.precision = this._framePrecision; + } + + if (this._altered.clearColor) { + this._intermediateFBO.clearColor(this._clearColor); + } + + this._accumulate.update(); + + if (this._camera.altered) { + this._program.bind(); + gl.uniformMatrix4fv(this._uViewProjection, gl.GL_FALSE, this._camera.viewProjection); + this._program.unbind(); + } + + this._altered.reset(); + this._camera.altered = false; + } + + /** + * After (1) update and (2) preparation are invoked, a frame is invoked. Renders both 2D and 3D labels. + * @param frameNumber for intermediate frames in accumulation rendering + */ + protected onFrame(frameNumber: number): void { + const gl = this._context.gl; + + gl.viewport(0, 0, this._frameSize[0], this._frameSize[1]); + this._camera.viewport = [this._frameSize[0], this._frameSize[1]]; + + let wasBlendEnabled = false; + const oldBlendSRC: any = gl.getParameter(gl.BLEND_SRC_RGB); + const oldBlendDST: any = gl.getParameter(gl.BLEND_DST_RGB); + + wasBlendEnabled = gl.isEnabled(gl.BLEND); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + this._program.bind(); + + const ndcOffset = this._ndcOffsetKernel.get(frameNumber); + ndcOffset[0] = 2.0 * ndcOffset[0] / this._frameSize[0]; + ndcOffset[1] = 2.0 * ndcOffset[1] / this._frameSize[1]; + gl.uniform2fv(this._uNdcOffset, ndcOffset); + gl.uniform1i(this._uFrameNumber, frameNumber); + + gl.uniformMatrix4fv(this._uViewProjection, gl.GL_FALSE, this._camera.viewProjection); + + this._fontFace.glyphTexture.bind(gl.TEXTURE0); + gl.uniform1i(this._uGlyphAtlas, 0); + + this._intermediateFBO.clear(gl.COLOR_BUFFER_BIT, true, false); + + this._3DLabelGeometry.bind(); + this._3DLabelGeometry.draw(); + this._3DLabelGeometry.unbind(); + + gl.uniformMatrix4fv(this._uViewProjection, gl.GL_FALSE, mat4.create()); + + this._2DLabelGeometry.bind(); + this._2DLabelGeometry.draw(); + this._2DLabelGeometry.unbind(); + + this._intermediateFBO.unbind(); + + this._accumulate.frame(frameNumber); + + this._fontFace.glyphTexture.unbind(gl.TEXTURE0); + gl.blendFunc(oldBlendSRC, oldBlendDST); + if (!wasBlendEnabled) { + gl.disable(gl.BLEND); + } + } + + /** + * After (1) update, (2) preparation, and (3) frame are invoked, a swap is invoked for multi-frame rendering. + */ + protected onSwap(): void { + this._blit.framebuffer = this._accumulate.framebuffer ? + this._accumulate.framebuffer : this._blit.framebuffer = this._intermediateFBO; + this._blit.frame(); + } + + /** + * Loads font files and creates a fontface. + * @param context valid context to create the FontFace for. + */ + protected loadFont(context: Context): void { + + /* This is a placeholder until the 'real' fontFace is loaded asynchronously by the fontLoader */ + const fontFace: FontFace = new FontFace(context); + + FontLoader.load(context, './data/opensansr144/opensansr144.fnt', false).then( + (fontFace) => { + this._fontFace = fontFace; + this.setupScene(); + this.invalidate(true); + }, + ); + this._fontFace = fontFace; + } + + /** + * Sets up an example scene with 2D and 3D labels and sets the corresponding data on LabelGeometries. + */ + protected setupScene(): void { + + /** OpenLL 2D Labels */ + + const pos2Dlabel = new Position2DLabel(new Text('Hello Position 2D!'), this._fontFace); + pos2Dlabel.fontSize = 40; + + /* position values in px, since fontSizeUnit is set to SpaceUnit.Px */ + pos2Dlabel.setPosition(-100, 0); + pos2Dlabel.setDirection(0.5, -0.5); + + let glyphVertices = pos2Dlabel.typeset(this._frameSize); + + /* fill buffers */ + let origins: Array = []; + let tangents: Array = []; + let ups: Array = []; + let texCoords: Array = []; + + let l = glyphVertices.length; + + for (let i = 0; i < l; i++) { + const v = glyphVertices[i]; + + origins.push.apply(origins, v.origin); + tangents.push.apply(tangents, v.tangent); + ups.push.apply(ups, v.up); + texCoords.push.apply(texCoords, v.uvRect); + } + + this._2DLabelGeometry.setTexCoords(Float32Array.from(texCoords)); + this._2DLabelGeometry.setGlyphCoords( + Float32Array.from(origins), Float32Array.from(tangents), Float32Array.from(ups)); + + /** OpenLL 3D Labels */ + const pos3Dlabel = new Position3DLabel(new Text('Hello Position 3D!'), this._fontFace); + pos3Dlabel.fontSize = 0.1; + + /* position values in world, since fontSizeUnit is set to SpaceUnit.World */ + pos3Dlabel.setPosition(0.0, 0.1, -0.5); + pos3Dlabel.setDirection(0.0, 1.0, 0.0); + pos3Dlabel.setUp(-1.0, 0.0, 0.0); + + glyphVertices = pos3Dlabel.typeset(); + + const shadowPos3Dlabel = new Position3DLabel(new Text('Hello Position Shadow'), this._fontFace); + shadowPos3Dlabel.setPosition(0.0, 0.1, -0.5); + shadowPos3Dlabel.fontSize = 0.1; + shadowPos3Dlabel.setDirection(0.0, 1.0, 0.0); + shadowPos3Dlabel.setUp(0.0, 0.0, -1.0); + + glyphVertices = glyphVertices.concat(shadowPos3Dlabel.typeset()); + + const anotherPos3Dlabel = new Position3DLabel(new Text('Yet another 3D Label'), this._fontFace); + anotherPos3Dlabel.setPosition(0.2, -0.1, 0.0); + anotherPos3Dlabel.setDirection(-1.0, 0.0, 0.0); + anotherPos3Dlabel.setUp(0.0, -1.0, 0.0); + glyphVertices = glyphVertices.concat(anotherPos3Dlabel.typeset()); + + /* fill buffers */ + origins = []; + tangents = []; + ups = []; + texCoords = []; + + l = glyphVertices.length; + + for (let i = 0; i < l; i++) { + const v = glyphVertices[i]; + + origins.push.apply(origins, v.origin); + tangents.push.apply(tangents, v.tangent); + ups.push.apply(ups, v.up); + texCoords.push.apply(texCoords, v.uvRect); + } + + this._3DLabelGeometry.setTexCoords(Float32Array.from(texCoords)); + this._3DLabelGeometry.setGlyphCoords( + Float32Array.from(origins), Float32Array.from(tangents), Float32Array.from(ups)); + } +} diff --git a/source/ndcfillingrectangle.ts b/source/ndcfillingrectangle.ts index f66fc0ac..a89709cc 100644 --- a/source/ndcfillingrectangle.ts +++ b/source/ndcfillingrectangle.ts @@ -48,7 +48,7 @@ export class NdcFillingRectangle extends Geometry { /* Generate identifier from constructor name if none given. */ identifier = identifier !== undefined && identifier !== `` ? identifier : this.constructor.name; - const vertexVBO = new Buffer(context, identifier + 'VBO'); + const vertexVBO = new Buffer(context, `${identifier}VBO`); this._buffers.push(vertexVBO); } diff --git a/source/ndcfillingtriangle.ts b/source/ndcfillingtriangle.ts index cc43d41c..c7aafa10 100644 --- a/source/ndcfillingtriangle.ts +++ b/source/ndcfillingtriangle.ts @@ -55,7 +55,7 @@ export class NdcFillingTriangle extends Geometry { /* Generate identifier from constructor name if none given. */ identifier = identifier !== undefined && identifier !== `` ? identifier : this.constructor.name; - const vertexVBO = new Buffer(context, identifier + 'VBO'); + const vertexVBO = new Buffer(context, `${identifier}VBO`); this._buffers.push(vertexVBO); } diff --git a/source/position2dlabel.ts b/source/position2dlabel.ts new file mode 100644 index 00000000..24b59bd6 --- /dev/null +++ b/source/position2dlabel.ts @@ -0,0 +1,143 @@ + +import { mat4, vec2, vec3, vec4 } from 'gl-matrix'; + +import { log, LogLevel } from './auxiliaries'; +import { FontFace } from './fontface'; +import { GlyphVertices } from './glyphvertices'; +import { Label } from './label'; +import { Text } from './text'; +import { Typesetter } from './typesetter'; + + +/** + * A Label that can be positioned in 2D space. The unit for positions, size and transformations, is pixel (px). + */ +export class Position2DLabel extends Label { + + protected _position: vec2; + protected _direction: vec2; + + /** + * Constructs a pre-configured 2D-label with given text + * @param text - Valid context to create the object for. + * @param identifier - Meaningful name for identification of this instances VAO and VBOs. + */ + constructor(text: Text, fontFace: FontFace) { + super(text, fontFace); + this._position = vec2.fromValues(0.0, 0.0); + this._direction = vec2.fromValues(1.0, 0.0); + + this._fontSizeUnit = Label.SpaceUnit.Px; + } + + /** + * Applies its position and direction, then prepares the vertex storage so that the Typesetter can typeset this + * label. + * @param frameSize The width and height of the frame, so that sizes can be calculated to use pixel units. + * @returns The transformed glyph vertices. + */ + typeset(frameSize: [number, number]): GlyphVertices { + /** @todo assert: this.fontSizeUnit === Label.SpaceUnit.Px or, later, === Label.SpaceUnit.Pt */ + + /** @todo meaningful margins from label.margins or config.margins ? */ + const margins: vec4 = vec4.create(); + /** @todo meaningful ppiScale from label.ppiScale or config.ppiScale ? */ + const ppiScale = 1; + + /* compute transform matrix */ + const transform = mat4.create(); + + /* translate to lower left in NDC */ + mat4.translate(transform, transform, vec3.fromValues(-1.0, -1.0, 0.0)); + /* scale glyphs to NDC size, this._frameSize should be the viewport size */ + mat4.scale(transform, transform, vec3.fromValues(2.0 / frameSize[0], 2.0 / frameSize[1], 1.0)); + + /* scale glyphs to pixel size with respect to the displays ppi */ + mat4.scale(transform, transform, vec3.fromValues(ppiScale, ppiScale, ppiScale)); + + /* translate to origin in point space - scale origin within margined extend + * (i.e., viewport with margined areas removed) + */ + const marginedExtent: vec2 = vec2.create(); + vec2.sub(marginedExtent, vec2.fromValues( + frameSize[0] / ppiScale, frameSize[1] / ppiScale), + vec2.fromValues(margins[3] + margins[1], margins[2] + margins[0])); + + const v3 = vec3.fromValues(0.5 * marginedExtent[0], 0.5 * marginedExtent[1], 0); + vec3.add(v3, v3, vec3.fromValues(margins[3], margins[2], 0.0)); + mat4.translate(transform, transform, v3); + + + /* apply user tranformations (position, direction) */ + mat4.translate(transform, transform, vec3.fromValues(this._position[0], this._position[1], 0)); + + const n: vec2 = vec2.fromValues(1.0, 0.0); + let angle = vec2.angle(n, this._direction); + + /* perp dot product for signed angle */ + if (n[0] * this._direction[1] - n[1] * this._direction[0] < 0.0) { + angle = -angle; + } + + mat4.rotateZ(transform, transform, angle); + + this.transform = transform; + + const vertices = this.prepareVertexStorage(); + Typesetter.typeset(this, vertices, 0); + + return vertices; + } + + /** + * Sets the 2D position of the label's reference point (i.e. lower left corner for horizontal alignment). + */ + set position(xy: vec2) { + this._position = vec2.clone(xy); + } + get position(): vec2 { + return this._position; + } + + /** + * Sets 2D position parameters as specified in OpenLL. Position is the label's reference point (i.e. lower left + * corner for horizontal alignment). + * @param x x coordinate of the 2D position + * @param y y coordinate of the 2D position + * @param unit the unit to interpret the coordinates + */ + setPosition(x: number, y: number, unit?: Label.SpaceUnit): void { + /** @todo assert that SpaceUnit is px or pt; transform to NDC? */ + this._position = vec2.fromValues(x, y); + } + + /** + * Sets the 2D direction of the label, i.e., the direction of the baseline. + */ + set direction(xy: vec2) { + vec2.normalize(this._direction, xy); + } + get direction(): vec2 { + return this._direction; + } + + /** + * Sets the 2D direction parameters as specified in OpenLL. The labels's direction is the direction of its baseline. + * @param x x coordinate of the 2D direction vector. + * @param y y coordinate of the 2D direction vector. + */ + setDirection(x: number, y: number): void { + this.direction = vec2.fromValues(x, y); + } + + /** + * This unit is used for the font size. This method overrides the super.fontSizeUnit, since a position2dlabel only + * allows px, not World. + * (@see {@link fontSize}) + * @param newUnit unused, since there is only one allowed unit (Px) for this kind of label + */ + set fontSizeUnit(newUnit: Label.SpaceUnit) { + log(LogLevel.Warning, `New SpaceUnit ${newUnit} not set; only allowed SpaceUnit is Px for this label.`); + } +} + diff --git a/source/position3dlabel.ts b/source/position3dlabel.ts new file mode 100644 index 00000000..e2ecfcfe --- /dev/null +++ b/source/position3dlabel.ts @@ -0,0 +1,139 @@ + +import { mat4, vec3 } from 'gl-matrix'; + +import { log, LogLevel } from './auxiliaries'; +import { FontFace } from './fontface'; +import { GlyphVertices } from './glyphvertices'; +import { Label } from './label'; +import { Text } from './text'; +import { Typesetter } from './typesetter'; + + +/** + * A Label that can be positioned in 3D space. The unit for positions, size and transformations, is the abstract World + * Unit. + */ +export class Position3DLabel extends Label { + + protected _position: vec3; + protected _direction: vec3; + protected _up: vec3; + + /** + * Constructs a pre-configured 3D-label with given text. + * @param text - Valid context to create the object for. + * @param identifier - Meaningful name for identification of this instances VAO and VBOs. + */ + constructor(text: Text, fontFace: FontFace) { + super(text, fontFace); + this._position = vec3.fromValues(0.0, 0.0, 0.0); + this._direction = vec3.fromValues(1.0, 0.0, 0.0); + this._up = vec3.fromValues(0.0, 1.0, 0.0); + + this._fontSizeUnit = Label.SpaceUnit.World; + } + + /** + * Applies its position, direction and up-vector, then prepares the vertex storage so that the Typesetter can + * typeset this label. + * @returns The transformed glyph vertices. + */ + typeset(): GlyphVertices { + /** @todo assert: this.fontSizeUnit === Label.SpaceUnit.World */ + + const transform = mat4.create(); + const normal = vec3.create(); + + /* apply user tranformations (position, direction) */ + + mat4.translate(transform, mat4.create(), + vec3.fromValues(this._position[0], this._position[1], this._position[2])); + + vec3.cross(normal, this._direction, this._up); + + const rotation = mat4.fromValues(this.direction[0], this.direction[1], this.direction[2], 0, + this.up[0], this.up[1], this.up[2], 0, + normal[0], normal[1], normal[2], 0, + 0.0, 0.0, 0.0, 1.0); + + this.transform = mat4.mul(this.transform, transform, rotation); + + const vertices = this.prepareVertexStorage(); + Typesetter.typeset(this, vertices, 0); + + return vertices; + } + + /** + * Sets the 3D position of the label's reference point (i.e. lower left corner for horizontal alignment). + */ + set position(xyz: vec3) { + this._position = vec3.clone(xyz); + } + get position(): vec3 { + return this._position; + } + + /** + * Sets 3D position parameters as specified in OpenLL. Position is the label's reference point (i.e. lower left + * corner for horizontal alignment). + * @param x x coordinate of 3D position + * @param y y coordinate of 3D position + * @param z z coordinate of 3D position + */ + setPosition(x: number, y: number, z: number): void { + this._position = vec3.fromValues(x, y, z); + } + + /** + * Sets the 3D direction of the label, i.e., the direction of the baseline. + */ + set direction(xyz: vec3) { + vec3.normalize(this._direction, xyz); + } + get direction(): vec3 { + return this._direction; + } + + /** + * Sets the 3D direction parameters as specified in OpenLL. The labels's direction is the direction of its baseline. + * @param x x coordinate of the 3D direction vector. + * @param y y coordinate of the 3D direction vector. + * @param z z coordinate of the 3D direction vector. + */ + setDirection(x: number, y: number, z: number): void { + this.direction = vec3.fromValues(x, y, z); + } + + /** + * Sets the up-vector of the label. It should be orthogonal to the direction to ensure that the label is not skewed. + */ + set up(xyz: vec3) { + this._up = vec3.normalize(this._up, xyz); + } + get up(): vec3 { + return this._up; + } + + /** + * Sets the 3D up-vector parameters as specified in OpenLL. It should be orthogonal to the direction to ensure that + * the label is not skewed. + * @param x x coordinate of the 3D up vector. + * @param y y coordinate of the 3D up vector. + * @param z z coordinate of the 3D up vector. + */ + setUp(x: number, y: number, z: number): void { + this.up = vec3.fromValues(x, y, z); + } + + /** + * This unit is used for the font size. This method overrides the super.fontSizeUnit, since a position3dlabel only + * allows World, not Px nor Pt. + * (@see {@link fontSize}) + * @param newUnit unused, since there is only one allowed unit (World) for this kind of label + */ + set fontSizeUnit(newUnit: Label.SpaceUnit) { + log(LogLevel.Warning, `New SpaceUnit ${newUnit} not set; only allowed SpaceUnit is World for this label.`); + } +} + diff --git a/source/shaders/glyphquad.frag b/source/shaders/glyphquad.frag new file mode 100644 index 00000000..b5c6008d --- /dev/null +++ b/source/shaders/glyphquad.frag @@ -0,0 +1,40 @@ +precision mediump float; + +@import ./facade.frag; + +#if __VERSION__ == 100 + #define fragColor gl_FragColor + #extension GL_OES_standard_derivatives : enable +#else + layout(location = 0) out vec4 fragColor; +#endif + + +uniform sampler2D u_glyphs; + +varying vec2 v_texture_coord; + +void main(void) +{ + /* requires blend: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */ + + float d = texture(u_glyphs, v_texture_coord).r; + + /** + * Don't discard fragments, as we might need them for an id-buffer for clicking-interaction. + * Furthermore, using if-statement and discard can slow down performance: + * it's bad for IMR, TBR, TBDR and early-Z optimization + * https://stackoverflow.com/questions/8509051/is-discard-bad-for-program-performance-in-opengl + * + */ + // if(d < 0.45) + // discard; + + /** black. @todo font color as vertex attrib or uniform */ + vec4 fc = vec4(0.0, 0.0, 0.0, 1.0); + + /** @todo mipmap access? */ + /* simplest aastep; when using multiframe sampling, smoothstep is not necessary and will add too much blur */ + float a = step(0.5, d); + fragColor = vec4(fc.rgb, fc.a * a); +} diff --git a/source/shaders/glyphquad.vert b/source/shaders/glyphquad.vert new file mode 100644 index 00000000..bacce6cf --- /dev/null +++ b/source/shaders/glyphquad.vert @@ -0,0 +1,55 @@ +precision mediump float; +precision lowp int; + +@import ./facade.vert; + +#if __VERSION__ == 100 + #extension GL_EXT_draw_buffers : enable + attribute vec2 a_quadVertex; + /* [ texture ll: vec2, ur: vec2 ] */ + attribute vec4 a_texCoord; + attribute vec3 a_origin; + attribute vec3 a_tangent; + attribute vec3 a_up; +#else + layout(location = 0) in vec2 a_quadVertex; + /* [ texture ll: vec2, ur: vec2 ]*/ + layout(location = 1) in vec4 a_texCoord; + layout(location = 2) in vec3 a_origin; + layout(location = 3) in vec3 a_tangent; + layout(location = 4) in vec3 a_up; +#endif + +uniform mat4 u_viewProjection; +uniform vec2 u_ndcOffset; + +varying vec2 v_texture_coord; + +@import ./ndcoffset; + +void main(void) +{ + /* TEXTURE COORDS */ + + /* flip y-coordinates */ + vec2 texExt = vec2(a_texCoord[2] - a_texCoord[0], a_texCoord[1] - a_texCoord[3]); + + v_texture_coord = a_quadVertex * texExt + vec2(a_texCoord[0], 1.0 - a_texCoord[1]); + + /* POSITIONING*/ + /* quad data as flat array: [0, 0, 0, 1, 1, 0, 1, 1] (a_quadVertex), which translates to ll, lr, ul, ur corners. + * 2-------4 + * | \ | + * | \ | + * 1-------3 + * The current vertex is calculated based on the current quad corners and the tangent / up attributes. + * The following lines are optimized for MAD optimization. + */ + vec3 tangentDirection = a_origin + a_quadVertex.x * a_tangent; + vec4 vertex = vec4(tangentDirection + a_quadVertex.y * a_up, 1.0); + + vertex = u_viewProjection * vertex; + + ndcOffset(vertex, u_ndcOffset); + gl_Position = vertex; +} diff --git a/source/text.ts b/source/text.ts new file mode 100644 index 00000000..3429efbe --- /dev/null +++ b/source/text.ts @@ -0,0 +1,83 @@ + +/** + * The text object is intended as character sequence manipulation interface. A text can be referenced by multiple labels + * for rendering and interaction. E.g., a single text could be rendered multiple times at different locations or using + * different font faces, alignments, etc. The text object will probably increase in complexity when additional features + * such as text formatting (bold, italic, varying size), (multi)cursor, (multi)selection, etc. will be added. + */ +export class Text { + + static readonly DEFAULT_LINEFEED = '\x0A'; + + + /** @see {@link text} */ + protected _text: string; + + /** @see {@link lineFeed} */ + protected _lineFeed: string = Text.DEFAULT_LINEFEED; + + /** @see {@link altered} */ + protected _altered = false; + + /** + * Constructs a Text to be used for a Label. + * @param str - the actual content of this Text. + * @param lineFeed - char for lineFeed, default is LF. + */ + constructor(str: string, lineFeed?: string) { + this._text = str; + + this._lineFeed = lineFeed !== undefined ? lineFeed : this._lineFeed; + } + + /** + * Length of the text, i.e., number of characters within the text. + */ + get length(): number { + return this._text.length; + } + + /** + * Text that is to be rendered. + */ + set text(text: string) { + if (this._text === text) { + return; + } + this._altered = true; + this._text = text; + } + get text(): string { + return this._text; + } + + /** + * Character that is to be used for Line feed. + */ + set lineFeed(lineFeed: string) { + if (this._lineFeed === lineFeed) { + return; + } + this._altered = true; + this._lineFeed = lineFeed; + return; + } + get lineFeed(): string { + return this._lineFeed; + } + + /** + * Intended for resetting alteration status. + */ + set altered(altered: boolean) { + this._altered = altered; + } + + /* + * Whether or not any other public property has changed. + */ + get altered(): boolean { + return this._altered; + } + +} diff --git a/source/typesetter.ts b/source/typesetter.ts new file mode 100644 index 00000000..947b34b1 --- /dev/null +++ b/source/typesetter.ts @@ -0,0 +1,305 @@ + +import { mat4, vec2, vec3, vec4 } from 'gl-matrix'; +import { fromVec4, v4 } from './gl-matrix-extensions'; + +import { assert } from './auxiliaries'; +import { FontFace } from './fontface'; +import { Glyph } from './glyph'; +import { GlyphVertex, GlyphVertices } from './glyphvertices'; +import { Label } from './label'; +import { GLfloat2 } from './tuples'; + + +/** + * The typesetter is responsible for layouting text on the screen or in a virtual space. It takes a label, + * which defines where it wants to appear (@see {@link Label}), and a font face that is used to display the + * text, and computes the actual position for each glyph. Its output is a vertex array, which describes the glyphs + * position and appearance on the screen/in the scene and which can be rendered using a LabelRenderer. + */ +export class Typesetter { + + protected static readonly DELIMITERS: string = '\x0A ,.-/()[]<>'; + + /** + * Returns if newline should be applied for next word (or next glyph if word exceeds the line width) + * @param label the label that has wordWrap enabled + * @param pen horizontal and vertical position at which typesetting takes place/arrived. + * @param glyph current glyph + * @param index current index for char in this label + * @param safeForwardIndex used to reduce the number of wordwrap forward passes + * @returns whether or not typesetting should go on a new line + */ + protected static wordWrap(label: Label, pen: vec2, glyph: Glyph, index: number, safeForwardIndex: number): boolean { + assert(label.wordWrap, `expected wordWrap to be enabled for label, given ${label}`); + const lineWidth = label.lineWidth; + + const penForward = pen[0] + glyph.advance + (index > 0 ? label.kerningBefore(index) : 0.0); + if (glyph.depictable() && penForward > lineWidth && (glyph.advance <= lineWidth || pen[0] > 0.0)) { + return true; + } + if (index < safeForwardIndex) { + return false; + } + /* tslint:disable-next-line:prefer-const */ + let forwardWidth = 0.0; + safeForwardIndex = Typesetter.forward(label, index, forwardWidth); + return forwardWidth <= lineWidth && (pen[0] + forwardWidth) > lineWidth; + } + + /** + * Accumulate glyph advances (including kerning) up to the next delimiter or line feed occurrence starting at + * the given index for the given label. + * @param label - Label to compute accumulated advances for. + * @param begin - Index to start glyph advance accumulation at. + * @param width - Out parameter: the accumulated width up to the next delimiter (reset to 0). + * @returns - The index of the last character that was included in the forward accumulation. + * @todo Perhaps switch to another approach, i.e., calculate the all advances between delimiters once and create + * an index based lookup... + */ + protected static forward(label: Label, begin: number, width: number): number { + let index: number = begin; + const iEnd: number = label.length; + + width = 0.0; + while (index < iEnd && Typesetter.DELIMITERS.indexOf(label.charAt(index)) === -1) { + if (index > 0) { + width += label.kerningBefore(index); + } + width += label.advance(index); + ++index; + } + return index; + } + + /** + * Revert advance of preceding, not depictable glyphs. Intended to be used, e.g., on line feed. + * The given vertical extent is increased by the label's line height and the horizontal extent is set to either + * 0 or the extent of the non-depictable characters. + * @param label - Label to compute accumulated advances for. + * @param index - In/out parameter: index to backtrack the advance of non-depictable glyphs. + * @param begin - Index to start glyph advance accumulation at. + * @param pen - In/out parameter: pen (typesetting position) to be used and adjusted. + * @param extent - In/out parameter: extent to be adjusted. + */ + protected static backward(label: Label, index: number, begin: number, pen: vec2, extent: vec2): void { + while (index > begin) { + const precedingGlyph = label.fontFace.glyph(label.charCodeAt(index)); + if (precedingGlyph.depictable()) { + break; + } + pen[0] -= precedingGlyph.advance; + --index; + } + extent[0] = Math.max(pen[0], extent[1]); + extent[1] += label.fontFace.lineHeight; + } + + /** + * Adjusts the vertices for a line after typesetting (done due to line feed, word wrap, or end of line) w.r.t. + * the targeted line alignment. + * @param pen - Current typesetting position (probably the end of the line in typesetting space). + * @param alignment - Targeted alignment, e.g., left, center, or right. + * @param vertices - Glyph vertices for rendering to align the origins' x-components of (expected untransformed). + * @param begin - Vertex index to start alignment at. + * @param end - Vertex index to stop alignment at. + */ + protected static transformAlignment(pen: vec2, alignment: Label.Alignment, + vertices: GlyphVertices | undefined, begin: number, end: number): void { + if (vertices === undefined || alignment === Label.Alignment.Left) { + return; + } + + let penOffset = -pen[0]; + if (alignment === Label.Alignment.Center) { + penOffset *= 0.5; + } + + /* Origin is expected to be in typesetting space (not transformed yet). */ + for (let i = begin; i < end; ++i) { + vertices[i].origin[0] += penOffset; + } + } + + /** + * Adjusts the vertices for line anchor (done due after typesetting) w.r.t. the targeted anchoring. + * @param label - Label to adjust the y-positions for. + * @param vertices - Glyph vertices for rendering to align the origins' y-components of (expected untransformed). + * @param begin - Vertex index to start alignment at. + * @param end - Vertex index to stop alignment at. + * + * @todo Apply once at the beginning! Initial offset! + */ + protected static transformLineAnchor(label: Label, + vertices: GlyphVertices | undefined, begin: number, end: number): void { + if (vertices === undefined) { + return; + } + + let offset = 0.0; + switch (label.lineAnchor) { + case Label.LineAnchor.Ascent: + offset = label.fontFace.ascent; + break; + case Label.LineAnchor.Center: + offset = label.fontFace.size * 0.5 + label.fontFace.descent; + break; + case Label.LineAnchor.Descent: + offset = label.fontFace.descent; + break; + case Label.LineAnchor.Top: + offset = label.fontFace.base; + break; + case Label.LineAnchor.Bottom: + offset = label.fontFace.base - label.fontFace.lineHeight; + break; + case Label.LineAnchor.Baseline: + default: + return; + } + + for (let i = begin; i <= end; ++i) { + vertices[i].origin[1] -= offset; + } + } + + /** + * Configuring the vertex for a given glyph to be rendered. If no vertex is given or the glyph is not depictable, + * this method immediately exits at the beginning. + * @param fontFace - Font face to be applied for setting up the vertex. + * @param pen - Typesetting position which is the not-yet-transformed position the glyph will be rendered at. + * @param glyph - Glyph that is to be rendered/configured. + * @param vertex - Associated vertex to store data required for rendering. + */ + protected static transformGlyph(fontFace: FontFace, pen: vec2, glyph: Glyph, + vertex: GlyphVertex | undefined): void { + + if (vertex === undefined || glyph.depictable() === false) { + return; + } + + const padding = fontFace.glyphTexturePadding; + vertex.origin = vec3.fromValues(pen[0], pen[1], 0.0); + vertex.origin[0] += glyph.bearing[0] - padding[3]; + vertex.origin[1] += glyph.bearing[1] - glyph.extent[1] + padding[0]; + + vertex.tangent = vec3.fromValues(glyph.extent[0], 0.0, 0.0); + vertex.up = vec3.fromValues(0.0, glyph.extent[1], 0.0); + + vertex.uvRect[0] = glyph.subTextureOrigin[0]; + vertex.uvRect[1] = glyph.subTextureOrigin[1]; + + const upperRight = vec2.create(); + vec2.add(upperRight, glyph.subTextureOrigin, glyph.subTextureExtent); + + vertex.uvRect[2] = upperRight[0]; + vertex.uvRect[3] = upperRight[1]; + } + + + /** + * Computes origin, tangent, and up vector for every vertex of in the given range. + * @param transform - Transformation to apply to every vertex. + * @param vertices - Glyph vertices to be transformed (expected untransformed, in typesetting space). + * @param begin - Vertex index to start alignment at. + * @param end - Vertex index to stop alignment at. + */ + protected static transformVertex(transform: mat4, + vertices: GlyphVertices | undefined, begin: number, end: number): void { + if (vertices === undefined || mat4.equals(transform, mat4.create())) { + return; + } + + for (let i: number = begin; i < end; ++i) { + const v = vertices[i]; + + const lowerLeft: vec4 = vec4.transformMat4(v4(), vec4.fromValues( + v.origin[0], v.origin[1], v.origin[2], 1.0), transform); + const lowerRight: vec4 = vec4.transformMat4(v4(), vec4.fromValues( + v.origin[0] + v.tangent[0], v.origin[1] + v.tangent[1], v.origin[2] + v.tangent[2], 1.0), transform); + const upperLeft: vec4 = vec4.transformMat4(v4(), vec4.fromValues( + v.origin[0] + v.up[0], v.origin[1] + v.up[1], v.origin[2] + v.up[2], 1.0), transform); + + v.origin = fromVec4(lowerLeft); + v.tangent = fromVec4(vec4.sub(v4(), lowerRight, lowerLeft)); + v.up = fromVec4(vec4.sub(v4(), upperLeft, lowerLeft)); + } + } + + + /** + * Transforms the labels extent (typesetting space to, e.g., world space or screen space). + * @param transform - Transformation that was applied to every vertex. + * @param extent - Untransformed label extent (in typesetting space) to be transformed. + * @returns - Transformed label extent (copy). + */ + protected static transformExtent(transform: mat4, extent: vec2): GLfloat2 { + const lowerLeft = vec4.transformMat4(v4(), vec4.fromValues(0.0, 0.0, 0.0, 1.0), transform); + const lowerRight = vec4.transformMat4(v4(), vec4.fromValues(extent[0], 0.0, 0.0, 1.0), transform); + const upperLeft = vec4.transformMat4(v4(), vec4.fromValues(0.0, extent[1], 0.0, 1.0), transform); + return [vec4.distance(lowerRight, lowerLeft), vec4.distance(upperLeft, lowerLeft)]; + } + + + /** + * Typesets the given label, transforming the vertices in-world, ready to be rendered. + * @param label the label that shall be typeset + * @param vertices the glyphvertices, a prepared (optionally empty) vertex storage + * @param begin vertex index to start the typesetting (usually 0) + */ + static typeset(label: Label, vertices?: GlyphVertices, begin?: number): GLfloat2 { + /* Horizontal and vertical position at which typesetting takes place/arrived. */ + const pen = vec2.create(); + + let vertexIndex = begin !== undefined ? begin : 0; + const extent = vec2.create(); + + const iBegin = 0; + const iEnd: number = label.length; + + /* Index used to reduce the number of wordwrap forward passes. */ + /* tslint:disable-next-line:prefer-const */ + let safeForwardIndex = iBegin; + let feedVertexIndex: number = vertexIndex; + + let index = iBegin; + for (; index !== iEnd; ++index) { + const glyph = label.fontFace.glyph(label.charCodeAt(index)); + + /* Handle line feeds as well as word wrap for next word (or next glyph if word exceeds the line width). */ + const feedLine = label.lineFeedAt(index) || (label.wordWrap && + Typesetter.wordWrap(label, pen, glyph, index, safeForwardIndex)); + + if (feedLine) { + /* Handle pen and extent w.r.t. non-depictable glyphs. */ + Typesetter.backward(label, index - 1, iBegin, pen, extent); + /* Handle alignment (does nothing if vertices are not required/undefined). */ + Typesetter.transformAlignment(pen, label.alignment, vertices, feedVertexIndex, vertexIndex); + + pen[0] = 0.0; + pen[1] -= label.fontFace.lineHeight; + + feedVertexIndex = vertexIndex; + + } else if (index > iBegin) { + pen[0] += label.kerningBefore(index); + } + + /* Add and configure data for rendering the current character/glyph of the label. */ + Typesetter.transformGlyph(label.fontFace, pen, glyph, vertices ? vertices[vertexIndex++] : undefined); + + pen[0] += glyph.advance; + } + + /* Handle alignment (when last line of sequence is processed). */ + Typesetter.backward(label, index - 1, iBegin, pen, extent); + /* Handle alignment and anchoring (does nothing if vertices are not required/undefined). */ + Typesetter.transformAlignment(pen, label.alignment, vertices, feedVertexIndex, iEnd - 1); + Typesetter.transformLineAnchor(label, vertices, iBegin, iEnd - 1); + + Typesetter.transformVertex(label.transform, vertices, iBegin, vertexIndex); + + const labelExtent = Typesetter.transformExtent(label.transform, extent); + label.extent = labelExtent; + return labelExtent; + } +} diff --git a/source/webgl-operate.slim.ts b/source/webgl-operate.slim.ts index ba9f4d3c..3ea83788 100644 --- a/source/webgl-operate.slim.ts +++ b/source/webgl-operate.slim.ts @@ -52,6 +52,7 @@ export { AccumulatePass } from './accumulatepass'; export { BlitPass } from './blitpass'; export { ReadbackPass } from './readbackpass'; +export { LabelRenderer } from './labelrenderer'; import * as root_auxiliaries from './auxiliaries'; export import auxiliaries = root_auxiliaries; diff --git a/source/webgl-operate.ts b/source/webgl-operate.ts index ce046b7b..67253d57 100644 --- a/source/webgl-operate.ts +++ b/source/webgl-operate.ts @@ -13,6 +13,15 @@ export import fetch = root_fetch; import * as root_raymath from './raymath'; export import ray_math = root_raymath; +export { FontFace } from './fontface'; +export { FontLoader } from './fontloader'; +export { GlyphVertex, GlyphVertices } from './glyphvertices'; +export { Label } from './label'; +export { Position2DLabel } from './position2dlabel'; +export { Position3DLabel } from './position3dlabel'; +export { LabelGeometry } from './labelgeometry'; +export { Text } from './text'; +export { Typesetter } from './typesetter'; // /* DEBUG facilities */ diff --git a/website/js/website.js b/website/js/website.js index db713056..124ffc1e 100644 --- a/website/js/website.js +++ b/website/js/website.js @@ -14,7 +14,7 @@ window.onload = function () { aboutCode.innerText = context.aboutString(); canvas.controller.multiFrameNumber = 1024; - canvas.frameScale = [0.2, 0.2]; - renderer = new gloperate.debug.TestRenderer(); + canvas.frameScale = [1.0, 1.0]; + renderer = new gloperate.LabelRenderer(); canvas.renderer = renderer; };