🎉 Add multiple fonts to text editor WASM playground

This commit is contained in:
Aitor Moreno
2025-08-26 10:46:20 +02:00
parent 4053e8c8db
commit b215689566
34 changed files with 1604 additions and 958 deletions

View File

@@ -0,0 +1,958 @@
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTjWacfw6zH4dthXcyms1lPpC8I_b0juU057p-xEJZj11l4.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTjWacfw6zH4dthXcyms1lPpC8I_b0juU057p-xEJ9j11l4.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTjWacfw6zH4dthXcyms1lPpC8I_b0juU057p-xEJRj11l4.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTjWacfw6zH4dthXcyms1lPpC8I_b0juU057p-xEJVj11l4.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTjWacfw6zH4dthXcyms1lPpC8I_b0juU057p-xEJtj1w.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8dAYxJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8dAYxA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8dAYxL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8dAYxK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8dAYxE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p95AoxJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p95AoxA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p95AoxL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p95AoxK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p95AoxE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU057pffIJl70w.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU057pfWIJl70w.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU057pfdIJl70w.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU057pfcIJl70w.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU057pfSIJk.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8hA4xJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8hA4xA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8hA4xL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8hA4xK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8hA4xE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8NBIxJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8NBIxA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8NBIxL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8NBIxK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p8NBIxE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9pBYxJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9pBYxA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9pBYxL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9pBYxK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9pBYxE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p91BoxJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p91BoxA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p91BoxL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p91BoxK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p91BoxE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9RB4xJ8mRBkw.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9RB4xA8mRBkw.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9RB4xL8mRBkw.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9RB4xK8mRBkw.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTkWacfw6zH4dthXcyms1lPpC8I_b0juU057p9RB4xE8mQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU0xiJffIJl70w.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU0xiJfWIJl70w.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU0xiJfdIJl70w.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU0xiJfcIJl70w.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFThWacfw6zH4dthXcyms1lPpC8I_b0juU0xiJfSIJk.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xJIbFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xJIbFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xJIbFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xJIbFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xJIbFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xQIXFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xQIXFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xQIXFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xQIXFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xQIXFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTvWacfw6zH4dthXcyms1lPpC8I_b0juU055qfQOJ0.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTvWacfw6zH4dthXcyms1lPpC8I_b0juU0576fQOJ0.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTvWacfw6zH4dthXcyms1lPpC8I_b0juU055KfQOJ0.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTvWacfw6zH4dthXcyms1lPpC8I_b0juU055afQOJ0.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTvWacfw6zH4dthXcyms1lPpC8I_b0juU0566fQ.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xGITFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xGITFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xGITFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xGITFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xGITFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xNIPFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xNIPFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xNIPFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xNIPFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xNIPFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xUILFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xUILFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xUILFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xUILFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xUILFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xTIHFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xTIHFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xTIHFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xTIHFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xTIHFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xaIDFCrxG6mA.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xaIDFA7xG6mA.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xaIDFCLxG6mA.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xaIDFCbxG6mA.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'MontserratAlternates';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url(https://fonts.gstatic.com/s/montserratalternates/v17/mFTiWacfw6zH4dthXcyms1lPpC8I_b0juU0xaIDFB7xG.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Oswald';
font-style: normal;
font-weight: 200 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/oswald/v56/TK3iWkUHHAIjg752FD8Ghe4.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Oswald';
font-style: normal;
font-weight: 200 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/oswald/v56/TK3iWkUHHAIjg752HT8Ghe4.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* vietnamese */
@font-face {
font-family: 'Oswald';
font-style: normal;
font-weight: 200 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/oswald/v56/TK3iWkUHHAIjg752Fj8Ghe4.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Oswald';
font-style: normal;
font-weight: 200 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/oswald/v56/TK3iWkUHHAIjg752Fz8Ghe4.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Oswald';
font-style: normal;
font-weight: 200 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/oswald/v56/TK3iWkUHHAIjg752GT8G.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* fallback */
@font-face {
font-family: 'Playwrite MX Guides';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/playwritemxguides/v1/k3kMo9ESPe9dzQ1UGbvoZhnhbtfklWqN0qk.woff2) format('woff2');
}

View File

@@ -2,225 +2,551 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Playwrite+ES:wght@100..400&family=Playwrite+NZ:wght@100..400&family=Playwrite+US+Trad:wght@100..400&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="/javascript.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Penpot - Text Editor Playground</title>
<style>
#output {
white-space: pre-wrap;
}
</style>
</head>
<body>
<form>
<fieldset>
<legend>Styles</legend>
<!-- Font -->
<div class="form-group">
<label for="font-family">Font family</label>
<select id="font-family">
<option value="Open+Sans">Open Sans</option>
<option value="sourcesanspro">Source Sans Pro</option>
<option value="whatever">Whatever</option>
</select>
</div>
<div class="form-group">
<label for="font-size">Font size</label>
<input id="font-size" type="number" value="14" />
</div>
<div class="form-group">
<label for="font-weight">Font weight</label>
<select id="font-weight">
<option value="100">100</option>
<option value="200">200</option>
<option value="300">300</option>
<option value="400">400 (normal)</option>
<option value="500">500</option>
<option value="600">600</option>
<option value="700">700 (bold)</option>
<option value="800">800</option>
<option value="900">900</option>
</select>
</div>
<div class="form-group">
<label for="font-style">Font style</label>
<select id="font-style">
<option value="normal">normal</option>
<option value="italic">italic</option>
<option value="oblique">oblique</option>
</select>
</div>
<!-- Text attributes -->
<div class="form-group">
<label for="line-height">Line height</label>
<input id="line-height" type="number" value="1.0" />
</div>
<div class="form-group">
<label for="letter-spacing">Letter spacing</label>
<input id="letter-spacing" type="number" value="0.0" />
</div>
<div class="form-group">
<label for="direction-ltr">LTR</label>
<input id="direction-ltr" type="radio" name="direction" value="ltr" checked />
</div>
<div class="form-group">
<label for="direction-rtl">RTL</label>
<input id="direction-rtl" type="radio" name="direction" value="rtl" />
</div>
<!-- Text Align -->
<div class="form-group">
<label for="text-align-left">Align left</label>
<input id="text-align-left" type="radio" name="text-align" value="left" checked />
</div>
<div class="form-group">
<label for="text-align-center">Align center</label>
<input id="text-align-center" type="radio" name="text-align" value="center" />
</div>
<div class="form-group">
<label for="text-align-right">Align right</label>
<input id="text-align-right" type="radio" name="text-align" value="right" />
</div>
<div class="form-group">
<label for="text-align-justify">Align justify</label>
<input id="text-align-justify" type="radio" name="text-align" value="justify" />
</div>
<!-- Text Transform -->
<div class="form-group">
<label for="text-transform-none">None</label>
<input id="text-transform-none" type="radio" name="text-transform" value="none" checked />
</div>
<div class="form-group">
<label for="text-transform-uppercase">Uppercase</label>
<input id="text-transform-uppercase" type="radio" name="text-transform" value="uppercase" checked />
</div>
<div class="form-group">
<label for="text-transform-capitalize">Capitalize</label>
<input id="text-transform-capitalize" type="radio" name="text-transform" value="capitalize" />
</div>
<div class="form-group">
<label for="text-transform-lowercase">Lowercase</label>
<input id="text-transform-lowercase" type="radio" name="text-transform" value="lowercase" />
</div>
</fieldset>
<fieldset>
<legend>Debug</legend>
<div class="form-group">
<label for="direction">Direction</label>
<input id="direction" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-node">Focus Node</label>
<input id="focus-node" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-offset">Focus offset</label>
<input id="focus-offset" readonly type="number">
</div>
<div class="form-group">
<label for="focus-inline">Focus Inline</label>
<input id="focus-inline" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-paragraph">Focus Paragraph</label>
<input id="focus-paragraph" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-node">Anchor Node</label>
<input id="anchor-node" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-offset">Anchor offset</label>
<input id="anchor-offset" readonly type="number">
</div>
<div class="form-group">
<label for="anchor-inline">Anchor Inline</label>
<input id="anchor-inline" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-paragraph">Anchor Paragraph</label>
<input id="anchor-paragraph" readonly type="text" />
</div>
<div class="form-group">
<label for="start-container">Start container</label>
<input id="start-container" readonly type="text" />
</div>
<div class="form-group">
<label for="start-offset">Start offset</label>
<input id="start-offset" readonly type="text" />
</div>
<div class="form-group">
<label for="end-container">End container</label>
<input id="end-container" readonly type="text" />
</div>
<div class="form-group">
<label for="end-offset">End offset</label>
<input id="end-offset" readonly type="text" />
</div>
<div class="form-group">
<label for="multi">Multi?</label>
<input id="multi" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="multi-inline">Multi inline?</label>
<input id="multi-inline" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="multi-paragraph">Multi paragraph?</label>
<input id="multi-paragraph" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-text-focus">Is text focus?</label>
<input id="is-text-focus" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-text-anchor">Is text anchor?</label>
<input id="is-text-anchor" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-paragraph-start">Is paragraph start?</label>
<input id="is-paragraph-start" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-paragraph-end">Is paragraph end?</label>
<input id="is-paragraph-end" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-inline-start">Is inline start?</label>
<input id="is-inline-start" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-inline-end">Is inline end?</label>
<input id="is-inline-end" readonly type="checkbox">
</div>
</fieldset>
</form>
<!--
<div class="playground">
<form>
<fieldset>
<legend>Styles</legend>
<!-- Font -->
<div class="form-group">
<label for="font-family">Font family</label>
<select id="font-family">
<option value="Open+Sans">Open Sans</option>
<option value="sourcesanspro">Source Sans Pro</option>
<option value="whatever">Whatever</option>
</select>
</div>
<div class="form-group">
<label for="font-size">Font size</label>
<input id="font-size" type="number" value="14" />
</div>
<div class="form-group">
<label for="font-weight">Font weight</label>
<select id="font-weight">
<option value="100">100</option>
<option value="200">200</option>
<option value="300">300</option>
<option value="400">400 (normal)</option>
<option value="500">500</option>
<option value="600">600</option>
<option value="700">700 (bold)</option>
<option value="800">800</option>
<option value="900">900</option>
</select>
</div>
<div class="form-group">
<label for="font-style">Font style</label>
<select id="font-style">
<option value="normal">normal</option>
<option value="italic">italic</option>
<option value="oblique">oblique</option>
</select>
</div>
<!-- Text attributes -->
<div class="form-group">
<label for="line-height">Line height</label>
<input id="line-height" type="number" value="1.0" />
</div>
<div class="form-group">
<label for="letter-spacing">Letter spacing</label>
<input id="letter-spacing" type="number" value="0.0" />
</div>
<div class="form-group">
<label for="direction-ltr">LTR</label>
<input id="direction-ltr" type="radio" name="direction" value="ltr" checked />
</div>
<div class="form-group">
<label for="direction-rtl">RTL</label>
<input id="direction-rtl" type="radio" name="direction" value="rtl" />
</div>
<!-- Text Align -->
<div class="form-group">
<label for="text-align-left">Align left</label>
<input id="text-align-left" type="radio" name="text-align" value="left" checked />
</div>
<div class="form-group">
<label for="text-align-center">Align center</label>
<input id="text-align-center" type="radio" name="text-align" value="center" />
</div>
<div class="form-group">
<label for="text-align-right">Align right</label>
<input id="text-align-right" type="radio" name="text-align" value="right" />
</div>
<div class="form-group">
<label for="text-align-justify">Align justify</label>
<input id="text-align-justify" type="radio" name="text-align" value="justify" />
</div>
<!-- Text Transform -->
<div class="form-group">
<label for="text-transform-none">None</label>
<input id="text-transform-none" type="radio" name="text-transform" value="none" checked />
</div>
<div class="form-group">
<label for="text-transform-uppercase">Uppercase</label>
<input id="text-transform-uppercase" type="radio" name="text-transform" value="uppercase" checked />
</div>
<div class="form-group">
<label for="text-transform-capitalize">Capitalize</label>
<input id="text-transform-capitalize" type="radio" name="text-transform" value="capitalize" />
</div>
<div class="form-group">
<label for="text-transform-lowercase">Lowercase</label>
<input id="text-transform-lowercase" type="radio" name="text-transform" value="lowercase" />
</div>
</fieldset>
<fieldset>
<legend>Debug</legend>
<div class="form-group">
<label for="direction">Direction</label>
<input id="direction" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-node">Focus Node</label>
<input id="focus-node" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-offset">Focus offset</label>
<input id="focus-offset" readonly type="number">
</div>
<div class="form-group">
<label for="focus-inline">Focus Inline</label>
<input id="focus-inline" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-paragraph">Focus Paragraph</label>
<input id="focus-paragraph" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-node">Anchor Node</label>
<input id="anchor-node" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-offset">Anchor offset</label>
<input id="anchor-offset" readonly type="number">
</div>
<div class="form-group">
<label for="anchor-inline">Anchor Inline</label>
<input id="anchor-inline" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-paragraph">Anchor Paragraph</label>
<input id="anchor-paragraph" readonly type="text" />
</div>
<div class="form-group">
<label for="start-container">Start container</label>
<input id="start-container" readonly type="text" />
</div>
<div class="form-group">
<label for="start-offset">Start offset</label>
<input id="start-offset" readonly type="text" />
</div>
<div class="form-group">
<label for="end-container">End container</label>
<input id="end-container" readonly type="text" />
</div>
<div class="form-group">
<label for="end-offset">End offset</label>
<input id="end-offset" readonly type="text" />
</div>
<div class="form-group">
<label for="multi">Multi?</label>
<input id="multi" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="multi-inline">Multi inline?</label>
<input id="multi-inline" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="multi-paragraph">Multi paragraph?</label>
<input id="multi-paragraph" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-text-focus">Is text focus?</label>
<input id="is-text-focus" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-text-anchor">Is text anchor?</label>
<input id="is-text-anchor" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-paragraph-start">Is paragraph start?</label>
<input id="is-paragraph-start" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-paragraph-end">Is paragraph end?</label>
<input id="is-paragraph-end" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-inline-start">Is inline start?</label>
<input id="is-inline-start" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-inline-end">Is inline end?</label>
<input id="is-inline-end" readonly type="checkbox">
</div>
</fieldset>
</form>
<!--
Editor
Editor
-->
<div class="text-editor-container align-top">
<div
id="text-editor-selection-imposter"
class="text-editor-selection-imposter"></div>
<div
class="text-editor-content"
contenteditable="true"
role="textbox"
aria-multiline="true"
aria-autocomplete="none"
spellcheck="false"
autocapitalize="false"></div>
-->
<div class="text-editor-container align-top">
<div
id="text-editor-selection-imposter"
class="text-editor-selection-imposter"></div>
<div
class="text-editor-content"
contenteditable="true"
role="textbox"
aria-multiline="true"
aria-autocomplete="none"
spellcheck="false"
autocapitalize="false"></div>
</div>
<!--
Text output
-->
<canvas id="canvas"></canvas>
</div>
<!--
<script type="module">
import "./style.css";
import "./fonts.css";
import "./editor/TextEditor.css";
import { TextEditor } from "./editor/TextEditor";
import { SelectionControllerDebug } from "./editor/debug/SelectionControllerDebug";
import initWasmModule from './wasm/render_wasm.js';
import {
init, assignCanvas, render, setupInteraction, useShape, setShapeChildren, addTextShape, updateTextShape, hexToU32ARGB,getRandomInt, getRandomColor, getRandomFloat, addShapeSolidFill, addShapeSolidStrokeFill, storeFonts
} from './wasm/lib.js';
Text output
async function loadFontList() {
const response = await fetch('fonts/fonts.txt')
const text = await response.text()
const fonts = text.split('\n').filter(l => !!l)
return fonts
}
function getFontStyleWeight(fontStyleName) {
if (fontStyleName.startsWith('Thin')) {
return 100
} else if (fontStyleName.startsWith('ExtraLight')) {
return 200
} else if (fontStyleName.startsWith('Light')) {
return 300
} else if (fontStyleName.startsWith('Regular')) {
return 400
} else if (fontStyleName.startsWith('Medium')) {
return 500
} else if (fontStyleName.startsWith('SemiBold')) {
return 600
} else if (fontStyleName.startsWith('Bold')) {
return 700
} else if (fontStyleName.startsWith('ExtraBold')) {
return 800
} else if (fontStyleName.startsWith('Black')) {
return 900
} else {
return 400
}
}
async function loadFonts() {
const fontData = new Map()
const fonts = await loadFontList()
for (const font of fonts) {
const response = await fetch(`fonts/${font}`)
const arrayBuffer = await response.arrayBuffer()
const [fontName, fontStyleNameAndExtension] = font.split('-')
const [fontStyleName, extension] = fontStyleNameAndExtension.split('.')
if (!fontData.has(fontName)) {
fontData.set(fontName, [])
}
const id = crypto.randomUUID()
const weight = getFontStyleWeight(fontStyleName)
const isItalic = (fontStyleName.endsWith('Italic'))
const currentFontData = fontData.get(fontName)
currentFontData.push({
id,
url: `fonts/${font}`,
name: fontName,
weight: weight,
style: isItalic ? 'italic' : 'normal',
arrayBuffer,
})
}
return fontData
}
const searchParams = new URLSearchParams(location.search);
const debug = searchParams.has("debug")
? searchParams.get("debug").split(",")
: [];
const textEditorSelectionImposterElement = document.getElementById(
"text-editor-selection-imposter",
);
const textEditorElement = document.querySelector(".text-editor-content");
const textEditor = new TextEditor(textEditorElement, {
styleDefaults: {
"font-family": "MontserratAlternates",
"font-size": "14",
"font-weight": "500",
"font-style": "normal",
"line-height": "1.2",
"letter-spacing": "0",
direction: "ltr",
"text-align": "left",
"text-transform": "none",
"text-decoration": "none",
"--typography-ref-id": '["~#\'",null]',
"--typography-ref-file": '["~#\'",null]',
"--font-id": '["~#\'","MontserratAlternates"]',
"--fills": '[["^ ","~:fill-color","#000000","~:fill-opacity",1]]',
},
selectionImposterElement: textEditorSelectionImposterElement,
debug: new SelectionControllerDebug({
direction: document.getElementById("direction"),
multiElement: document.getElementById("multi"),
multiInlineElement: document.getElementById("multi-inline"),
multiParagraphElement: document.getElementById("multi-paragraph"),
isParagraphStart: document.getElementById("is-paragraph-start"),
isParagraphEnd: document.getElementById("is-paragraph-end"),
isInlineStart: document.getElementById("is-inline-start"),
isInlineEnd: document.getElementById("is-inline-end"),
isTextAnchor: document.getElementById("is-text-anchor"),
isTextFocus: document.getElementById("is-text-focus"),
focusNode: document.getElementById("focus-node"),
focusOffset: document.getElementById("focus-offset"),
focusInline: document.getElementById("focus-inline"),
focusParagraph: document.getElementById("focus-paragraph"),
anchorNode: document.getElementById("anchor-node"),
anchorOffset: document.getElementById("anchor-offset"),
anchorInline: document.getElementById("anchor-inline"),
anchorParagraph: document.getElementById("anchor-paragraph"),
startContainer: document.getElementById("start-container"),
startOffset: document.getElementById("start-offset"),
endContainer: document.getElementById("end-container"),
endOffset: document.getElementById("end-offset"),
}),
});
const fontFamilyElement = document.getElementById("font-family");
const fontSizeElement = document.getElementById("font-size");
const fontWeightElement = document.getElementById("font-weight");
const fontStyleElement = document.getElementById("font-style");
const directionLTRElement = document.getElementById("direction-ltr");
const directionRTLElement = document.getElementById("direction-rtl");
const lineHeightElement = document.getElementById("line-height");
const letterSpacingElement = document.getElementById("letter-spacing");
const textAlignLeftElement = document.getElementById("text-align-left");
const textAlignCenterElement = document.getElementById("text-align-center");
const textAlignRightElement = document.getElementById("text-align-right");
const textAlignJustifyElement = document.getElementById("text-align-justify");
const fonts = await loadFonts()
console.log(fonts)
const fontFamiliesFragment = document.createDocumentFragment()
for (const [font, fontData] of fonts) {
const fontFamilyOptionElement = document.createElement('option')
fontFamilyOptionElement.value = font
fontFamilyOptionElement.textContent = font
fontFamiliesFragment.appendChild(fontFamilyOptionElement)
}
fontFamilyElement.replaceChildren(fontFamiliesFragment)
function onDirectionChange(e) {
if (debug.includes("events")) {
console.log(e);
}
if (e.target.checked) {
textEditor.applyStylesToSelection({
direction: e.target.value,
});
}
}
directionLTRElement.addEventListener("change", onDirectionChange);
directionRTLElement.addEventListener("change", onDirectionChange);
function onTextAlignChange(e) {
if (debug.includes("events")) {
console.log(e);
}
if (e.target.checked) {
textEditor.applyStylesToSelection({
"text-align": e.target.value,
});
}
}
textAlignLeftElement.addEventListener("change", onTextAlignChange);
textAlignCenterElement.addEventListener("change", onTextAlignChange);
textAlignRightElement.addEventListener("change", onTextAlignChange);
textAlignJustifyElement.addEventListener("change", onTextAlignChange);
fontFamilyElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
const fontStyles = fonts.get(e.target.value)
console.log('fontStyles', fontStyles)
textEditor.applyStylesToSelection({
"font-family": e.target.value,
});
});
fontWeightElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-weight": e.target.value,
});
});
fontSizeElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-size": e.target.value,
});
});
lineHeightElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"line-height": e.target.value,
});
});
letterSpacingElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"letter-spacing": e.target.value,
});
});
fontStyleElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-style": e.target.value,
});
});
function formatHTML(html, options) {
const spaces = options?.spaces ?? 4;
let indent = 0;
return html.replace(/<\/?(.*?)>/g, (fullMatch) => {
let str = fullMatch + "\n";
if (fullMatch.startsWith("</")) {
--indent;
str = " ".repeat(indent * spaces) + str;
} else {
str = " ".repeat(indent * spaces) + str;
++indent;
if (fullMatch === "<br>") --indent;
}
return str;
});
}
const fontSize = 14;
const children = [];
const uuid = crypto.randomUUID();
children.push(uuid);
const outputElement = document.getElementById("output");
textEditorElement.addEventListener("input", (e) => {
if (debug.includes("events")) {
console.log(e);
}
outputElement.textContent = formatHTML(textEditor.element.innerHTML);
});
textEditor.addEventListener("needslayout", (e) => {
useShape(uuid);
updateTextShape(textEditor.root, fonts);
render();
})
textEditor.addEventListener("stylechange", (e) => {
if (debug.includes("events")) {
console.log(e);
}
const fontSize = parseInt(e.detail.getPropertyValue("font-size"), 10);
const fontWeight = e.detail.getPropertyValue("font-weight");
const fontStyle = e.detail.getPropertyValue("font-style");
const fontFamily = e.detail.getPropertyValue("font-family");
fontFamilyElement.value = fontFamily;
fontSizeElement.value = fontSize;
fontStyleElement.value = fontStyle;
fontWeightElement.value = fontWeight;
const textAlign = e.detail.getPropertyValue("text-align");
textAlignLeftElement.checked = textAlign === "left";
textAlignCenterElement.checked = textAlign === "center";
textAlignRightElement.checked = textAlign === "right";
textAlignJustifyElement.checked = textAlign === "justify";
const direction = e.detail.getPropertyValue("direction");
directionLTRElement.checked = direction === "ltr";
directionRTLElement.checked = direction === "rtl";
});
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const MIN_LINES = 1;
const MAX_LINES = 5;
const MIN_WORDS = 1;
const MAX_WORDS = 10;
initWasmModule().then(Module => {
init(Module);
assignCanvas(canvas);
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
Module._set_view(1, 0, 0);
Module._init_shapes_pool(1);
setupInteraction(canvas);
storeFonts(fonts)
useShape(uuid);
Module._set_parent(0, 0, 0, 0);
Module._set_shape_type(5);
const x1 = 0;
const y1 = 0;
const width = canvas.width;
const height = canvas.height;
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
addTextShape("", fonts);
useShape("00000000-0000-0000-0000-000000000000");
setShapeChildren(children);
render()
});
</script>
-->
<div id="output"></div>
<script type="module" src="/main.js"></script>
</body>
</html>

