ساخت انتخابگر فونت در جاوا اسکریپت
ساخت انتخابگر فونت در جاوا اسکریپت
مدتها پیش، هنگام ساختن سایت سازنده Pinecast، به یک منوی کشویی نیاز داشتم که به کاربر اجازه میدهد فونتی را از فونتهای گوگل انتخاب کند. اکثر انتخابگرهای فونت در سایر برنامهها، هر نوع تایپ را با رندر کردن نام خود نشان میدهند. در حالی که برنامه هایی مانند Google Docs و سایرین فقط تعداد انگشت شماری از فونت ها را برای انتخاب نمایش می دهند، سایت ساز نیاز به نمایش بیش از هزار فونت دارد. این یک مجموعه نسبتاً منحصر به فرد و پیچیده از چالش ها را معرفی کرد.
فونتها در فونتهای Google بهعنوان WOFF2 (یا WOFF یا فرمت دیگری اگر مرورگر شما قدیمیتر است) ارائه میشوند. استفاده مستقیم از اینها گزینه ضعیفی برای کشویی است زیرا باز کردن منوی کرکره باعث می شود هزاران بار از صدها مگابایت فایل فونت از Google بارگیری شود. شروع بارگیری زمانی که فونت واقعاً نمایش داده می شود، اسکرول را با تأخیر مواجه می کند و باعث فلش فونت های رندر نشده می شود.
در عوض، تصمیم گرفتم هر فونت را از قبل رندر کنم. با انجام این کار، یک پیش نمایش به راحتی قابل اجرا در جاوا اسکریپت من ذخیره می شود و بلافاصله به صفحه نمایش کشیده می شود.
من پروژه را با ساختن یک کامپایلر شروع کردم. این کامپایلر لیست کامل فونت ها را از Google Fonts API دانلود می کند. نامزدهای ضعیف را برای فونتها (مانند بارکد) فیلتر میکند و ساختارهای دادهای را برای ذخیره ابرداده ایجاد میکند. فایل TTF هر فونت در /tmp ذخیره می شود و به opentype.js ارسال می شود تا به عنوان یک SVG ارائه شود. هر مسیر SVG (یعنی مسیر برداری، نه مسیر فایل) به عنوان یک رشته ذخیره میشود تا بعداً با React ارائه شود.
این امر مستلزم یک سرهم بندی خوب بود. هر فایل TTF به عنوان یک بافر در حافظه بارگذاری می شود و مسیر SVG حاصل می تواند ده ها کیلوبایت باشد. اگر به طور همزمان انجام شود، Node به راحتی خطاهای خارج از حافظه را بررسی می کند. من این مشکل را به درستی حل نکردم (به عنوان مثال، با یک سمافور)، اما در عوض از یک جریان نوشتن برای تخلیه هر خط از مسیرهای فونت رندر شده به دیسک استفاده کردم، و به جمعآورنده زباله اجازه داد هر فایل فونت و مسیر را همانطور که هست پاک کند. ارائه شده است.
انجام پروژه در فریلنس پروژه
انتخابگر فونت با استفاده از مسیرهای فونت از پیش رندر شده
این نتایجی را که من می خواستم به دست آورد، اما یک چالش جدید ظاهر شد. فایل TypeScript حاصل بسیار زیاد بود. بعد از gzip حجم فایل مگابایت بود. حتی اگر تمام فونت هایی را که بیش از 14 کیلوبایت به عنوان مسیر SVG مصرف می کنند کنار بگذارم، بسته به دست آمده سه مگابایت بزرگتر از قبل بود.
میتوان برای کاهش زمان بارگذاری در چنین برنامههای پیچیدهای استدلال کرد و از شاخصهای بارگذاری (مانند اسپینرها یا نقاط رقصنده) برای کاهش تأخیر در بارگیری بسته جاوا اسکریپت استفاده کرد. با این حال، این راضی کننده نیست، و با اضافه کردن فونت های جدید گوگل، مشکل بدتر خواهد شد.
تقسیم کد برای نجات
ساخت انتخابگر فونت در جاوا اسکریپت
یک راه سریع برای جلوگیری از مشکل، بارگیری داده های فونت به طور جداگانه از بسته اصلی جاوا اسکریپت است. چند رویکرد واضح وجود دارد. ساده ترین کار این است که داده های فونت را به عنوان یک فایل JSON با fetch بارگیری کنید. این امر بارگذاری فونت را در صورت نیاز آسان می کند، اما تعداد زیادی دیگ درگیر است و به خوبی با سیستم ساخت بازی نمی کند.
پاسخ دیگر این است که به Webpack تکیه کنید تا کارهای سنگین را برای ما انجام دهید. این تکنیک به تقسیم کد معروف است.
تقسیم کد راهی است برای آموزش Webpack برای تولید یک بسته جاوا اسکریپت دوم (یا سوم یا چهارم …) که حاوی تکه ای از برنامه شما است. سپس می توانید آن قطعه را پس از بارگیری بسته اولیه بارگیری کنید.
من به جزئیات در مورد نحوه انجام تقسیم کد با Webpack نمی پردازم، زیرا مقالات زیادی در آنجا وجود دارد که به جزئیات زیاد می پردازند. طولانی و کوتاه آن این است که فایل جاوا اسکریپت حجیم را بعد از بقیه صفحه بارگذاری می کنید، به برنامه شما اجازه می دهید زودتر بارگیری شود و تنها زمانی که رابط کاربری قبل از بارگیری قطعه جاوا اسکریپت رندر شود، رندر رابط کاربری خود را در دانلود مسدود می کند.
هنگامی که پیشنمایشهای فونت من در فایل خود ذخیره میشوند، مشکلات عملکرد تنها در صورتی مشکل ساز میشوند که انتخابگر فونت را باز کنید. بسیاری از کاربران انتخابگر فونت را باز نمی کنند، بنابراین مشکل بیشتر کاهش می یابد. با این حال، ما هنوز نمیخواهیم زمانی که انتخابگر فونت برای اولین بار باز میشود، ده یا پانزده ثانیه طول بکشد تا فونتهای شما بارگیری شوند.
فکر کردن به فشرده سازی
اندازه هنوز مشکل دارد. بسته حاوی اطلاعات مسیر قلم به تنهایی بیش از سه مگابایت است. باید راه بهتری وجود داشته باشد
من شک داشتم که راهی برای رمزگذاری مسیرهای SVG وجود دارد که فضای کمتری مصرف کند. مسیرهای SVG حروف (“دستورها”) هستند که با اعداد اعشاری صفر یا بیشتر (“پارامترها”) دنبال می شوند. من حدس زدم که یک رمزگذاری باینری بسیار کارآمدتر از یک رمزگذاری مبتنی بر متن است.
اگر دادههای باینری را با ascii85 رمزگذاری کنیم (از آنجایی که دادههای باینری را نمیتوان مستقیماً در یک فایل جاوا اسکریپت کدگذاری کرد)، حدود 25٪ سربار اضافه میکند.
بسیاری از دستورات مسیر SVG دارای دو عدد ممیز شناور هستند. با شناورهای 64 بیتی، این 16 بایت برای هر دستور با دو پارامتر است، اگرچه می توانیم از اعداد ممیز شناور 32 بیتی برای ذخیره 8 بایت استفاده کنیم. جفت هایی مانند 0 0 زمانی که به عنوان یک رشته ذخیره می شوند بسیار کوتاهتر از هشت بایت خواهند بود، اما جفت هایی مانند -12.12 15.67 چند بایت را با رمزگذاری باینری ذخیره می کنند.
را
پارامترهای ممیز شناور بیشتر در هر دستور مسیر، احتمال ذخیره بیت ها بیشتر است (زیرا فرصت های بیشتری برای بیت های تلف شده در رمزگذاری رشته وجود دارد). توزیع دستورات نقش زیادی در تعیین اندازه دارد.
بیایید تست کنیم!
پاس ساده لوحانه
اولین و ساده ترین آزمایش این بود که فقط یک رمزگذاری باینری از مسیرهای SVG ایجاد کنید. من از ماژول parse-svg برای تجزیه مسیرهای SVG به آرایههای جاوا اسکریپت استفاده کردم که M1.8 0.2L0.2 -0.5 را به شکل [[‘M’, 1.8, 0.2], [‘L’, 0.2, – تبدیل میکند. 0.5]، …]، و سپس آن را در یک ArrayBuffer متشکل از سه بخش بسته بندی کرد:
یک سرصفحه uint 32 بیتی که تعداد دستورات مسیر را فهرست می کند
یک Uint8Array حاوی دستوراتی (اصطلاح SVG برای حروف موجود در مسیر SVG) که به هر کاراکتر استفاده شده در مسیرها نگاشت می شود.
یک Float32Array حاوی هر یک از پارامترهای هر دستور مسیر
برای هر نوع فرمان، تعداد ثابتی از پارامترها وجود دارد. برای مثال M همیشه دو پارامتر خواهد داشت. بر اساس Uint8Array، میتوانیم تعداد اعداد را از Float32Array بخوانیم.
اینجا ضایعات زیادی وجود دارد. اول، 8 بیت برای دستورات هدر دادن بزرگی است: تنها حدود 20 دستور توسط مشخصات SVG تعریف شده است، به این معنی که در هر دستور سه بیت تلف می شود. در مرحله بعد، 32 بیت در هر پارامتر بسیار بیشتر از مقدار مورد نیاز است. دستور مسیر M0 0 فقط چهار بایت طول می کشد تا به عنوان یک رشته رمزگذاری شود، اما ما نه بایت مصرف می کنیم تا آن را در قالب باینری رمزگذاری کنیم.
من یک اسکریپت کوچک نوشتم تا آزمایش تلاش هایم را خودکار کنم، و تلاش ساده لوحانه این خروجی را ایجاد کرد:
بایت های ذخیره شده: -721494
نسبت: 1.2097567163545102
نسبت Gzip: 1.4426273810518684
در مقایسه با نسخه gzip فایل اصلی، تلاش سادهلوحانه من خروجی 40 درصد بزرگتر ایجاد کرد!
بسته بندی دستورات
پاس بعدی من این بود که بسته بندی داده ها را محکم تر شروع کنم. بدیهی ترین قدم اول، بسته بندی دستورات در پنج بیت است تا هر یک از 20 احتمال را نشان دهد.
من یک پیاده سازی از آنچه UintNArray می نامم نوشتم که طول و تعداد بیت در هر واحد طول می کشد. درست مانند Uint8Array رفتار می کند، با این تفاوت که مقادیر را به جای 8 به N بیت کوتاه می کند.
با استفاده از این برای بسته بندی دستورات به جای Uint8Array، توانستم چند بایت ذخیره کنم:
بایت های ذخیره شده: -580885
نسبت: 1.1688470197477028
نسبت Gzip: 1.4499228881612436
کل بایت های ذخیره شده بسیار بهبود یافته است! در مقایسه با تلاش قبلی، حدود 150 کیلوبایت ذخیره کردیم. متأسفانه، اندازه فایل gzip شده به میزان قابل توجهی افزایش یافته است.
این اتفاق می افتد زیرا دستورات مسیر اغلب تکرار می شوند. یک مسیر SVG ممکن است حاوی دستورات MLLLQLLQLLQLL باشد که زیر مجموعههای آن رشته را در کل مسیر تکرار میکند. هنگامی که هر دستور مسیر دقیقاً یک بایت مصرف می کند، تکرار در خروجی وجود دارد. هنگامی که نسخه های کدگذاری شده باینری بایتی تراز نشده باشند، تکرار بسیار کمتری در خروجی وجود دارد. برای gzip، این شبیه نویز تصادفی است. با فشردهتر کردن دادهها، آنها را کمتر فشردهسازی کردهایم!
من این را کمی انتخاب کردم و متوجه شدم که opentype.js فقط از چهار دستور مختلف استفاده می کند: M، L، Q، و Z. این بدان معناست که ما می توانیم هر دستور را تنها در دو بیت قرار دهیم و هیچ دستوری هرگز بایت ها را متقاطع نمی کند (از 8 تا مضرب 2 است)، بنابراین gzip باید با کل فرآیند بسیار راحت تر باشد.
کاهش تعداد بیت ها از 5 به 2 کمی کمک کرد، اما نه مقدار زیادی:
بایت های ذخیره شده: -440311
نسبت: 1.1279486700544477
نسبت Gzip: 1.443929762564997
خروجی جدید تنها 12 درصد بزرگتر از خروجی اصلی است، اما ما همچنان 44 درصد در خروجی gzipped بزرگتر هستیم، هرچند بهتر از قبل.
بسته بندی پارامترها
من یک تکه بزرگ از میوه های کم آویزان را روی درخت اینجا گذاشته ام: پارامترها. چهار بایت در هر پارامتر بسیار بیشتر از نیاز است. من ابتدا فقط به نصف کردن آن و ایجاد اعداد ممیز شناور 16 بیتی فکر کردم، اما این هنوز هم بسیار بیهوده است. اعداد ممیز شناور از این جهت جالب هستند که از -Infinity به +Infinity می روند و 50% اعداد دقیقاً قابل نمایش بین -1 و +1 هستند. این بیهوده است زیرا میتوانیم با خیال راحت مقادیر اعشاری اعداد خود را به تعداد کمی از ارقام مهم کوتاه کنیم (اکثر افراد SVG خود را با دقت 1 تا 3 رقم اعشار کوتاه میکنند). ما قطعاً به اندازه اعشار ممیز شناور به دقت اعشار نیاز نداریم: اگر به دو رقم اعشار کوتاه کنیم و 50 درصد از اعداد ممیز شناور بین 1- و 1 باشد، به این معنی است که تقریباً نیمی از اعشار را تلف میکنیم. 32 بیت ما برای ذخیره حدود 20 مقدار ممکن: -0.9، -0.8، … 0.8، 0.9.
اعداد ممیز شناور در اینجا نمایش بدی هستند. یک تغییر ساده می تواند استفاده از اعداد صحیح و تقسیم بر 10 باشد. با اعداد صحیح امضا شده 8 بیتی، این امکان را به 12.7- تا 12.7 می دهد. متأسفانه، این تقریباً کافی نیست، با مقادیر واقعی پارامترها به صدها عدد.
یکی از ویژگیهای جالب این اعداد این است که چند مقدار وجود دارد که در همه SVGها هزاران بار ظاهر میشوند و هزاران عددی که کمتر از ده بار استفاده میشوند. سیصد مقدار دقیقاً یک بار استفاده می شود.
من تعداد دفعات هر مقدار عددی را در همه فونت ها محاسبه کردم.
من این داده ها را در قالب CSV به یک صفحه گسترده وارد کردم و آن را ترسیم کردم. این کار بر اساس نوع فرمان مسیر انجام شد که در یک دقیقه در مورد آن بحث خواهم کرد.
اینها فرکانسهایی برای نوع فرمان مسیر “M” هستند. نمودار برای نشان دادن 10 درصد بالای مقادیر عددی کوتاه شده است.
ساخت انتخابگر فونت در جاوا اسکریپت
در اینجا می توانید ببینید که صفر محبوب ترین عدد برای نوع دستور مسیر M است و به دنبال آن 0.2، 0.3 و 0.1 قرار می گیرد. صفر، در این مورد، 660 بار دیده می شود. Q و L هر کدام اعداد بسیار دراماتیک تری دارند و هزاران نمونه 0 و 0.2 دارند.
یک راه حل در اینجا استفاده از کد نویسی هافمن است. کدگذاری هافمن فرآیندی است که در آن به رایجترین نمادها در یک دنباله کوتاهترین نمایش داده میشود، در حالی که کممعمولترین نمادها در دنباله، نمایشهای بسیار طولانیتری دارند. این اجازه می دهد تا مقادیر بسیار رایج با بیت های بسیار کمتری رمزگذاری شوند.
این فرآیند با هر مقدار عددی در SVG های ما به عنوان یک نماد رفتار می کند. مهم نیست که ارزش چقدر دقیق است یا حتی مقدار آن چقدر است. آنچه مهم است، اصلی بودن مجموعه مقادیر پارامتر ما است. به عنوان مثال، یک صفر می تواند به صورت سه بیت رمزگذاری شود. 234.5 که فقط یک بار ظاهر می شود، ممکن است پانزده بیت یا بیشتر مصرف کند.
اجرای این امر چالش برانگیز بود. هیچ کتابخانه کدگذاری عمومی هافمن به صورت آنلاین موجود نیست که نه تنها نمایش بیت، بلکه مجموعه ای از بیت ها را نیز تولید کند (مثلاً با یک ArrayBuffer سازگار است). من مقداری کد را از کتابخانه huffman_js در Github تطبیق دادم تا یک پیادهسازی ایجاد کنم که نمادهای دلخواه (به جای کاراکترها) را بپذیرد تا درخت مورد استفاده برای فرمولبندی کد هافمن را بسازد.
تلاش اولیه من یک درخت هافمن برای همه مقادیر عددی در همه دستورات در همه مسیرها ایجاد کرد. در بالای تلاش قبلی، این نتایج را دریافت می کنیم:
بایت ذخیره شده: 2180514
نسبت: 0.3654320060935835
نسبت Gzip: 1.0707779233309709
وای! این بسیار خوب است (2 مگابایت ذخیره شده!)، با توجه به اینکه مسیرها – در این مرحله – بدون تلفات رمزگذاری شده اند. ما همچنین واقعاً زمان زیادی را برای بهینه سازی چیزها صرف نکرده ایم.
آنچه نگران کننده است نسبت gzip است. هنوز بیش از 1 است، به این معنی که با وجود تلاشهای شجاعانه ما، باز هم بارگیری نتیجه بیشتر طول میکشد. این 7 درصد بزرگتر است، که اطمینان بخش نیست، اما با توجه به اینکه ascii85 سربار اضافه می کند، بسیار مناسب است.
از دست دادن
تاکنون تلاش های من بر روی عملیات بدون ضرر متمرکز بوده است. این به معنای رمزگذاری به گونه ای است که هیچ اطلاعاتی از بین نرود: خروجی کل فرآیند با ورودی یکسان است. نقطه مقابل رمزگذاری بدون اتلاف، رمزگذاری با اتلاف است. این زمانی است که اطلاعات را دور می ریزید و کیفیت خروجی را برای صرفه جویی در فضا کاهش می دهید، مانند یک تصویر JPEG.
اولین چیزی که در ذهن من است، کاهش دقت مقادیر عددی “بزرگ” است. تفاوت بین 0.2 و 0.3 می تواند قابل توجه باشد، به خصوص اگر صدها بار تکرار شود. اگرچه تفاوت بین 141.1 و 141.5 تقریباً غیرقابل تشخیص است.
من آستانههای کاملاً دلخواه را برای دور کردن دور پیدا کردم. این چیزی است که من استفاده کردم:
function lossify(value) {
if (Math.abs(value) > 70) {
return Math.round(value / 4) * 4;
}
if (Math.abs(value) > 40) {
return Math.round(value / 3) * 3;
}
if (Math.abs(value) > 20) {
return Math.round(value / 2) * 2;
}
if (Math.abs(value) > 10) {
return Math.round(value);
}
return value;
}
اساساً، هرچه یک عدد از صفر دورتر شود، زیان آن بیشتر می شود. یک خط عددی را تصور کنید: میخواهیم مقادیری را که روی خط اعداد رسم میکنیم به مقادیر از پیش تعریفشده «snap» بزنند. اما هر چه از صفر در خط عددی که در هر دو جهت قرار می گیرید دورتر باشد، فاصله این نقاط ضربه ای بیشتر می شود. نزدیک به صفر، اعداد خیلی دور نیستند. دورتر از صفر، اعداد در هر دو جهت دورتر می شوند.