View File

@@ -1,216 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright (c) KALEIDOS INC
*/
import "./style.css";
import "./editor/TextEditor.css";
import { TextEditor } from "./editor/TextEditor";
import { SelectionControllerDebug } from "./editor/debug/SelectionControllerDebug";
const searchParams = new URLSearchParams(location.search);
const debug = searchParams.has("debug")
? searchParams.get("debug").split(",")
: [];
const textEditorSelectionImposterElement = document.getElementById(
"text-editor-selection-imposter",
);
const textEditorElement = document.querySelector(".text-editor-content");
const textEditor = new TextEditor(textEditorElement, {
styleDefaults: {
"font-family": "sourcesanspro",
"font-size": "14",
"font-weight": "500",
"font-style": "normal",
"line-height": "1.2",
"letter-spacing": "0",
direction: "ltr",
"text-align": "left",
"text-transform": "none",
"text-decoration": "none",
"--typography-ref-id": '["~#\'",null]',
"--typography-ref-file": '["~#\'",null]',
"--font-id": '["~#\'","sourcesanspro"]',
"--fills": '[["^ ","~:fill-color","#000000","~:fill-opacity",1]]',
},
selectionImposterElement: textEditorSelectionImposterElement,
debug: new SelectionControllerDebug({
direction: document.getElementById("direction"),
multiElement: document.getElementById("multi"),
multiInlineElement: document.getElementById("multi-inline"),
multiParagraphElement: document.getElementById("multi-paragraph"),
isParagraphStart: document.getElementById("is-paragraph-start"),
isParagraphEnd: document.getElementById("is-paragraph-end"),
isInlineStart: document.getElementById("is-inline-start"),
isInlineEnd: document.getElementById("is-inline-end"),
isTextAnchor: document.getElementById("is-text-anchor"),
isTextFocus: document.getElementById("is-text-focus"),
focusNode: document.getElementById("focus-node"),
focusOffset: document.getElementById("focus-offset"),
focusInline: document.getElementById("focus-inline"),
focusParagraph: document.getElementById("focus-paragraph"),
anchorNode: document.getElementById("anchor-node"),
anchorOffset: document.getElementById("anchor-offset"),
anchorInline: document.getElementById("anchor-inline"),
anchorParagraph: document.getElementById("anchor-paragraph"),
startContainer: document.getElementById("start-container"),
startOffset: document.getElementById("start-offset"),
endContainer: document.getElementById("end-container"),
endOffset: document.getElementById("end-offset"),
}),
});
const fontFamilyElement = document.getElementById("font-family");
const fontSizeElement = document.getElementById("font-size");
const fontWeightElement = document.getElementById("font-weight");
const fontStyleElement = document.getElementById("font-style");
const directionLTRElement = document.getElementById("direction-ltr");
const directionRTLElement = document.getElementById("direction-rtl");
const lineHeightElement = document.getElementById("line-height");
const letterSpacingElement = document.getElementById("letter-spacing");
const textAlignLeftElement = document.getElementById("text-align-left");
const textAlignCenterElement = document.getElementById("text-align-center");
const textAlignRightElement = document.getElementById("text-align-right");
const textAlignJustifyElement = document.getElementById("text-align-justify");
function onDirectionChange(e) {
if (debug.includes("events")) {
console.log(e);
}
if (e.target.checked) {
textEditor.applyStylesToSelection({
direction: e.target.value,
});
}
}
directionLTRElement.addEventListener("change", onDirectionChange);
directionRTLElement.addEventListener("change", onDirectionChange);
function onTextAlignChange(e) {
if (debug.includes("events")) {
console.log(e);
}
if (e.target.checked) {
textEditor.applyStylesToSelection({
"text-align": e.target.value,
});
}
}
textAlignLeftElement.addEventListener("change", onTextAlignChange);
textAlignCenterElement.addEventListener("change", onTextAlignChange);
textAlignRightElement.addEventListener("change", onTextAlignChange);
textAlignJustifyElement.addEventListener("change", onTextAlignChange);
fontFamilyElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-family": e.target.value,
});
});
fontWeightElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-weight": e.target.value,
});
});
fontSizeElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-size": e.target.value,
});
});
lineHeightElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"line-height": e.target.value,
});
});
letterSpacingElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"letter-spacing": e.target.value,
});
});
fontStyleElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-style": e.target.value,
});
});
function formatHTML(html, options) {
const spaces = options?.spaces ?? 4;
let indent = 0;
return html.replace(/<\/?(.*?)>/g, (fullMatch) => {
let str = fullMatch + "\n";
if (fullMatch.startsWith("</")) {
--indent;
str = " ".repeat(indent * spaces) + str;
} else {
str = " ".repeat(indent * spaces) + str;
++indent;
if (fullMatch === "<br>") --indent;
}
return str;
});
}
const outputElement = document.getElementById("output");
textEditorElement.addEventListener("input", (e) => {
if (debug.includes("events")) {
console.log(e);
}
outputElement.textContent = formatHTML(textEditor.element.innerHTML);
});
textEditor.addEventListener("stylechange", (e) => {
if (debug.includes("events")) {
console.log(e);
}
const fontSize = parseInt(e.detail.getPropertyValue("font-size"), 10);
const fontWeight = e.detail.getPropertyValue("font-weight");
const fontStyle = e.detail.getPropertyValue("font-style");
const fontFamily = e.detail.getPropertyValue("font-family");
fontFamilyElement.value = fontFamily;
fontSizeElement.value = fontSize;
fontStyleElement.value = fontStyle;
fontWeightElement.value = fontWeight;
const textAlign = e.detail.getPropertyValue("text-align");
textAlignLeftElement.checked = textAlign === "left";
textAlignCenterElement.checked = textAlign === "center";
textAlignRightElement.checked = textAlign === "right";
textAlignJustifyElement.checked = textAlign === "justify";
const direction = e.detail.getPropertyValue("direction");
directionLTRElement.checked = direction === "ltr";
directionRTLElement.checked = direction === "rtl";
});

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,26 @@
MontserratAlternates-BlackItalic.ttf
MontserratAlternates-Black.ttf
MontserratAlternates-BoldItalic.ttf
MontserratAlternates-Bold.ttf
MontserratAlternates-ExtraBoldItalic.ttf
MontserratAlternates-ExtraBold.ttf
MontserratAlternates-ExtraLightItalic.ttf
MontserratAlternates-ExtraLight.ttf
MontserratAlternates-Italic.ttf
MontserratAlternates-LightItalic.ttf
MontserratAlternates-Light.ttf
MontserratAlternates-MediumItalic.ttf
MontserratAlternates-Medium.ttf
MontserratAlternates-Regular.ttf
MontserratAlternates-SemiBoldItalic.ttf
MontserratAlternates-SemiBold.ttf
MontserratAlternates-ThinItalic.ttf
MontserratAlternates-Thin.ttf
Oswald-Bold.ttf
Oswald-ExtraLight.ttf
Oswald-Light.ttf
Oswald-Medium.ttf
Oswald-Regular.ttf
Oswald-SemiBold.ttf
Oswald-VariableFont_wght.ttf
PlaywriteMXGuides-Regular.ttf

View File

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 995 B

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,482 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Playwrite+ES:wght@100..400&family=Playwrite+NZ:wght@100..400&family=Playwrite+US+Trad:wght@100..400&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Penpot - Text Editor Playground</title>
</head>
<body>
<div class="playground">
<form>
<fieldset>
<legend>Styles</legend>
<!-- Font -->
<div class="form-group">
<label for="font-family">Font family</label>
<select id="font-family">
<option value="Open+Sans">Open Sans</option>
<option value="sourcesanspro">Source Sans Pro</option>
<option value="whatever">Whatever</option>
</select>
</div>
<div class="form-group">
<label for="font-size">Font size</label>
<input id="font-size" type="number" value="14" />
</div>
<div class="form-group">
<label for="font-weight">Font weight</label>
<select id="font-weight">
<option value="100">100</option>
<option value="200">200</option>
<option value="300">300</option>
<option value="400">400 (normal)</option>
<option value="500">500</option>
<option value="600">600</option>
<option value="700">700 (bold)</option>
<option value="800">800</option>
<option value="900">900</option>
</select>
</div>
<div class="form-group">
<label for="font-style">Font style</label>
<select id="font-style">
<option value="normal">normal</option>
<option value="italic">italic</option>
<option value="oblique">oblique</option>
</select>
</div>
<!-- Text attributes -->
<div class="form-group">
<label for="line-height">Line height</label>
<input id="line-height" type="number" value="1.0" />
</div>
<div class="form-group">
<label for="letter-spacing">Letter spacing</label>
<input id="letter-spacing" type="number" value="0.0" />
</div>
<div class="form-group">
<label for="direction-ltr">LTR</label>
<input id="direction-ltr" type="radio" name="direction" value="ltr" checked />
</div>
<div class="form-group">
<label for="direction-rtl">RTL</label>
<input id="direction-rtl" type="radio" name="direction" value="rtl" />
</div>
<!-- Text Align -->
<div class="form-group">
<label for="text-align-left">Align left</label>
<input id="text-align-left" type="radio" name="text-align" value="left" checked />
</div>
<div class="form-group">
<label for="text-align-center">Align center</label>
<input id="text-align-center" type="radio" name="text-align" value="center" />
</div>
<div class="form-group">
<label for="text-align-right">Align right</label>
<input id="text-align-right" type="radio" name="text-align" value="right" />
</div>
<div class="form-group">
<label for="text-align-justify">Align justify</label>
<input id="text-align-justify" type="radio" name="text-align" value="justify" />
</div>
<!-- Text Transform -->
<div class="form-group">
<label for="text-transform-none">None</label>
<input id="text-transform-none" type="radio" name="text-transform" value="none" checked />
</div>
<div class="form-group">
<label for="text-transform-uppercase">Uppercase</label>
<input id="text-transform-uppercase" type="radio" name="text-transform" value="uppercase" checked />
</div>
<div class="form-group">
<label for="text-transform-capitalize">Capitalize</label>
<input id="text-transform-capitalize" type="radio" name="text-transform" value="capitalize" />
</div>
<div class="form-group">
<label for="text-transform-lowercase">Lowercase</label>
<input id="text-transform-lowercase" type="radio" name="text-transform" value="lowercase" />
</div>
</fieldset>
<fieldset>
<legend>Debug</legend>
<div class="form-group">
<label for="direction">Direction</label>
<input id="direction" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-node">Focus Node</label>
<input id="focus-node" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-offset">Focus offset</label>
<input id="focus-offset" readonly type="number">
</div>
<div class="form-group">
<label for="focus-inline">Focus Inline</label>
<input id="focus-inline" readonly type="text" />
</div>
<div class="form-group">
<label for="focus-paragraph">Focus Paragraph</label>
<input id="focus-paragraph" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-node">Anchor Node</label>
<input id="anchor-node" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-offset">Anchor offset</label>
<input id="anchor-offset" readonly type="number">
</div>
<div class="form-group">
<label for="anchor-inline">Anchor Inline</label>
<input id="anchor-inline" readonly type="text" />
</div>
<div class="form-group">
<label for="anchor-paragraph">Anchor Paragraph</label>
<input id="anchor-paragraph" readonly type="text" />
</div>
<div class="form-group">
<label for="start-container">Start container</label>
<input id="start-container" readonly type="text" />
</div>
<div class="form-group">
<label for="start-offset">Start offset</label>
<input id="start-offset" readonly type="text" />
</div>
<div class="form-group">
<label for="end-container">End container</label>
<input id="end-container" readonly type="text" />
</div>
<div class="form-group">
<label for="end-offset">End offset</label>
<input id="end-offset" readonly type="text" />
</div>
<div class="form-group">
<label for="multi">Multi?</label>
<input id="multi" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="multi-inline">Multi inline?</label>
<input id="multi-inline" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="multi-paragraph">Multi paragraph?</label>
<input id="multi-paragraph" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-text-focus">Is text focus?</label>
<input id="is-text-focus" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-text-anchor">Is text anchor?</label>
<input id="is-text-anchor" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-paragraph-start">Is paragraph start?</label>
<input id="is-paragraph-start" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-paragraph-end">Is paragraph end?</label>
<input id="is-paragraph-end" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-inline-start">Is inline start?</label>
<input id="is-inline-start" readonly type="checkbox" />
</div>
<div class="form-group">
<label for="is-inline-end">Is inline end?</label>
<input id="is-inline-end" readonly type="checkbox">
</div>
</fieldset>
</form>
<!--
Editor
-->
<div class="text-editor-container align-top">
<div
id="text-editor-selection-imposter"
class="text-editor-selection-imposter"></div>
<div
class="text-editor-content"
contenteditable="true"
role="textbox"
aria-multiline="true"
aria-autocomplete="none"
spellcheck="false"
autocapitalize="false"></div>
</div>
<!--
Text output
-->
<canvas id="canvas"></canvas>
</div>
<script type="module">
import "./style.css";
import "./editor/TextEditor.css";
import { TextEditor } from "./editor/TextEditor";
import { SelectionControllerDebug } from "./editor/debug/SelectionControllerDebug";
import initWasmModule from './wasm/render_wasm.js';
import {
init, assignCanvas, render, setupInteraction, useShape, setShapeChildren, addTextShape, updateTextShape, hexToU32ARGB,getRandomInt, getRandomColor, getRandomFloat, addShapeSolidFill, addShapeSolidStrokeFill
} from './wasm/lib.js';
const searchParams = new URLSearchParams(location.search);
const debug = searchParams.has("debug")
? searchParams.get("debug").split(",")
: [];
const textEditorSelectionImposterElement = document.getElementById(
"text-editor-selection-imposter",
);
const textEditorElement = document.querySelector(".text-editor-content");
const textEditor = new TextEditor(textEditorElement, {
styleDefaults: {
"font-family": "sourcesanspro",
"font-size": "14",
"font-weight": "500",
"font-style": "normal",
"line-height": "1.2",
"letter-spacing": "0",
direction: "ltr",
"text-align": "left",
"text-transform": "none",
"text-decoration": "none",
"--typography-ref-id": '["~#\'",null]',
"--typography-ref-file": '["~#\'",null]',
"--font-id": '["~#\'","sourcesanspro"]',
"--fills": '[["^ ","~:fill-color","#000000","~:fill-opacity",1]]',
},
selectionImposterElement: textEditorSelectionImposterElement,
debug: new SelectionControllerDebug({
direction: document.getElementById("direction"),
multiElement: document.getElementById("multi"),
multiInlineElement: document.getElementById("multi-inline"),
multiParagraphElement: document.getElementById("multi-paragraph"),
isParagraphStart: document.getElementById("is-paragraph-start"),
isParagraphEnd: document.getElementById("is-paragraph-end"),
isInlineStart: document.getElementById("is-inline-start"),
isInlineEnd: document.getElementById("is-inline-end"),
isTextAnchor: document.getElementById("is-text-anchor"),
isTextFocus: document.getElementById("is-text-focus"),
focusNode: document.getElementById("focus-node"),
focusOffset: document.getElementById("focus-offset"),
focusInline: document.getElementById("focus-inline"),
focusParagraph: document.getElementById("focus-paragraph"),
anchorNode: document.getElementById("anchor-node"),
anchorOffset: document.getElementById("anchor-offset"),
anchorInline: document.getElementById("anchor-inline"),
anchorParagraph: document.getElementById("anchor-paragraph"),
startContainer: document.getElementById("start-container"),
startOffset: document.getElementById("start-offset"),
endContainer: document.getElementById("end-container"),
endOffset: document.getElementById("end-offset"),
}),
});
const fontFamilyElement = document.getElementById("font-family");
const fontSizeElement = document.getElementById("font-size");
const fontWeightElement = document.getElementById("font-weight");
const fontStyleElement = document.getElementById("font-style");
const directionLTRElement = document.getElementById("direction-ltr");
const directionRTLElement = document.getElementById("direction-rtl");
const lineHeightElement = document.getElementById("line-height");
const letterSpacingElement = document.getElementById("letter-spacing");
const textAlignLeftElement = document.getElementById("text-align-left");
const textAlignCenterElement = document.getElementById("text-align-center");
const textAlignRightElement = document.getElementById("text-align-right");
const textAlignJustifyElement = document.getElementById("text-align-justify");
function onDirectionChange(e) {
if (debug.includes("events")) {
console.log(e);
}
if (e.target.checked) {
textEditor.applyStylesToSelection({
direction: e.target.value,
});
}
}
directionLTRElement.addEventListener("change", onDirectionChange);
directionRTLElement.addEventListener("change", onDirectionChange);
function onTextAlignChange(e) {
if (debug.includes("events")) {
console.log(e);
}
if (e.target.checked) {
textEditor.applyStylesToSelection({
"text-align": e.target.value,
});
}
}
textAlignLeftElement.addEventListener("change", onTextAlignChange);
textAlignCenterElement.addEventListener("change", onTextAlignChange);
textAlignRightElement.addEventListener("change", onTextAlignChange);
textAlignJustifyElement.addEventListener("change", onTextAlignChange);
fontFamilyElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-family": e.target.value,
});
});
fontWeightElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-weight": e.target.value,
});
});
fontSizeElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-size": e.target.value,
});
});
lineHeightElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"line-height": e.target.value,
});
});
letterSpacingElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"letter-spacing": e.target.value,
});
});
fontStyleElement.addEventListener("change", (e) => {
if (debug.includes("events")) {
console.log(e);
}
textEditor.applyStylesToSelection({
"font-style": e.target.value,
});
});
function formatHTML(html, options) {
const spaces = options?.spaces ?? 4;
let indent = 0;
return html.replace(/<\/?(.*?)>/g, (fullMatch) => {
let str = fullMatch + "\n";
if (fullMatch.startsWith("</")) {
--indent;
str = " ".repeat(indent * spaces) + str;
} else {
str = " ".repeat(indent * spaces) + str;
++indent;
if (fullMatch === "<br>") --indent;
}
return str;
});
}
const fontSize = 14;
const children = [];
const uuid = crypto.randomUUID();
children.push(uuid);
const outputElement = document.getElementById("output");
textEditorElement.addEventListener("input", (e) => {
if (debug.includes("events")) {
console.log(e);
}
outputElement.textContent = formatHTML(textEditor.element.innerHTML);
});
textEditor.addEventListener("needslayout", (e) => {
useShape(uuid);
updateTextShape(fontSize, textEditor.root);
render();
})
textEditor.addEventListener("stylechange", (e) => {
if (debug.includes("events")) {
console.log(e);
}
const fontSize = parseInt(e.detail.getPropertyValue("font-size"), 10);
const fontWeight = e.detail.getPropertyValue("font-weight");
const fontStyle = e.detail.getPropertyValue("font-style");
const fontFamily = e.detail.getPropertyValue("font-family");
fontFamilyElement.value = fontFamily;
fontSizeElement.value = fontSize;
fontStyleElement.value = fontStyle;
fontWeightElement.value = fontWeight;
const textAlign = e.detail.getPropertyValue("text-align");
textAlignLeftElement.checked = textAlign === "left";
textAlignCenterElement.checked = textAlign === "center";
textAlignRightElement.checked = textAlign === "right";
textAlignJustifyElement.checked = textAlign === "justify";
const direction = e.detail.getPropertyValue("direction");
directionLTRElement.checked = direction === "ltr";
directionRTLElement.checked = direction === "rtl";
});
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const MIN_LINES = 1;
const MAX_LINES = 5;
const MIN_WORDS = 1;
const MAX_WORDS = 10;
initWasmModule().then(Module => {
init(Module);
assignCanvas(canvas);
Module._set_canvas_background(hexToU32ARGB("#FABADA", 1));
Module._set_view(1, 0, 0);
Module._init_shapes_pool(1);
setupInteraction(canvas);
useShape(uuid);
Module._set_parent(0, 0, 0, 0);
Module._set_shape_type(5);
const x1 = 0;
const y1 = 0;
const width = canvas.width;
const height = canvas.height;
Module._set_shape_selrect(x1, y1, x1 + width, y1 + height);
addTextShape(fontSize, "");
useShape("00000000-0000-0000-0000-000000000000");
setShapeChildren(children);
render()
});
</script>
</body>
</html>