ایده در اینجا این است که گرد کردن اعداد، کاردینالیته مجموعه مقادیر عددی رمزگذاری شده را کاهش می دهد، که باید تعداد نمادهای رمزگذاری شده را کاهش دهد، که باید طول مقادیر تولید شده توسط کدگذاری هافمن را کاهش دهد.
ممکن است کنجکاو باشید که این چگونه بر خروجی رندر شده تأثیر می گذارد. پاسخ این است که حداقل برای من از نظر بصری نامحسوس است.
بیایید ببینیم که چگونه بر خروجی ما تأثیر گذاشت:
بایت ذخیره شده: 2452764
نسبت: 0.2933209157366562
نسبت Gzip: 0.8354983617125883
عالی! ما به نقطهای رسیدهایم که خروجی gzip ما کوچکتر از فایل gzip اصلی است، در حدود 16%.
یکی دیگر از تکنیک های ضرری که من بررسی کردم استفاده از ساده سازی مسیر است. من یک پیاده سازی آزمایشی را با استفاده از الگوریتم ساده سازی مسیر کتابخانه افسانه ای Paper.js ایجاد کردم، اما متوجه شدم که نتایج، زمانی که رندر می شوند، خوب نیستند. فونت ها جزئیات بسیار ریز زیادی دارند که نمی توان آنها را در الگوریتم ساده سازی موجود قرار داد. شاید راهی برای تخصصی کردن یکی از آن الگوریتمها برای استفاده با فونتها وجود داشته باشد، اما این کار برای فردی بسیار باهوشتر از من است.
کدنویسی هافمن بیشتر
انجام پروژه برنامه نویسی(در تب جدید مرورگر باز می شود )
من در ابتدا این فرضیه را داشتم که استفاده از سه درخت هافمن جداگانه (یکی برای هر نوع دستور مسیر) نتایج بهتری به همراه خواهد داشت. مقادیر عددی مشترک برای هر نوع فرمان (از لحاظ نظری) با رشته های کوچکتری از بیت ها مرتبط است. اگر 0.2 رایج تر از 0 برای a باشد
نوع، 0.2 رشته کوچکتری دریافت می کند.
من اینو تست کردم من یک پیادهسازی از کتابخانه کدگذاری هافمن خود ایجاد کردم که به شما امکان میدهد درختی را برای رمزگذاری و رمزگشایی نماد بعدی مشخص کنید. نتایج خوب نبود.
بایت ذخیره شده: 2453094
نسبت: 0.29434068322385015
نسبت Gzip: 0.8449959788970782
ساخت انتخابگر فونت در جاوا اسکریپت
توجه داشته باشید که تعداد بایت های ذخیره شده بیشتر از نتیجه قبلی است، اما نسبت بدتر است. این به این دلیل است که هر مسیر جداگانه فضای کمتری را اشغال می کند (چند بایت) اما اندازه کل فایل بزرگتر است زیرا نیاز به ذخیره دو کد هافمن اضافی دارد.
ساخت انتخابگر فونت در جاوا اسکریپت
سپس به ایده دیگری رسیدم: ما دستورات را به صورت دو بیتی در UintNArray خود ذخیره می کنیم، اما برخی از دستورات بسیار بیشتر از بقیه ظاهر می شوند. به جای اینکه آن بیت ها را به سادگی جمع کنیم، می توانیم یک کد هافمن برای آن ها نیز ایجاد کنیم!
طراحی لوگو با Illustrator(در تب جدید مرورگر باز می شود )
بایت ذخیره شده: 2479326
نسبت: 0.2862957215153192
نسبت Gzip: 0.8343815695650806
نتایج در اینجا یک بهبود است، اما نه بزرگ. فقط یکی از دستورات به یک بیت تبدیل می شود (اعطا شده است، اغلب ظاهر می شود)، و کم رایج ترین آنها به سه بیت تبدیل می شوند. به طور کلی یک برد خالص است، اما وقتی شما فقط چند صد (یا چند ده) فرمان در هر مسیر دارید، تعداد بیت های زیادی نیست. در مورد ما، در کل حدود 20000 بود (با 1000 فونت، این چیزی است که من انتظار دارم).
فکر بعدی من این است که دنباله ای از دستورات را در یک واحد ذخیره کنم نه اینکه هر فرمان را به عنوان نماد خودش در نظر بگیرم. در اینجا دستورات فونت Acme آمده است:
MLLLLLLLLLLZMLLLLZMLLQLLQLLQLLQLLQLLQLLLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLLQZMLQLLQLLQLLLLLLQLLLQLLQLLQLLQLLQLLQLLQLLQLLQLLLLLLQLLQLLQLLLLLZMLLQLLQLLLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLQLLLLQZMLLQLLQLLLQLLQZ
من تمام دستورات M و Z را برجسته کرده ام تا کار را آسان تر کنم. چند نکته جالب توجه:
هر “قطعه” از یک علامت با علامت M شروع می شود و با L و Q دنبال می شود تا زمانی که به یک Z برسد.
Q و M تقریبا همیشه با یک یا چند L دنبال می شوند.
دنباله های [MQ]L+ اغلب تکرار می شوند.
تصمیم گرفتم با تطبیق هر رشته از دستورات با عبارت منظم/([MQ]L*|Z)/g، نمادها را ایجاد کنم، نه اینکه هر دستور را به عنوان یک نماد در نظر بگیرم. فقط کمی زحمت کشید تا اجرای کدنویسی هافمن به خوبی اجرا شود و نتایج بد نیستند:
بایت ذخیره شده: 2513864
نسبت: 0.2758463779197209
نسبت Gzip: 0.8172053280427702
با ادامه پیشرفت خوب، چند درصد دیگر از بسته نرم افزاری gzip شده کاهش می یابد.
من با بیان منظم بازی کردم و ترکیبهای متفاوتی را امتحان کردم که میتوانست سرعت فشردهسازی را بهبود بخشد. برخی از تلاش ها:
محدود کردن تعداد Lهای متوالی. تعداد زیادی از دستورات L متوالی بعد از M یا Q می تواند منجر به نمادهای کم استفاده شود، در حالی که دو نماد کوتاهتر (مانند MLLLL و LLLL در مقابل MLLLLLLLL) می توانند بیشتر مورد استفاده قرار گیرند. این تاثیر منفی داشت.
تطبیق QZ به عنوان نماد خود.
تطبیق دستورات متوالی Q. این هیچ تاثیری نداشت.
من روی /(QZ|[MQ]L*|Z)/ مستقر شدم زیرا ظاهراً بهترین نتایج را ایجاد می کند:
بایت ذخیره شده: 2515364
نسبت: 0.27538726434060756
نسبت Gzip: 0.8158475953153337
راه های بالقوه دیگر برای فشرده سازی
نتیجه بالا همان جایی است که من متوقف شدم. اما این بدان معنا نیست که من ایده های اضافی کم دارم! برخی از چیزهایی که می خواهم در آینده با زمان بیشتری امتحان کنم:
M، Q و L جفت پارامتر را می پذیرند (Q دو جفت را می پذیرد). جالب است که بررسی کنیم که آیا این جفت ها می توانند به عنوان نمادهای خود برای صرفه جویی در فضا در نظر گرفته شوند.
شاید در ترکیب با ایده قبلی، دستورات SVG که از حروف بزرگ استفاده می کنند از مختصات مطلق استفاده می کنند. انواع کوچک وجود دارد که از مختصات نسبی استفاده می کنند. هنگام استفاده از مختصات نسبی، مقادیر به احتمال زیاد کوچکتر هستند. این احتمال وجود دارد که اگر مقادیر کوچک بیشتری وجود داشته باشد، کاردینالیته مقادیر پارامتر را می توان کاهش داد. تبدیل از مختصات مطلق به نسبی یک ماجراجویی خواهد بود، اما می تواند یک برد قابل توجه به همراه داشته باشد.
من نگاه نکردهام، اما گمان میکنم svgomg نوعی الگوریتم سادهسازی مسیر بدون تلفات را تعبیه کرده است. جالب است که بررسی کنیم که آیا این کار را انجام می دهد، و اگر چنین است، چگونه کار می کند. یک الگوریتم سادهسازی بدون تلفات به سختی وارد کد نمیشود.
مانند ساده سازی مسیر، توالی دستورات L را می توان به راحتی ساده کرد. L خطی را از موقعیت مکان نما به نقطه ای که توسط پارامترها تعریف شده است رسم می کند. اگر زاویه ای که دو خط ایجاد می کنند دارای زاویه کافی نزدیک به 180 درجه باشد، ممکن است برخی از خطوط میانی کاملاً قابل جابجایی باشند.
پیاده سازی
ساخت انتخابگر فونت در جاوا اسکریپت
داشتن نسخه های فشرده این فونت ها عالی است. با وجود استفاده از حافظه، رمزگشایی آنها در مرورگر گران نیست. من نتوانستم هیچ مکث قابل توجهی را هنگام انجام فرآیند رمزگشایی اندازه گیری کنم.
پیادهسازی انتخابگر فونت جادوی بسیار کمی دارد: این یک کشویی سفارشی ساده با استفاده از react-list برای به حداقل رساندن تعداد گرهها در صفحه در یک زمان است.
انجام پروژه برنامه نویسی(در تب جدید مرورگر باز می شود )
بسته نرم افزاری اصلی جاوا اسکریپت لیست فونت ها را می شناسد، بنابراین می تواند یک مورد را در فهرست کشویی به ازای هر خانواده فونت ارائه کند (افتاده
بر روی یک تایپ پیش فرض که نام را در حین بارگیری مسیرهای SVG نشان می دهد. مسیرهای SVG و یک مؤلفه برای رندر کردن آنها با React.lazy بارگذاری می شوند. کامپوننت به سادگی نام خانواده فونت را به عنوان پایه می پذیرد.
نتیجه یک فهرست کشویی اسکرول هموار از نامهای خانواده فونت است که با حروف چاپی خود بهعنوان یک بردار، با اندازه فایل نسبتاً کوچکی ارائه میشوند. خیلی هم بد نیست!
در حال تمام شدن
بیایید در مورد لوگوتایپ ها صحبت کنیم(در تب جدید مرورگر باز می شود )
این خیلی کار بود، اما در این راه چیزهای زیادی یاد گرفتم. در پایان، من فقط حدود 18٪ از اندازه نهایی بسته نرم افزاری gzipped صرفه جویی کردم. این یک قرص برای قورت دادن کمی سخت است. همانطور که مشخص است Gzip در فشرده سازی محتوا با تکرار فوق العاده خوب است. و این چیز بدی نیست! من آن را اندازهگیری نکردم، اما اگر بروتلی کار موثرتری برای فشردهسازی دادهها انجام دهد، تعجب نمیکنم. حتی اگر نسبت Brotli بسیار نزدیک به 1 باشد، باندل حاصل هنوز بسیار کوچک است.
طبیعتاً، باید از خود بپرسیم که آیا معاوضه بین زمان محاسبه (در این مورد، زمان رمزگشایی) و ذخیره سازی (در این مورد، پهنای باند استفاده شده است). در این مورد، زمان محاسبه به اندازهای ناچیز است که حتی اگر 10 برابر کار را انجام دهیم، باز هم معامله ارزشمند خواهد بود.
ساخت انتخابگر فونت در جاوا اسکریپت
تأثیر دنیای واقعی در اینجا چیست؟ خوب، اندازه بسته نرم افزاری بین استقرار متفاوت است. در حال حاضر، قطعه حاوی اطلاعات فونت در حدود 950 کیلوبایت gzip شده است. این به این معنی است که اندازه اصلی gzip حدود 1.12 مگابایت خواهد بود، که میتوانیم بگوییم که فقط یک چهارم مگابایت تفاوت دارد. این بی معنی نیست! برای کاربری که اتصال آهسته دارد، این یک چهارم مگابایت می تواند چند ثانیه باشد.
وقتی این کار را انجام میدادم، کمپرسور تصویر لپتون Dropbox را در ذهنم داشتم. لپتون با اعمال فشرده سازی بدون اتلاف بر روی JPEG ها به گونه ای کار می کند که gzip به تنهایی نمی تواند. Gzip، با وجود اینکه بسیار خوب است، هیچ اطلاعی از آنچه در یک JPEG وجود دارد، ندارد. لپتون درعوض از تکنیکهای مختص به نحوه کدگذاری یک JPEG استفاده میکند و توانایی ذخیره بایتهایی را که یک الگوریتم فشردهسازی ساده نمیتواند به تنهایی ذخیره کند، باز میکند.
اکنون، البته، هدف در اینجا بسیار متفاوت است: Dropbox میخواهد فضای ذخیرهسازی را در سرورهای خود ذخیره کند، در حالی که Pinecast میخواهد تعداد بایتهای ارسال شده از طریق سیم به مرورگرهای مشتریان را کاهش دهد. معاوضه هزینه/زمان/منابع بسیار متفاوت است، اما اصول فنی اساسی بسیار مشابه هستند. و از نظر نتایج، دیدن این که Dropbox به 22% فشردهسازی دست یافت، دلگرمکننده است، در حالی که من به 18% فشردهسازی اضافی دست یافتم.
ساخت انتخابگر فونت در جاوا اسکریپت
به طور کلی، من از نتیجه اینجا راضی هستم. 18٪ بهترین نتیجه مطلق نیست که بتوانم به آن امیدوار باشم، اما خروجی فشرده نشده هنوز تقریباً 73٪ کوچکتر است، که به عنوان یک تمرین کاملاً آکادمیک بسیار جالب است. من احتمالاً زمانی این کار را دوباره بررسی خواهم کرد تا ببینم آیا نمی توانم بایت های بیشتری را جمع کنم. اگر ایده یا پیشنهادی دارید، لطفاً در تماس باشید.


نظرات