View File

@@ -182,7 +182,6 @@ export function set_parent(id) {
}
export function render() {
console.log('render')
Module._set_view(1, 0, 0);
Module._render_from_cache();
debouncedRender();
@@ -284,9 +283,11 @@ function getFontStyle(fontStyle) {
switch (fontStyle) {
default:
case 'normal':
case 'regular':
return 0;
case 'oblique':
case 'italic':
return 0;
return 1;
}
}
@@ -316,6 +317,7 @@ function setParagraphData(dview, { numLeaves, textAlign, textDirection, textDeco
}
function setLeafData(dview, leafOffset, {
fontId,
fontStyle,
fontSize,
fontWeight,
@@ -331,10 +333,10 @@ function setLeafData(dview, leafOffset, {
dview.setFloat32(leafOffset + 4, fontSize, true); // font-size
dview.setFloat32(leafOffset + 8, letterSpacing, true); // letter-spacing
dview.setInt32(leafOffset + 12, fontWeight, true); // font-weight: normal
dview.setUint32(leafOffset + 16, 0, true); // font-id (UUID part 1)
dview.setUint32(leafOffset + 20, 0, true); // font-id (UUID part 2)
dview.setUint32(leafOffset + 24, 0, true); // font-id (UUID part 3)
dview.setUint32(leafOffset + 28, 0, true); // font-id (UUID part 4)
dview.setUint32(leafOffset + 16, fontId[0], true); // font-id (UUID part 1)
dview.setUint32(leafOffset + 20, fontId[1], true); // font-id (UUID part 2)
dview.setUint32(leafOffset + 24, fontId[2], true); // font-id (UUID part 3)
dview.setUint32(leafOffset + 28, fontId[3], true); // font-id (UUID part 4)
dview.setUint32(leafOffset + 32, 0, true); // font-family hash
dview.setUint32(leafOffset + 36, 0, true); // font-variant-id (UUID part 1)
dview.setUint32(leafOffset + 40, 0, true); // font-variant-id (UUID part 2)
@@ -344,7 +346,15 @@ function setLeafData(dview, leafOffset, {
dview.setUint32(leafOffset + 56, totalFills, true); // total fills count
}
export function updateTextShape(fontSize, root) {
function getFontFrom(fontFamily, fontWeight, fontStyle, fonts) {
const fontList = fonts.get(fontFamily)
if (!fontList) {
return null
}
return fontList.find(fontData => fontData.weight === fontWeight && fontStyle === fontData.style)
}
export function updateTextShape(root, fonts) {
// Calculate fills
const fills = [
{
@@ -358,7 +368,6 @@ export function updateTextShape(fontSize, root) {
const totalFillsSize = totalFills * FILL_SIZE;
const paragraphs = root.children;
console.log("paragraphs", paragraphs.length);
Module._clear_shape_text();
for (const paragraph of paragraphs) {
@@ -366,19 +375,16 @@ export function updateTextShape(fontSize, root) {
const leaves = paragraph.children;
const numLeaves = leaves.length;
console.log("leaves", numLeaves);
for (const leaf of leaves) {
const text = leaf.textContent;
const textBuffer = new TextEncoder().encode(text);
const textSize = textBuffer.byteLength;
console.log("text", text, textSize);
totalSize += LEAF_ATTR_SIZE + totalFillsSize;
}
totalSize += paragraph.textContent.length;
console.log("Total Size", totalSize);
// Allocate buffer
const bufferPtr = allocBytes(totalSize);
const heap = new Uint8Array(Module.HEAPU8.buffer, bufferPtr, totalSize);
@@ -387,39 +393,21 @@ export function updateTextShape(fontSize, root) {
const textAlign = getTextAlign(
paragraph.style.getPropertyValue("text-align"),
);
console.log("text-align", textAlign);
const textDirection = getTextDirection(
paragraph.style.getPropertyValue("text-direction"),
);
console.log("text-direction", textDirection);
const textDecoration = getTextDecoration(
paragraph.style.getPropertyValue("text-decoration"),
);
console.log("text-decoration", textDecoration);
const textTransform = getTextTransform(
paragraph.style.getPropertyValue("text-transform"),
);
console.log("text-transform", textTransform);
const lineHeight = parseFloat(
paragraph.style.getPropertyValue("line-height"),
);
console.log("line-height", lineHeight);
const letterSpacing = parseFloat(
paragraph.style.getPropertyValue("letter-spacing"),
);
console.log("letter-spacing", letterSpacing);
/*
num_leaves: u32,
text_align: u8,
text_direction: u8,
text_decoration: u8,
text_transform: u8,
line_height: f32,
letter_spacing: f32,
typography_ref_file: [u32; 4],
typography_ref_id: [u32; 4],
*/
setParagraphData(dview, {
numLeaves,
@@ -432,31 +420,25 @@ export function updateTextShape(fontSize, root) {
})
let leafOffset = PARAGRAPH_ATTR_SIZE;
for (const leaf of leaves) {
console.log(
"leafOffset",
leafOffset,
PARAGRAPH_ATTR_SIZE,
LEAF_ATTR_SIZE,
FILL_SIZE,
totalFills,
totalFillsSize,
);
const fontStyle = getFontStyle(leaf.style.getPropertyValue("font-style"));
const fontStyle = leaf.style.getPropertyValue("font-style");
const fontStyleSerialized = getFontStyle(fontStyle);
const fontSize = parseFloat(leaf.style.getPropertyValue("font-size"));
const letterSpacing = parseFloat(leaf.style.getPropertyValue("letter-spacing"))
console.log("font-size", fontSize, "letter-spacing", letterSpacing);
const fontWeight = parseInt(
leaf.style.getPropertyValue("font-weight"),
10,
);
console.log("font-weight", fontWeight);
const text = leaf.textContent;
const textBuffer = new TextEncoder().encode(text);
const textSize = textBuffer.byteLength;
const fontFamily = leaf.style.getPropertyValue('font-family');
const fontData = getFontFrom(fontFamily, fontWeight, fontStyle, fonts)
const defaultFontId = new Uint32Array([0, 0, 0, 0])
const fontId = fontData ? getU32(fontData.id) : defaultFontId
setLeafData(dview, leafOffset, {
fontStyle,
fontId,
fontStyle: fontStyleSerialized,
textDecoration: 0,
textTransform: 0,
textDirection: 0,
@@ -464,8 +446,8 @@ export function updateTextShape(fontSize, root) {
fontWeight,
letterSpacing,
textSize,
totalFills
})
totalFills,
});
// Serialize fills
let fillOffset = leafOffset + LEAF_ATTR_SIZE;
@@ -485,14 +467,13 @@ export function updateTextShape(fontSize, root) {
// Add text content
const textOffset = leafOffset;
console.log('textOffset', textOffset);
heap.set(textBuffer, textOffset);
Module._set_shape_text_content();
}
}
export function addTextShape(fontSize, text) {
export function addTextShape(text, fonts) {
const numLeaves = 1; // Single text leaf for simplicity
const textBuffer = new TextEncoder().encode(text);
const textSize = textBuffer.byteLength;
@@ -529,13 +510,25 @@ export function addTextShape(fontSize, text) {
// Serialize leaf attributes
const leafOffset = PARAGRAPH_ATTR_SIZE;
const fontStyle = getFontStyle('normal');
const fontStyleSerialized = getFontStyle(fontStyle);
const fontSize = 14;
const letterSpacing = 0;
const fontWeight = 400;
const fontFamily = 'MontserratAlternates';
const fontData = getFontFrom(fontFamily, fontWeight, fontStyle, fonts);
const defaultFontId = new Uint32Array([0, 0, 0, 0]);
const fontId = fontData ? getU32(fontData.id) : defaultFontId;
setLeafData(dview, leafOffset, {
fontId,
fontSize,
fontWeight: 400,
fontStyle: fontStyleSerialized,
fontWeight,
textDecoration: 0,
textDirection: 0,
textTransform: 0,
letterSpacing: 0,
letterSpacing,
textSize,
totalFills,
});
@@ -558,3 +551,44 @@ export function addTextShape(fontSize, text) {
// Call the WebAssembly function
Module._set_shape_text_content();
}
export function storeFonts(fonts) {
for (const [fontName, fontStyles] of fonts) {
for (const font of fontStyles) {
const shapeId = getU32('00000000-0000-0000-0000-000000000000');
const fontId = getU32(font.id);
const weight = font.weight;
const style = getFontStyle(font.style);
const size = font.arrayBuffer.byteLength;
const ptr = Module._alloc_bytes(size);
const heap = Module.HEAPU8;
const mem = new Uint8Array(heap.buffer, ptr, size);
mem.set(new Uint8Array(font.arrayBuffer));
const emoji = false
const fallback = false
Module._store_font(
shapeId[0],
shapeId[1],
shapeId[2],
shapeId[3],
fontId[0],
fontId[1],
fontId[2],
fontId[3],
weight,
style,
emoji,
fallback,
);
Module._is_font_uploaded(
fontId[0],
fontId[1],
fontId[2],
fontId[3],
weight,
style,
emoji,
);
}
}
}